Explorar o código

Add export section in setting for saving and exporting settings (#476)

* Add export section in setting for saving and exporting settings
* Add import buttons on first start and preview when importing
* Add done button to setup modals and clear first setup buttons when credentials is set
* Refactor QR code scanner to UIKit
* Add loading screen on startup
* Add Dexcom settings export and import functionality with persistent  iCloud support
Daniel Mini Johansson hai 7 meses
pai
achega
d719e55866

+ 82 - 52
LoopFollow.xcodeproj/project.pbxproj

@@ -17,6 +17,23 @@
 		656F8C122E49F3780008DC1D /* QRCodeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 656F8C112E49F3780008DC1D /* QRCodeGenerator.swift */; };
 		656F8C122E49F3780008DC1D /* QRCodeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 656F8C112E49F3780008DC1D /* QRCodeGenerator.swift */; };
 		656F8C142E49F3D20008DC1D /* RemoteCommandSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 656F8C132E49F3D20008DC1D /* RemoteCommandSettings.swift */; };
 		656F8C142E49F3D20008DC1D /* RemoteCommandSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 656F8C132E49F3D20008DC1D /* RemoteCommandSettings.swift */; };
 		6584B1012E4A263900135D4D /* TOTPService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6584B1002E4A263900135D4D /* TOTPService.swift */; };
 		6584B1012E4A263900135D4D /* TOTPService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6584B1002E4A263900135D4D /* TOTPService.swift */; };
+		6589CC622E9E7D1600BB18FE /* ImportExportSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6589CC532E9E7D1600BB18FE /* ImportExportSettingsView.swift */; };
+		6589CC632E9E7D1600BB18FE /* GeneralSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6589CC5D2E9E7D1600BB18FE /* GeneralSettingsView.swift */; };
+		6589CC642E9E7D1600BB18FE /* ContactSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6589CC592E9E7D1600BB18FE /* ContactSettingsView.swift */; };
+		6589CC652E9E7D1600BB18FE /* DexcomSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6589CC5C2E9E7D1600BB18FE /* DexcomSettingsViewModel.swift */; };
+		6589CC662E9E7D1600BB18FE /* AdvancedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6589CC562E9E7D1600BB18FE /* AdvancedSettingsView.swift */; };
+		6589CC672E9E7D1600BB18FE /* ImportExportSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6589CC542E9E7D1600BB18FE /* ImportExportSettingsViewModel.swift */; };
+		6589CC682E9E7D1600BB18FE /* ExportableSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6589CC522E9E7D1600BB18FE /* ExportableSettings.swift */; };
+		6589CC692E9E7D1600BB18FE /* ContactSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6589CC5A2E9E7D1600BB18FE /* ContactSettingsViewModel.swift */; };
+		6589CC6A2E9E7D1600BB18FE /* DexcomSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6589CC5B2E9E7D1600BB18FE /* DexcomSettingsView.swift */; };
+		6589CC6B2E9E7D1600BB18FE /* TabCustomizationModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6589CC602E9E7D1600BB18FE /* TabCustomizationModal.swift */; };
+		6589CC6C2E9E7D1600BB18FE /* GraphSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6589CC5E2E9E7D1600BB18FE /* GraphSettingsView.swift */; };
+		6589CC6D2E9E7D1600BB18FE /* CalendarSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6589CC582E9E7D1600BB18FE /* CalendarSettingsView.swift */; };
+		6589CC6E2E9E7D1600BB18FE /* SettingsMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6589CC5F2E9E7D1600BB18FE /* SettingsMenuView.swift */; };
+		6589CC6F2E9E7D1600BB18FE /* AdvancedSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6589CC572E9E7D1600BB18FE /* AdvancedSettingsViewModel.swift */; };
+		6589CC712E9E814F00BB18FE /* AlarmSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6589CC702E9E814F00BB18FE /* AlarmSelectionView.swift */; };
+		6589CC752E9EAFB700BB18FE /* SettingsMigrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6589CC742E9EAFB700BB18FE /* SettingsMigrationManager.swift */; };
+		65E153C32E4BB69100693A4F /* URLTokenValidationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E153C22E4BB69100693A4F /* URLTokenValidationView.swift */; };
 		65E153C32E4BB69100693A4F /* URLTokenValidationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E153C22E4BB69100693A4F /* URLTokenValidationView.swift */; };
 		65E153C32E4BB69100693A4F /* URLTokenValidationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E153C22E4BB69100693A4F /* URLTokenValidationView.swift */; };
 		65E8A2862E44B0300065037B /* VolumeButtonHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E8A2852E44B0300065037B /* VolumeButtonHandler.swift */; };
 		65E8A2862E44B0300065037B /* VolumeButtonHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E8A2852E44B0300065037B /* VolumeButtonHandler.swift */; };
 		DD0247592DB2E89600FCADF6 /* AlarmCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0247582DB2E89600FCADF6 /* AlarmCondition.swift */; };
 		DD0247592DB2E89600FCADF6 /* AlarmCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0247582DB2E89600FCADF6 /* AlarmCondition.swift */; };
@@ -52,14 +69,10 @@
 		DD16AF0D2C98485400FB655A /* SecureStorageValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD16AF0C2C98485400FB655A /* SecureStorageValue.swift */; };
 		DD16AF0D2C98485400FB655A /* SecureStorageValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD16AF0C2C98485400FB655A /* SecureStorageValue.swift */; };
 		DD16AF0F2C99592F00FB655A /* HKQuantityInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD16AF0E2C99592F00FB655A /* HKQuantityInputView.swift */; };
 		DD16AF0F2C99592F00FB655A /* HKQuantityInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD16AF0E2C99592F00FB655A /* HKQuantityInputView.swift */; };
 		DD16AF112C997B4600FB655A /* LoadingButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD16AF102C997B4600FB655A /* LoadingButtonView.swift */; };
 		DD16AF112C997B4600FB655A /* LoadingButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD16AF102C997B4600FB655A /* LoadingButtonView.swift */; };
-		DD1A97142D4294A5000DDC11 /* AdvancedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1A97132D4294A4000DDC11 /* AdvancedSettingsView.swift */; };
-		DD1A97162D4294B3000DDC11 /* AdvancedSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1A97152D4294B2000DDC11 /* AdvancedSettingsViewModel.swift */; };
 		DD1D52B92E1EB5DC00432050 /* TabPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1D52B82E1EB5DC00432050 /* TabPosition.swift */; };
 		DD1D52B92E1EB5DC00432050 /* TabPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1D52B82E1EB5DC00432050 /* TabPosition.swift */; };
 		DD1D52BB2E1EB60B00432050 /* MoreMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1D52BA2E1EB60B00432050 /* MoreMenuViewController.swift */; };
 		DD1D52BB2E1EB60B00432050 /* MoreMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1D52BA2E1EB60B00432050 /* MoreMenuViewController.swift */; };
 		DD2C2E4F2D3B8AF1006413A5 /* NightscoutSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2C2E4E2D3B8AEC006413A5 /* NightscoutSettingsView.swift */; };
 		DD2C2E4F2D3B8AF1006413A5 /* NightscoutSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2C2E4E2D3B8AEC006413A5 /* NightscoutSettingsView.swift */; };
 		DD2C2E512D3B8B0C006413A5 /* NightscoutSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2C2E502D3B8B0B006413A5 /* NightscoutSettingsViewModel.swift */; };
 		DD2C2E512D3B8B0C006413A5 /* NightscoutSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2C2E502D3B8B0B006413A5 /* NightscoutSettingsViewModel.swift */; };
-		DD2C2E542D3C37DC006413A5 /* DexcomSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2C2E532D3C37D7006413A5 /* DexcomSettingsViewModel.swift */; };
-		DD2C2E562D3C3917006413A5 /* DexcomSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2C2E552D3C3913006413A5 /* DexcomSettingsView.swift */; };
 		DD485F142E454B2600CE8CBF /* SecureMessenger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD485F132E454B2600CE8CBF /* SecureMessenger.swift */; };
 		DD485F142E454B2600CE8CBF /* SecureMessenger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD485F132E454B2600CE8CBF /* SecureMessenger.swift */; };
 		DD485F162E46631000CE8CBF /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = DD485F152E46631000CE8CBF /* CryptoSwift */; };
 		DD485F162E46631000CE8CBF /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = DD485F152E46631000CE8CBF /* CryptoSwift */; };
 		DD4878032C7B297E0048F05C /* StorageValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4878022C7B297E0048F05C /* StorageValue.swift */; };
 		DD4878032C7B297E0048F05C /* StorageValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4878022C7B297E0048F05C /* StorageValue.swift */; };
@@ -93,8 +106,6 @@
 		DD4AFB612DB68BBC00BB593F /* AlarmListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4AFB602DB68BBC00BB593F /* AlarmListView.swift */; };
 		DD4AFB612DB68BBC00BB593F /* AlarmListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4AFB602DB68BBC00BB593F /* AlarmListView.swift */; };
 		DD4AFB672DB68C5500BB593F /* UUID+Identifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4AFB662DB68C5500BB593F /* UUID+Identifiable.swift */; };
 		DD4AFB672DB68C5500BB593F /* UUID+Identifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4AFB662DB68C5500BB593F /* UUID+Identifiable.swift */; };
 		DD4AFB6B2DB6BF2A00BB593F /* Binding+Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4AFB6A2DB6BF2A00BB593F /* Binding+Optional.swift */; };
 		DD4AFB6B2DB6BF2A00BB593F /* Binding+Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4AFB6A2DB6BF2A00BB593F /* Binding+Optional.swift */; };
-		DD50C7502D0828800057AE6F /* ContactSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD50C74F2D0828800057AE6F /* ContactSettingsViewModel.swift */; };
-		DD50C7532D0828D10057AE6F /* ContactSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD50C7522D0828D10057AE6F /* ContactSettingsView.swift */; };
 		DD50C7552D0862770057AE6F /* ContactImageUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD50C7542D0862770057AE6F /* ContactImageUpdater.swift */; };
 		DD50C7552D0862770057AE6F /* ContactImageUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD50C7542D0862770057AE6F /* ContactImageUpdater.swift */; };
 		DD5334212C60EBEE00062F9D /* InsulinCartridgeChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5334202C60EBEE00062F9D /* InsulinCartridgeChange.swift */; };
 		DD5334212C60EBEE00062F9D /* InsulinCartridgeChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5334202C60EBEE00062F9D /* InsulinCartridgeChange.swift */; };
 		DD5334232C60ED3600062F9D /* IAge.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5334222C60ED3600062F9D /* IAge.swift */; };
 		DD5334232C60ED3600062F9D /* IAge.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5334222C60ED3600062F9D /* IAge.swift */; };
@@ -139,15 +150,10 @@
 		DD7F4C232DD7A62200D449E9 /* AlarmType+SortDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7F4C222DD7A62200D449E9 /* AlarmType+SortDirection.swift */; };
 		DD7F4C232DD7A62200D449E9 /* AlarmType+SortDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7F4C222DD7A62200D449E9 /* AlarmType+SortDirection.swift */; };
 		DD7F4C252DD7B20700D449E9 /* AlarmType+timeUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7F4C242DD7B20700D449E9 /* AlarmType+timeUnit.swift */; };
 		DD7F4C252DD7B20700D449E9 /* AlarmType+timeUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7F4C242DD7B20700D449E9 /* AlarmType+timeUnit.swift */; };
 		DD7FFAFD2A72953000C3A304 /* EKEventStore+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7FFAFC2A72953000C3A304 /* EKEventStore+Extensions.swift */; };
 		DD7FFAFD2A72953000C3A304 /* EKEventStore+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7FFAFC2A72953000C3A304 /* EKEventStore+Extensions.swift */; };
-		DD8060DB2E2ACE5900626B91 /* TabCustomizationModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8060DA2E2ACE5900626B91 /* TabCustomizationModal.swift */; };
-		DD8316182DE3633D004467AA /* GeneralSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8316172DE3633D004467AA /* GeneralSettingsView.swift */; };
 		DD8316442DE47CA9004467AA /* BGPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8316432DE47CA9004467AA /* BGPicker.swift */; };
 		DD8316442DE47CA9004467AA /* BGPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8316432DE47CA9004467AA /* BGPicker.swift */; };
-		DD8316462DE49B09004467AA /* GraphSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8316452DE49B09004467AA /* GraphSettingsView.swift */; };
 		DD8316482DE49EE5004467AA /* Storage+Migrate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8316472DE49EE5004467AA /* Storage+Migrate.swift */; };
 		DD8316482DE49EE5004467AA /* Storage+Migrate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8316472DE49EE5004467AA /* Storage+Migrate.swift */; };
 		DD83164A2DE4C504004467AA /* SettingsStepperRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8316492DE4C504004467AA /* SettingsStepperRow.swift */; };
 		DD83164A2DE4C504004467AA /* SettingsStepperRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8316492DE4C504004467AA /* SettingsStepperRow.swift */; };
 		DD83164C2DE4DB3A004467AA /* BinaryFloatingPoint+localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD83164B2DE4DB3A004467AA /* BinaryFloatingPoint+localized.swift */; };
 		DD83164C2DE4DB3A004467AA /* BinaryFloatingPoint+localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD83164B2DE4DB3A004467AA /* BinaryFloatingPoint+localized.swift */; };
-		DD83164E2DE4E093004467AA /* CalendarSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD83164D2DE4E093004467AA /* CalendarSettingsView.swift */; };
-		DD8316502DE4E635004467AA /* SettingsMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD83164F2DE4E635004467AA /* SettingsMenuView.swift */; };
 		DD85E9952D739CFE001C8BB7 /* OmnipodDashHeartbeatBluetoothTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD85E9942D739CED001C8BB7 /* OmnipodDashHeartbeatBluetoothTransmitter.swift */; };
 		DD85E9952D739CFE001C8BB7 /* OmnipodDashHeartbeatBluetoothTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD85E9942D739CED001C8BB7 /* OmnipodDashHeartbeatBluetoothTransmitter.swift */; };
 		DD91E4DD2BDEC3F8002D9E97 /* GlucoseConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD91E4DC2BDEC3F8002D9E97 /* GlucoseConversion.swift */; };
 		DD91E4DD2BDEC3F8002D9E97 /* GlucoseConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD91E4DC2BDEC3F8002D9E97 /* GlucoseConversion.swift */; };
 		DD98F54424BCEFEE0007425A /* ShareClientExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD98F54324BCEFEE0007425A /* ShareClientExtension.swift */; };
 		DD98F54424BCEFEE0007425A /* ShareClientExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD98F54324BCEFEE0007425A /* ShareClientExtension.swift */; };
@@ -411,6 +417,23 @@
 		656F8C112E49F3780008DC1D /* QRCodeGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeGenerator.swift; sourceTree = "<group>"; };
 		656F8C112E49F3780008DC1D /* QRCodeGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeGenerator.swift; sourceTree = "<group>"; };
 		656F8C132E49F3D20008DC1D /* RemoteCommandSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteCommandSettings.swift; sourceTree = "<group>"; };
 		656F8C132E49F3D20008DC1D /* RemoteCommandSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteCommandSettings.swift; sourceTree = "<group>"; };
 		6584B1002E4A263900135D4D /* TOTPService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TOTPService.swift; sourceTree = "<group>"; };
 		6584B1002E4A263900135D4D /* TOTPService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TOTPService.swift; sourceTree = "<group>"; };
+		6589CC522E9E7D1600BB18FE /* ExportableSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportableSettings.swift; sourceTree = "<group>"; };
+		6589CC532E9E7D1600BB18FE /* ImportExportSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportExportSettingsView.swift; sourceTree = "<group>"; };
+		6589CC542E9E7D1600BB18FE /* ImportExportSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportExportSettingsViewModel.swift; sourceTree = "<group>"; };
+		6589CC562E9E7D1600BB18FE /* AdvancedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsView.swift; sourceTree = "<group>"; };
+		6589CC572E9E7D1600BB18FE /* AdvancedSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsViewModel.swift; sourceTree = "<group>"; };
+		6589CC582E9E7D1600BB18FE /* CalendarSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarSettingsView.swift; sourceTree = "<group>"; };
+		6589CC592E9E7D1600BB18FE /* ContactSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSettingsView.swift; sourceTree = "<group>"; };
+		6589CC5A2E9E7D1600BB18FE /* ContactSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSettingsViewModel.swift; sourceTree = "<group>"; };
+		6589CC5B2E9E7D1600BB18FE /* DexcomSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomSettingsView.swift; sourceTree = "<group>"; };
+		6589CC5C2E9E7D1600BB18FE /* DexcomSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomSettingsViewModel.swift; sourceTree = "<group>"; };
+		6589CC5D2E9E7D1600BB18FE /* GeneralSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsView.swift; sourceTree = "<group>"; };
+		6589CC5E2E9E7D1600BB18FE /* GraphSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphSettingsView.swift; sourceTree = "<group>"; };
+		6589CC5F2E9E7D1600BB18FE /* SettingsMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsMenuView.swift; sourceTree = "<group>"; };
+		6589CC602E9E7D1600BB18FE /* TabCustomizationModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabCustomizationModal.swift; sourceTree = "<group>"; };
+		6589CC702E9E814F00BB18FE /* AlarmSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmSelectionView.swift; sourceTree = "<group>"; };
+		6589CC742E9EAFB700BB18FE /* SettingsMigrationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsMigrationManager.swift; sourceTree = "<group>"; };
+		65E153C22E4BB69100693A4F /* URLTokenValidationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLTokenValidationView.swift; sourceTree = "<group>"; };
 		65E153C22E4BB69100693A4F /* URLTokenValidationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLTokenValidationView.swift; sourceTree = "<group>"; };
 		65E153C22E4BB69100693A4F /* URLTokenValidationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLTokenValidationView.swift; sourceTree = "<group>"; };
 		65E8A2852E44B0300065037B /* VolumeButtonHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeButtonHandler.swift; sourceTree = "<group>"; };
 		65E8A2852E44B0300065037B /* VolumeButtonHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeButtonHandler.swift; sourceTree = "<group>"; };
 		A7D55B42A22051DAD69E89D0 /* Pods_LoopFollow.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LoopFollow.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		A7D55B42A22051DAD69E89D0 /* Pods_LoopFollow.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LoopFollow.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -447,14 +470,10 @@
 		DD16AF0C2C98485400FB655A /* SecureStorageValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureStorageValue.swift; sourceTree = "<group>"; };
 		DD16AF0C2C98485400FB655A /* SecureStorageValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureStorageValue.swift; sourceTree = "<group>"; };
 		DD16AF0E2C99592F00FB655A /* HKQuantityInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKQuantityInputView.swift; sourceTree = "<group>"; };
 		DD16AF0E2C99592F00FB655A /* HKQuantityInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKQuantityInputView.swift; sourceTree = "<group>"; };
 		DD16AF102C997B4600FB655A /* LoadingButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingButtonView.swift; sourceTree = "<group>"; };
 		DD16AF102C997B4600FB655A /* LoadingButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingButtonView.swift; sourceTree = "<group>"; };
-		DD1A97132D4294A4000DDC11 /* AdvancedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsView.swift; sourceTree = "<group>"; };
-		DD1A97152D4294B2000DDC11 /* AdvancedSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsViewModel.swift; sourceTree = "<group>"; };
 		DD1D52B82E1EB5DC00432050 /* TabPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabPosition.swift; sourceTree = "<group>"; };
 		DD1D52B82E1EB5DC00432050 /* TabPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabPosition.swift; sourceTree = "<group>"; };
 		DD1D52BA2E1EB60B00432050 /* MoreMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreMenuViewController.swift; sourceTree = "<group>"; };
 		DD1D52BA2E1EB60B00432050 /* MoreMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreMenuViewController.swift; sourceTree = "<group>"; };
 		DD2C2E4E2D3B8AEC006413A5 /* NightscoutSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutSettingsView.swift; sourceTree = "<group>"; };
 		DD2C2E4E2D3B8AEC006413A5 /* NightscoutSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutSettingsView.swift; sourceTree = "<group>"; };
 		DD2C2E502D3B8B0B006413A5 /* NightscoutSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutSettingsViewModel.swift; sourceTree = "<group>"; };
 		DD2C2E502D3B8B0B006413A5 /* NightscoutSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutSettingsViewModel.swift; sourceTree = "<group>"; };
-		DD2C2E532D3C37D7006413A5 /* DexcomSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomSettingsViewModel.swift; sourceTree = "<group>"; };
-		DD2C2E552D3C3913006413A5 /* DexcomSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomSettingsView.swift; sourceTree = "<group>"; };
 		DD485F132E454B2600CE8CBF /* SecureMessenger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureMessenger.swift; sourceTree = "<group>"; };
 		DD485F132E454B2600CE8CBF /* SecureMessenger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureMessenger.swift; sourceTree = "<group>"; };
 		DD4878022C7B297E0048F05C /* StorageValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageValue.swift; sourceTree = "<group>"; };
 		DD4878022C7B297E0048F05C /* StorageValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageValue.swift; sourceTree = "<group>"; };
 		DD4878042C7B2C970048F05C /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
 		DD4878042C7B2C970048F05C /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
@@ -486,8 +505,6 @@
 		DD4AFB602DB68BBC00BB593F /* AlarmListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmListView.swift; sourceTree = "<group>"; };
 		DD4AFB602DB68BBC00BB593F /* AlarmListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmListView.swift; sourceTree = "<group>"; };
 		DD4AFB662DB68C5500BB593F /* UUID+Identifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UUID+Identifiable.swift"; sourceTree = "<group>"; };
 		DD4AFB662DB68C5500BB593F /* UUID+Identifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UUID+Identifiable.swift"; sourceTree = "<group>"; };
 		DD4AFB6A2DB6BF2A00BB593F /* Binding+Optional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+Optional.swift"; sourceTree = "<group>"; };
 		DD4AFB6A2DB6BF2A00BB593F /* Binding+Optional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+Optional.swift"; sourceTree = "<group>"; };
-		DD50C74F2D0828800057AE6F /* ContactSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSettingsViewModel.swift; sourceTree = "<group>"; };
-		DD50C7522D0828D10057AE6F /* ContactSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSettingsView.swift; sourceTree = "<group>"; };
 		DD50C7542D0862770057AE6F /* ContactImageUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageUpdater.swift; sourceTree = "<group>"; };
 		DD50C7542D0862770057AE6F /* ContactImageUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageUpdater.swift; sourceTree = "<group>"; };
 		DD5334202C60EBEE00062F9D /* InsulinCartridgeChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsulinCartridgeChange.swift; sourceTree = "<group>"; };
 		DD5334202C60EBEE00062F9D /* InsulinCartridgeChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsulinCartridgeChange.swift; sourceTree = "<group>"; };
 		DD5334222C60ED3600062F9D /* IAge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IAge.swift; sourceTree = "<group>"; };
 		DD5334222C60ED3600062F9D /* IAge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IAge.swift; sourceTree = "<group>"; };
@@ -532,15 +549,10 @@
 		DD7F4C222DD7A62200D449E9 /* AlarmType+SortDirection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlarmType+SortDirection.swift"; sourceTree = "<group>"; };
 		DD7F4C222DD7A62200D449E9 /* AlarmType+SortDirection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlarmType+SortDirection.swift"; sourceTree = "<group>"; };
 		DD7F4C242DD7B20700D449E9 /* AlarmType+timeUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlarmType+timeUnit.swift"; sourceTree = "<group>"; };
 		DD7F4C242DD7B20700D449E9 /* AlarmType+timeUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlarmType+timeUnit.swift"; sourceTree = "<group>"; };
 		DD7FFAFC2A72953000C3A304 /* EKEventStore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EKEventStore+Extensions.swift"; sourceTree = "<group>"; };
 		DD7FFAFC2A72953000C3A304 /* EKEventStore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EKEventStore+Extensions.swift"; sourceTree = "<group>"; };
-		DD8060DA2E2ACE5900626B91 /* TabCustomizationModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabCustomizationModal.swift; sourceTree = "<group>"; };
-		DD8316172DE3633D004467AA /* GeneralSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsView.swift; sourceTree = "<group>"; };
 		DD8316432DE47CA9004467AA /* BGPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGPicker.swift; sourceTree = "<group>"; };
 		DD8316432DE47CA9004467AA /* BGPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGPicker.swift; sourceTree = "<group>"; };
-		DD8316452DE49B09004467AA /* GraphSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphSettingsView.swift; sourceTree = "<group>"; };
 		DD8316472DE49EE5004467AA /* Storage+Migrate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Migrate.swift"; sourceTree = "<group>"; };
 		DD8316472DE49EE5004467AA /* Storage+Migrate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Migrate.swift"; sourceTree = "<group>"; };
 		DD8316492DE4C504004467AA /* SettingsStepperRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsStepperRow.swift; sourceTree = "<group>"; };
 		DD8316492DE4C504004467AA /* SettingsStepperRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsStepperRow.swift; sourceTree = "<group>"; };
 		DD83164B2DE4DB3A004467AA /* BinaryFloatingPoint+localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BinaryFloatingPoint+localized.swift"; sourceTree = "<group>"; };
 		DD83164B2DE4DB3A004467AA /* BinaryFloatingPoint+localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BinaryFloatingPoint+localized.swift"; sourceTree = "<group>"; };
-		DD83164D2DE4E093004467AA /* CalendarSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarSettingsView.swift; sourceTree = "<group>"; };
-		DD83164F2DE4E635004467AA /* SettingsMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsMenuView.swift; sourceTree = "<group>"; };
 		DD85E9942D739CED001C8BB7 /* OmnipodDashHeartbeatBluetoothTransmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmnipodDashHeartbeatBluetoothTransmitter.swift; sourceTree = "<group>"; };
 		DD85E9942D739CED001C8BB7 /* OmnipodDashHeartbeatBluetoothTransmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmnipodDashHeartbeatBluetoothTransmitter.swift; sourceTree = "<group>"; };
 		DD91E4DC2BDEC3F8002D9E97 /* GlucoseConversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseConversion.swift; sourceTree = "<group>"; };
 		DD91E4DC2BDEC3F8002D9E97 /* GlucoseConversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseConversion.swift; sourceTree = "<group>"; };
 		DD98F54324BCEFEE0007425A /* ShareClientExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareClientExtension.swift; sourceTree = "<group>"; };
 		DD98F54324BCEFEE0007425A /* ShareClientExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareClientExtension.swift; sourceTree = "<group>"; };
@@ -817,6 +829,37 @@
 /* End PBXFrameworksBuildPhase section */
 /* End PBXFrameworksBuildPhase section */
 
 
 /* Begin PBXGroup section */
 /* Begin PBXGroup section */
+		6589CC552E9E7D1600BB18FE /* ImportExport */ = {
+			isa = PBXGroup;
+			children = (
+				6589CC742E9EAFB700BB18FE /* SettingsMigrationManager.swift */,
+				6589CC702E9E814F00BB18FE /* AlarmSelectionView.swift */,
+				6589CC522E9E7D1600BB18FE /* ExportableSettings.swift */,
+				6589CC532E9E7D1600BB18FE /* ImportExportSettingsView.swift */,
+				6589CC542E9E7D1600BB18FE /* ImportExportSettingsViewModel.swift */,
+			);
+			path = ImportExport;
+			sourceTree = "<group>";
+		};
+		6589CC612E9E7D1600BB18FE /* Settings */ = {
+			isa = PBXGroup;
+			children = (
+				6589CC552E9E7D1600BB18FE /* ImportExport */,
+				6589CC562E9E7D1600BB18FE /* AdvancedSettingsView.swift */,
+				6589CC572E9E7D1600BB18FE /* AdvancedSettingsViewModel.swift */,
+				6589CC582E9E7D1600BB18FE /* CalendarSettingsView.swift */,
+				6589CC592E9E7D1600BB18FE /* ContactSettingsView.swift */,
+				6589CC5A2E9E7D1600BB18FE /* ContactSettingsViewModel.swift */,
+				6589CC5B2E9E7D1600BB18FE /* DexcomSettingsView.swift */,
+				6589CC5C2E9E7D1600BB18FE /* DexcomSettingsViewModel.swift */,
+				6589CC5D2E9E7D1600BB18FE /* GeneralSettingsView.swift */,
+				6589CC5E2E9E7D1600BB18FE /* GraphSettingsView.swift */,
+				6589CC5F2E9E7D1600BB18FE /* SettingsMenuView.swift */,
+				6589CC602E9E7D1600BB18FE /* TabCustomizationModal.swift */,
+			);
+			path = Settings;
+			sourceTree = "<group>";
+		};
 		6A5880E0B811AF443B05AB02 /* Frameworks */ = {
 		6A5880E0B811AF443B05AB02 /* Frameworks */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
@@ -902,24 +945,6 @@
 			path = InfoTable;
 			path = InfoTable;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
-		DD1A97122D429495000DDC11 /* Settings */ = {
-			isa = PBXGroup;
-			children = (
-				DD8060DA2E2ACE5900626B91 /* TabCustomizationModal.swift */,
-				DD83164F2DE4E635004467AA /* SettingsMenuView.swift */,
-				DD2C2E552D3C3913006413A5 /* DexcomSettingsView.swift */,
-				DD2C2E532D3C37D7006413A5 /* DexcomSettingsViewModel.swift */,
-				DD83164D2DE4E093004467AA /* CalendarSettingsView.swift */,
-				DD8316452DE49B09004467AA /* GraphSettingsView.swift */,
-				DD1A97152D4294B2000DDC11 /* AdvancedSettingsViewModel.swift */,
-				DD1A97132D4294A4000DDC11 /* AdvancedSettingsView.swift */,
-				DD8316172DE3633D004467AA /* GeneralSettingsView.swift */,
-				DD50C74F2D0828800057AE6F /* ContactSettingsViewModel.swift */,
-				DD50C7522D0828D10057AE6F /* ContactSettingsView.swift */,
-			);
-			path = Settings;
-			sourceTree = "<group>";
-		};
 		DD2C2E4D2D3B8ACF006413A5 /* Nightscout */ = {
 		DD2C2E4D2D3B8ACF006413A5 /* Nightscout */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
@@ -1442,6 +1467,7 @@
 		FC8DEEE32485D1680075863F /* LoopFollow */ = {
 		FC8DEEE32485D1680075863F /* LoopFollow */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				6589CC612E9E7D1600BB18FE /* Settings */,
 				DDCF9A7E2D85FCE6004DF4DD /* Alarm */,
 				DDCF9A7E2D85FCE6004DF4DD /* Alarm */,
 				FC16A97624995FEE003D6245 /* Application */,
 				FC16A97624995FEE003D6245 /* Application */,
 				DDFF3D792D140F1800BF9D9E /* BackgroundRefresh */,
 				DDFF3D792D140F1800BF9D9E /* BackgroundRefresh */,
@@ -1458,7 +1484,6 @@
 				DD2C2E4D2D3B8ACF006413A5 /* Nightscout */,
 				DD2C2E4D2D3B8ACF006413A5 /* Nightscout */,
 				DD0C0C6E2C4AFFB800DBADDF /* Remote */,
 				DD0C0C6E2C4AFFB800DBADDF /* Remote */,
 				FC7CE59A248D334B001F83B8 /* Resources */,
 				FC7CE59A248D334B001F83B8 /* Resources */,
-				DD1A97122D429495000DDC11 /* Settings */,
 				DDC7E5142DBCE1B900EB1127 /* Snoozer */,
 				DDC7E5142DBCE1B900EB1127 /* Snoozer */,
 				DDC7E5CD2DC6637800EB1127 /* Storage */,
 				DDC7E5CD2DC6637800EB1127 /* Storage */,
 				DDEF503D2D32753A00999A5D /* Task */,
 				DDEF503D2D32753A00999A5D /* Task */,
@@ -1888,9 +1913,7 @@
 				DD5334292C6166A500062F9D /* InfoDisplaySettingsView.swift in Sources */,
 				DD5334292C6166A500062F9D /* InfoDisplaySettingsView.swift in Sources */,
 				DD48781E2C7DAF2F0048F05C /* PushNotificationManager.swift in Sources */,
 				DD48781E2C7DAF2F0048F05C /* PushNotificationManager.swift in Sources */,
 				DD0B9D582DE1F3B20090C337 /* AlarmType+canAcknowledge.swift in Sources */,
 				DD0B9D582DE1F3B20090C337 /* AlarmType+canAcknowledge.swift in Sources */,
-				DD2C2E562D3C3917006413A5 /* DexcomSettingsView.swift in Sources */,
 				DD7F4BC52DD3CE0700D449E9 /* AlarmBGLimitSection.swift in Sources */,
 				DD7F4BC52DD3CE0700D449E9 /* AlarmBGLimitSection.swift in Sources */,
-				DD8060DB2E2ACE5900626B91 /* TabCustomizationModal.swift in Sources */,
 				DD7F4B9F2DD1F92700D449E9 /* AlarmActiveSection.swift in Sources */,
 				DD7F4B9F2DD1F92700D449E9 /* AlarmActiveSection.swift in Sources */,
 				DD4AFB672DB68C5500BB593F /* UUID+Identifiable.swift in Sources */,
 				DD4AFB672DB68C5500BB593F /* UUID+Identifiable.swift in Sources */,
 				DD9ED0CA2D355257000D2A63 /* LogView.swift in Sources */,
 				DD9ED0CA2D355257000D2A63 /* LogView.swift in Sources */,
@@ -1948,6 +1971,7 @@
 				DDCC3A5A2DDC988F006F1C10 /* CarbSample.swift in Sources */,
 				DDCC3A5A2DDC988F006F1C10 /* CarbSample.swift in Sources */,
 				DD0650F12DCE9A9E004D3B41 /* MissedReadingCondition.swift in Sources */,
 				DD0650F12DCE9A9E004D3B41 /* MissedReadingCondition.swift in Sources */,
 				DDC6CA4B2DD8E4960060EE25 /* PumpVolumeAlarmEditor.swift in Sources */,
 				DDC6CA4B2DD8E4960060EE25 /* PumpVolumeAlarmEditor.swift in Sources */,
+				6589CC752E9EAFB700BB18FE /* SettingsMigrationManager.swift in Sources */,
 				DD16AF0F2C99592F00FB655A /* HKQuantityInputView.swift in Sources */,
 				DD16AF0F2C99592F00FB655A /* HKQuantityInputView.swift in Sources */,
 				DDFF3D7F2D1414A200BF9D9E /* BLEDevice.swift in Sources */,
 				DDFF3D7F2D1414A200BF9D9E /* BLEDevice.swift in Sources */,
 				DD9ACA042D32821400415D8A /* DeviceStatusTask.swift in Sources */,
 				DD9ACA042D32821400415D8A /* DeviceStatusTask.swift in Sources */,
@@ -1964,7 +1988,6 @@
 				DD7F4C1D2DD650D500D449E9 /* COBAlarmEditor.swift in Sources */,
 				DD7F4C1D2DD650D500D449E9 /* COBAlarmEditor.swift in Sources */,
 				DDE75D272DE5E539007C1FC1 /* ActionRow.swift in Sources */,
 				DDE75D272DE5E539007C1FC1 /* ActionRow.swift in Sources */,
 				DD0C0C622C4175FD00DBADDF /* NSProfile.swift in Sources */,
 				DD0C0C622C4175FD00DBADDF /* NSProfile.swift in Sources */,
-				DD8316182DE3633D004467AA /* GeneralSettingsView.swift in Sources */,
 				DD58171E2D299FCA0041FB98 /* BluetoothDeviceDelegate.swift in Sources */,
 				DD58171E2D299FCA0041FB98 /* BluetoothDeviceDelegate.swift in Sources */,
 				DDE69ED22C7256260013EAEC /* RemoteType.swift in Sources */,
 				DDE69ED22C7256260013EAEC /* RemoteType.swift in Sources */,
 				DDD10F032C518A6500D76A8E /* TreatmentResponse.swift in Sources */,
 				DDD10F032C518A6500D76A8E /* TreatmentResponse.swift in Sources */,
@@ -2017,6 +2040,20 @@
 				FC8589BF252B54F500C8FC73 /* Mobileprovision.swift in Sources */,
 				FC8589BF252B54F500C8FC73 /* Mobileprovision.swift in Sources */,
 				DD4878052C7B2C970048F05C /* Storage.swift in Sources */,
 				DD4878052C7B2C970048F05C /* Storage.swift in Sources */,
 				DD493AE12ACF22FE009A6922 /* Profile.swift in Sources */,
 				DD493AE12ACF22FE009A6922 /* Profile.swift in Sources */,
+				6589CC622E9E7D1600BB18FE /* ImportExportSettingsView.swift in Sources */,
+				6589CC632E9E7D1600BB18FE /* GeneralSettingsView.swift in Sources */,
+				6589CC642E9E7D1600BB18FE /* ContactSettingsView.swift in Sources */,
+				6589CC652E9E7D1600BB18FE /* DexcomSettingsViewModel.swift in Sources */,
+				6589CC662E9E7D1600BB18FE /* AdvancedSettingsView.swift in Sources */,
+				6589CC672E9E7D1600BB18FE /* ImportExportSettingsViewModel.swift in Sources */,
+				6589CC682E9E7D1600BB18FE /* ExportableSettings.swift in Sources */,
+				6589CC692E9E7D1600BB18FE /* ContactSettingsViewModel.swift in Sources */,
+				6589CC6A2E9E7D1600BB18FE /* DexcomSettingsView.swift in Sources */,
+				6589CC6B2E9E7D1600BB18FE /* TabCustomizationModal.swift in Sources */,
+				6589CC6C2E9E7D1600BB18FE /* GraphSettingsView.swift in Sources */,
+				6589CC6D2E9E7D1600BB18FE /* CalendarSettingsView.swift in Sources */,
+				6589CC6E2E9E7D1600BB18FE /* SettingsMenuView.swift in Sources */,
+				6589CC6F2E9E7D1600BB18FE /* AdvancedSettingsViewModel.swift in Sources */,
 				DD493ADF2ACF22BB009A6922 /* SAge.swift in Sources */,
 				DD493ADF2ACF22BB009A6922 /* SAge.swift in Sources */,
 				DDC6CA3F2DD7C6340060EE25 /* TemporaryAlarmEditor.swift in Sources */,
 				DDC6CA3F2DD7C6340060EE25 /* TemporaryAlarmEditor.swift in Sources */,
 				DDF699992C5AA3060058A8D9 /* TempTargetPresetManager.swift in Sources */,
 				DDF699992C5AA3060058A8D9 /* TempTargetPresetManager.swift in Sources */,
@@ -2029,7 +2066,6 @@
 				DD13BC752C3FD6210062313B /* InfoType.swift in Sources */,
 				DD13BC752C3FD6210062313B /* InfoType.swift in Sources */,
 				DDCC3A4D2DDBB77C006F1C10 /* BatteryAlarmEditor.swift in Sources */,
 				DDCC3A4D2DDBB77C006F1C10 /* BatteryAlarmEditor.swift in Sources */,
 				DDC6CA492DD8E47A0060EE25 /* PumpVolumeCondition.swift in Sources */,
 				DDC6CA492DD8E47A0060EE25 /* PumpVolumeCondition.swift in Sources */,
-				DD1A97142D4294A5000DDC11 /* AdvancedSettingsView.swift in Sources */,
 				DD9ACA0A2D33095600415D8A /* MinAgoTask.swift in Sources */,
 				DD9ACA0A2D33095600415D8A /* MinAgoTask.swift in Sources */,
 				DD9ED0CC2D35526E000D2A63 /* SearchBar.swift in Sources */,
 				DD9ED0CC2D35526E000D2A63 /* SearchBar.swift in Sources */,
 				DDD10EFF2C510C3C00D76A8E /* ObservableUserDefaultsValue.swift in Sources */,
 				DDD10EFF2C510C3C00D76A8E /* ObservableUserDefaultsValue.swift in Sources */,
@@ -2037,8 +2073,8 @@
 				DD58171A2D299EF80041FB98 /* DexcomHeartbeatBluetoothDevice.swift in Sources */,
 				DD58171A2D299EF80041FB98 /* DexcomHeartbeatBluetoothDevice.swift in Sources */,
 				DD7F4C0B2DD51C5500D449E9 /* OverrideEndCondition.swift in Sources */,
 				DD7F4C0B2DD51C5500D449E9 /* OverrideEndCondition.swift in Sources */,
 				DDFF3D872D14280500BF9D9E /* BackgroundRefreshSettingsViewModel.swift in Sources */,
 				DDFF3D872D14280500BF9D9E /* BackgroundRefreshSettingsViewModel.swift in Sources */,
+				6589CC712E9E814F00BB18FE /* AlarmSelectionView.swift in Sources */,
 				DD7F4C192DD63FD500D449E9 /* RecBolusAlarmEditor.swift in Sources */,
 				DD7F4C192DD63FD500D449E9 /* RecBolusAlarmEditor.swift in Sources */,
-				DD50C7502D0828800057AE6F /* ContactSettingsViewModel.swift in Sources */,
 				DDAD162F2D2EF9830084BE10 /* RileyLinkHeartbeatBluetoothDevice.swift in Sources */,
 				DDAD162F2D2EF9830084BE10 /* RileyLinkHeartbeatBluetoothDevice.swift in Sources */,
 				FC97881C2485969B00A7906C /* MainViewController.swift in Sources */,
 				FC97881C2485969B00A7906C /* MainViewController.swift in Sources */,
 				DD6A935E2BFA6FA2003FFB8E /* DeviceStatusOpenAPS.swift in Sources */,
 				DD6A935E2BFA6FA2003FFB8E /* DeviceStatusOpenAPS.swift in Sources */,
@@ -2056,7 +2092,6 @@
 				DDFF3D852D14279B00BF9D9E /* BackgroundRefreshSettingsView.swift in Sources */,
 				DDFF3D852D14279B00BF9D9E /* BackgroundRefreshSettingsView.swift in Sources */,
 				DDCF9A882D85FD33004DF4DD /* AlarmData.swift in Sources */,
 				DDCF9A882D85FD33004DF4DD /* AlarmData.swift in Sources */,
 				DD608A0C2C27415C00F91132 /* BackgroundAlertManager.swift in Sources */,
 				DD608A0C2C27415C00F91132 /* BackgroundAlertManager.swift in Sources */,
-				DD50C7532D0828D10057AE6F /* ContactSettingsView.swift in Sources */,
 				DD4878082C7B30BF0048F05C /* RemoteSettingsView.swift in Sources */,
 				DD4878082C7B30BF0048F05C /* RemoteSettingsView.swift in Sources */,
 				DDE75D2B2DE5E613007C1FC1 /* NavigationRow.swift in Sources */,
 				DDE75D2B2DE5E613007C1FC1 /* NavigationRow.swift in Sources */,
 				DD4AFB3B2DB55CB600BB593F /* TimeOfDay.swift in Sources */,
 				DD4AFB3B2DB55CB600BB593F /* TimeOfDay.swift in Sources */,
@@ -2088,7 +2123,6 @@
 				DDC6CA432DD8CED20060EE25 /* SensorAgeCondition.swift in Sources */,
 				DDC6CA432DD8CED20060EE25 /* SensorAgeCondition.swift in Sources */,
 				DD7F4C212DD66BB200D449E9 /* FastRiseAlarmEditor.swift in Sources */,
 				DD7F4C212DD66BB200D449E9 /* FastRiseAlarmEditor.swift in Sources */,
 				FC97881A2485969B00A7906C /* SceneDelegate.swift in Sources */,
 				FC97881A2485969B00A7906C /* SceneDelegate.swift in Sources */,
-				DD83164E2DE4E093004467AA /* CalendarSettingsView.swift in Sources */,
 				DD0C0C662C46E54C00DBADDF /* InfoDataSeparator.swift in Sources */,
 				DD0C0C662C46E54C00DBADDF /* InfoDataSeparator.swift in Sources */,
 				DD58171C2D299F940041FB98 /* BluetoothDevice.swift in Sources */,
 				DD58171C2D299F940041FB98 /* BluetoothDevice.swift in Sources */,
 				DD7E198A2ACDA62600DBD158 /* SensorStart.swift in Sources */,
 				DD7E198A2ACDA62600DBD158 /* SensorStart.swift in Sources */,
@@ -2102,9 +2136,7 @@
 				FC1BDD2D24A23204001B652C /* MainViewController+updateStats.swift in Sources */,
 				FC1BDD2D24A23204001B652C /* MainViewController+updateStats.swift in Sources */,
 				DD4878102C7B74BF0048F05C /* TrioRemoteControlView.swift in Sources */,
 				DD4878102C7B74BF0048F05C /* TrioRemoteControlView.swift in Sources */,
 				DD7F4C152DD51FEB00D449E9 /* TempTargetEndAlarmEditor.swift in Sources */,
 				DD7F4C152DD51FEB00D449E9 /* TempTargetEndAlarmEditor.swift in Sources */,
-				DD2C2E542D3C37DC006413A5 /* DexcomSettingsViewModel.swift in Sources */,
 				FCE537BC249A4D7D00F80BF8 /* carbBolusArrays.swift in Sources */,
 				FCE537BC249A4D7D00F80BF8 /* carbBolusArrays.swift in Sources */,
-				DD8316462DE49B09004467AA /* GraphSettingsView.swift in Sources */,
 				DD5334232C60ED3600062F9D /* IAge.swift in Sources */,
 				DD5334232C60ED3600062F9D /* IAge.swift in Sources */,
 				FCD2A27D24C9D044009F7B7B /* Globals.swift in Sources */,
 				FCD2A27D24C9D044009F7B7B /* Globals.swift in Sources */,
 				DD0C0C642C45A59400DBADDF /* HKUnit+Extensions.swift in Sources */,
 				DD0C0C642C45A59400DBADDF /* HKUnit+Extensions.swift in Sources */,
@@ -2120,11 +2152,9 @@
 				DD7F4C132DD51FD500D449E9 /* TempTargetEndCondition.swift in Sources */,
 				DD7F4C132DD51FD500D449E9 /* TempTargetEndCondition.swift in Sources */,
 				DDD10F012C510C6B00D76A8E /* ObservableUserDefaults.swift in Sources */,
 				DDD10F012C510C6B00D76A8E /* ObservableUserDefaults.swift in Sources */,
 				DD16AF0D2C98485400FB655A /* SecureStorageValue.swift in Sources */,
 				DD16AF0D2C98485400FB655A /* SecureStorageValue.swift in Sources */,
-				DD8316502DE4E635004467AA /* SettingsMenuView.swift in Sources */,
 				DDC7E5CF2DC77C2000EB1127 /* SnoozerViewModel.swift in Sources */,
 				DDC7E5CF2DC77C2000EB1127 /* SnoozerViewModel.swift in Sources */,
 				DD2C2E4F2D3B8AF1006413A5 /* NightscoutSettingsView.swift in Sources */,
 				DD2C2E4F2D3B8AF1006413A5 /* NightscoutSettingsView.swift in Sources */,
 				DD8316482DE49EE5004467AA /* Storage+Migrate.swift in Sources */,
 				DD8316482DE49EE5004467AA /* Storage+Migrate.swift in Sources */,
-				DD1A97162D4294B3000DDC11 /* AdvancedSettingsViewModel.swift in Sources */,
 				FCA2DDE62501095000254A8C /* Timers.swift in Sources */,
 				FCA2DDE62501095000254A8C /* Timers.swift in Sources */,
 				DD2C2E512D3B8B0C006413A5 /* NightscoutSettingsViewModel.swift in Sources */,
 				DD2C2E512D3B8B0C006413A5 /* NightscoutSettingsViewModel.swift in Sources */,
 				DD9ED0C82D355244000D2A63 /* LogViewModel.swift in Sources */,
 				DD9ED0C82D355244000D2A63 /* LogViewModel.swift in Sources */,

+ 3 - 0
LoopFollow/Controllers/Nightscout/BGData.swift

@@ -261,6 +261,9 @@ extension MainViewController {
                 Observable.shared.deltaText.value = "+" + Localizer.toDisplayUnits(String(deltaBG))
                 Observable.shared.deltaText.value = "+" + Localizer.toDisplayUnits(String(deltaBG))
             }
             }
 
 
+            // Mark BG data as loaded for initial loading state
+            self.markDataLoaded("bg")
+
             // Update contact
             // Update contact
             if Storage.shared.contactEnabled.value {
             if Storage.shared.contactEnabled.value {
                 self.contactImageUpdater
                 self.contactImageUpdater

+ 4 - 0
LoopFollow/Controllers/Nightscout/DeviceStatus.swift

@@ -224,6 +224,10 @@ extension MainViewController {
         }
         }
 
 
         evaluateNotLooping()
         evaluateNotLooping()
+
+        // Mark device status as loaded for initial loading state
+        markDataLoaded("deviceStatus")
+
         LogManager.shared.log(category: .deviceStatus, message: "Update Device Status done", isDebug: true)
         LogManager.shared.log(category: .deviceStatus, message: "Update Device Status done", isDebug: true)
     }
     }
 }
 }

+ 3 - 0
LoopFollow/Controllers/Nightscout/Profile.swift

@@ -24,6 +24,9 @@ extension MainViewController {
         profileManager.loadProfile(from: profileData)
         profileManager.loadProfile(from: profileData)
         infoManager.updateInfoData(type: .profile, value: profileData.defaultProfile)
         infoManager.updateInfoData(type: .profile, value: profileData.defaultProfile)
 
 
+        // Mark profile data as loaded for initial loading state
+        markDataLoaded("profile")
+
         basalProfile.removeAll()
         basalProfile.removeAll()
         for basalEntry in store.basal {
         for basalEntry in store.basal {
             let entry = basalProfileStruct(value: basalEntry.value, time: basalEntry.time, timeAsSeconds: basalEntry.timeAsSeconds)
             let entry = basalProfileStruct(value: basalEntry.value, time: basalEntry.time, timeAsSeconds: basalEntry.timeAsSeconds)

+ 30 - 0
LoopFollow/Helpers/AppConstants.swift

@@ -6,4 +6,34 @@ import Foundation
 // Class that contains general constants used in different classes
 // Class that contains general constants used in different classes
 class AppConstants {
 class AppConstants {
     static let APP_GROUP_ID = "group.com.$(unique_id).LoopFollow"
     static let APP_GROUP_ID = "group.com.$(unique_id).LoopFollow"
+
+    /// Extracts the app suffix from the bundle identifier
+    /// Bundle identifier format: com.$(unique_id).LoopFollow$(app_suffix)
+    /// Returns the suffix part (e.g., "2" for "com.example.LoopFollow2")
+    static var appSuffix: String {
+        guard let bundleId = Bundle.main.bundleIdentifier else {
+            return ""
+        }
+
+        // Extract suffix from bundle identifier
+        // Pattern: com.$(unique_id).LoopFollow$(app_suffix)
+        let pattern = "LoopFollow(.+)$"
+        if let regex = try? NSRegularExpression(pattern: pattern) {
+            let range = NSRange(location: 0, length: bundleId.utf16.count)
+            if let match = regex.firstMatch(in: bundleId, options: [], range: range) {
+                let suffixRange = match.range(at: 1)
+                if let swiftRange = Range(suffixRange, in: bundleId) {
+                    let suffix = String(bundleId[swiftRange])
+                    return suffix.isEmpty ? "" : "_\(suffix)"
+                }
+            }
+        }
+
+        return ""
+    }
+
+    /// Returns a unique identifier for this app instance based on the app suffix
+    static var appInstanceId: String {
+        return "LoopFollow\(appSuffix)"
+    }
 }
 }

+ 0 - 6
LoopFollow/Helpers/Views/QRCodeDisplayView.swift

@@ -42,12 +42,6 @@ struct QRCodeDisplayView: View {
                             .scaleEffect(1.5)
                             .scaleEffect(1.5)
                     )
                     )
             }
             }
-
-            Text("Scan this QR code with another LoopFollow app to import remote command settings")
-                .font(.caption)
-                .foregroundColor(.secondary)
-                .multilineTextAlignment(.center)
-                .padding(.horizontal, 20)
         }
         }
         .onAppear {
         .onAppear {
             generateQRCode()
             generateQRCode()

+ 73 - 36
LoopFollow/Helpers/Views/SimpleQRCodeScannerView.swift

@@ -3,46 +3,62 @@
 
 
 import AVFoundation
 import AVFoundation
 import SwiftUI
 import SwiftUI
+import UIKit
 
 
 struct SimpleQRCodeScannerView: UIViewControllerRepresentable {
 struct SimpleQRCodeScannerView: UIViewControllerRepresentable {
     @Environment(\.presentationMode) var presentationMode
     @Environment(\.presentationMode) var presentationMode
     var completion: (Result<String, Error>) -> Void
     var completion: (Result<String, Error>) -> Void
 
 
-    // MARK: - Coordinator
+    func makeUIViewController(context _: Context) -> UINavigationController {
+        let scannerVC = SimpleQRCodeScannerViewController { result in
+            completion(result)
+        }
 
 
-    class Coordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate {
-        var parent: SimpleQRCodeScannerView
-        var session: AVCaptureSession?
+        let navController = UINavigationController(rootViewController: scannerVC)
 
 
-        init(parent: SimpleQRCodeScannerView) {
-            self.parent = parent
+        // Apply dark mode if needed
+        if Storage.shared.forceDarkMode.value {
+            scannerVC.overrideUserInterfaceStyle = .dark
+            navController.overrideUserInterfaceStyle = .dark
         }
         }
 
 
-        func metadataOutput(_: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from _: AVCaptureConnection) {
-            if let session, session.isRunning {
-                if let metadataObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
-                   metadataObject.type == .qr,
-                   let stringValue = metadataObject.stringValue
-                {
-                    DispatchQueue.global(qos: .userInitiated).async {
-                        session.stopRunning()
-                    }
-                    parent.completion(.success(stringValue))
-                }
-            }
-        }
+        return navController
+    }
+
+    func updateUIViewController(_: UINavigationController, context _: Context) {}
+}
+
+class SimpleQRCodeScannerViewController: UIViewController {
+    private var session: AVCaptureSession?
+    private var completion: (Result<String, Error>) -> Void
+
+    init(completion: @escaping (Result<String, Error>) -> Void) {
+        self.completion = completion
+        super.init(nibName: nil, bundle: nil)
     }
     }
 
 
-    // MARK: - UIViewControllerRepresentable Methods
+    @available(*, unavailable)
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        // Add cancel button
+        navigationItem.rightBarButtonItem = UIBarButtonItem(
+            barButtonSystemItem: .cancel,
+            target: self,
+            action: #selector(cancelTapped)
+        )
+        navigationItem.title = "Scan QR Code"
 
 
-    func makeCoordinator() -> Coordinator {
-        Coordinator(parent: self)
+        setupCamera()
     }
     }
 
 
-    func makeUIViewController(context: Context) -> UIViewController {
-        let controller = UIViewController()
+    private func setupCamera() {
         let session = AVCaptureSession()
         let session = AVCaptureSession()
-        context.coordinator.session = session // Assign session to coordinator
+        self.session = session
 
 
         guard let videoCaptureDevice = AVCaptureDevice.default(for: .video),
         guard let videoCaptureDevice = AVCaptureDevice.default(for: .video),
               let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice),
               let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice),
@@ -50,7 +66,7 @@ struct SimpleQRCodeScannerView: UIViewControllerRepresentable {
         else {
         else {
             let error = NSError(domain: "QRCodeScannerError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to set up camera input."])
             let error = NSError(domain: "QRCodeScannerError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to set up camera input."])
             completion(.failure(error))
             completion(.failure(error))
-            return controller
+            return
         }
         }
 
 
         session.addInput(videoInput)
         session.addInput(videoInput)
@@ -58,33 +74,54 @@ struct SimpleQRCodeScannerView: UIViewControllerRepresentable {
         let metadataOutput = AVCaptureMetadataOutput()
         let metadataOutput = AVCaptureMetadataOutput()
         if session.canAddOutput(metadataOutput) {
         if session.canAddOutput(metadataOutput) {
             session.addOutput(metadataOutput)
             session.addOutput(metadataOutput)
-            metadataOutput.setMetadataObjectsDelegate(context.coordinator, queue: DispatchQueue.main)
+            metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
             metadataOutput.metadataObjectTypes = [.qr]
             metadataOutput.metadataObjectTypes = [.qr]
         } else {
         } else {
             let error = NSError(domain: "QRCodeScannerError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to set up metadata output."])
             let error = NSError(domain: "QRCodeScannerError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to set up metadata output."])
             completion(.failure(error))
             completion(.failure(error))
-            return controller
+            return
         }
         }
 
 
         let previewLayer = AVCaptureVideoPreviewLayer(session: session)
         let previewLayer = AVCaptureVideoPreviewLayer(session: session)
-        previewLayer.frame = controller.view.layer.bounds
+        previewLayer.frame = view.layer.bounds
         previewLayer.videoGravity = .resizeAspectFill
         previewLayer.videoGravity = .resizeAspectFill
-        controller.view.layer.addSublayer(previewLayer)
+        view.layer.addSublayer(previewLayer)
 
 
         DispatchQueue.global(qos: .userInitiated).async {
         DispatchQueue.global(qos: .userInitiated).async {
             session.startRunning()
             session.startRunning()
         }
         }
-
-        return controller
     }
     }
 
 
-    func updateUIViewController(_: UIViewController, context _: Context) {}
-
-    func dismantleUIViewController(_: UIViewController, coordinator: Coordinator) {
+    @objc private func cancelTapped() {
         DispatchQueue.global(qos: .userInitiated).async {
         DispatchQueue.global(qos: .userInitiated).async {
-            if let session = coordinator.session, session.isRunning {
+            if let session = self.session, session.isRunning {
                 session.stopRunning()
                 session.stopRunning()
             }
             }
         }
         }
+        let error = NSError(domain: "QRCodeScannerError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Scanning cancelled by user."])
+        completion(.failure(error))
+    }
+
+    override func viewDidLayoutSubviews() {
+        super.viewDidLayoutSubviews()
+        if let previewLayer = view.layer.sublayers?.first as? AVCaptureVideoPreviewLayer {
+            previewLayer.frame = view.layer.bounds
+        }
+    }
+}
+
+extension SimpleQRCodeScannerViewController: AVCaptureMetadataOutputObjectsDelegate {
+    func metadataOutput(_: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from _: AVCaptureConnection) {
+        if let session, session.isRunning {
+            if let metadataObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
+               metadataObject.type == .qr,
+               let stringValue = metadataObject.stringValue
+            {
+                DispatchQueue.global(qos: .userInitiated).async {
+                    session.stopRunning()
+                }
+                completion(.success(stringValue))
+            }
+        }
     }
     }
 }
 }

+ 13 - 0
LoopFollow/Loop Follow.entitlements

@@ -6,6 +6,19 @@
 	<string>development</string>
 	<string>development</string>
 	<key>com.apple.developer.aps-environment</key>
 	<key>com.apple.developer.aps-environment</key>
 	<string>development</string>
 	<string>development</string>
+	<key>com.apple.developer.icloud-container-identifiers</key>
+	<array>
+		<string>iCloud.$(CFBundleIdentifier)</string>
+	</array>
+	<key>com.apple.developer.icloud-services</key>
+	<array>
+		<string>CloudDocuments</string>
+		<string>CloudKit</string>
+	</array>
+	<key>com.apple.developer.ubiquity-container-identifiers</key>
+	<array>
+		<string>iCloud.$(CFBundleIdentifier)</string>
+	</array>
 	<key>com.apple.security.app-sandbox</key>
 	<key>com.apple.security.app-sandbox</key>
 	<true/>
 	<true/>
 	<key>com.apple.security.device.bluetooth</key>
 	<key>com.apple.security.device.bluetooth</key>

+ 13 - 0
LoopFollow/Nightscout/NightscoutSettingsView.swift

@@ -12,6 +12,7 @@ struct NightscoutSettingsView: View {
                 urlSection
                 urlSection
                 tokenSection
                 tokenSection
                 statusSection
                 statusSection
+                importSection
             }
             }
             .onDisappear {
             .onDisappear {
                 viewModel.dismiss()
                 viewModel.dismiss()
@@ -54,4 +55,16 @@ struct NightscoutSettingsView: View {
             Text(viewModel.nightscoutStatus)
             Text(viewModel.nightscoutStatus)
         }
         }
     }
     }
+
+    private var importSection: some View {
+        Section(header: Text("Import Settings")) {
+            NavigationLink(destination: ImportExportSettingsView()) {
+                HStack {
+                    Image(systemName: "square.and.arrow.down")
+                        .foregroundColor(.blue)
+                    Text("Import Settings from QR Code or iCloud")
+                }
+            }
+        }
+    }
 }
 }

+ 1 - 1
LoopFollow/Remote/Settings/RemoteCommandSettings.swift

@@ -145,7 +145,7 @@ struct RemoteCommandSettings: Codable {
     }
     }
 
 
     /// Checks if the settings are valid for the given remote type
     /// Checks if the settings are valid for the given remote type
-    func isValid() -> Bool {
+    func hasValidSettings() -> Bool {
         switch remoteType {
         switch remoteType {
         case .none:
         case .none:
             return true
             return true

+ 13 - 72
LoopFollow/Remote/Settings/RemoteSettingsView.swift

@@ -1,8 +1,10 @@
 // LoopFollow
 // LoopFollow
 // RemoteSettingsView.swift
 // RemoteSettingsView.swift
 
 
+import AVFoundation
 import HealthKit
 import HealthKit
 import SwiftUI
 import SwiftUI
+import UIKit
 
 
 struct RemoteSettingsView: View {
 struct RemoteSettingsView: View {
     @ObservedObject var viewModel: RemoteSettingsViewModel
     @ObservedObject var viewModel: RemoteSettingsViewModel
@@ -19,7 +21,6 @@ struct RemoteSettingsView: View {
 
 
     enum AlertType {
     enum AlertType {
         case validation
         case validation
-        case qrCodeError
         case urlTokenValidation
         case urlTokenValidation
         case urlTokenUpdate
         case urlTokenUpdate
     }
     }
@@ -62,34 +63,21 @@ struct RemoteSettingsView: View {
                     .foregroundColor(.secondary)
                     .foregroundColor(.secondary)
             }
             }
 
 
-            // MARK: - QR Code Sharing Section
+            // MARK: - Import/Export Settings Section
 
 
             Section {
             Section {
-                if viewModel.remoteType == .none {
-                    Button(action: {
-                        viewModel.isShowingQRCodeScanner = true
-                    }) {
-                        HStack {
-                            Image(systemName: "qrcode.viewfinder")
-                            Text("Import Remote Settings from QR Code")
-                        }
-                    }
-                    .buttonStyle(.borderedProminent)
-                    .frame(maxWidth: .infinity)
-                    .padding(.vertical, 10)
-                } else {
-                    Button(action: {
-                        viewModel.isShowingQRCodeDisplay = true
-                    }) {
-                        HStack {
-                            Image(systemName: "qrcode")
-                            Text("Export Remote Settings as QR Code")
-                        }
+                NavigationLink(destination: ImportExportSettingsView()) {
+                    HStack {
+                        Image(systemName: "square.and.arrow.down")
+                            .foregroundColor(.blue)
+                        Text("Import/Export Settings")
+                        Spacer()
+                        Image(systemName: "chevron.right")
+                            .foregroundColor(.secondary)
+                            .font(.caption)
                     }
                     }
-                    .buttonStyle(.borderedProminent)
-                    .frame(maxWidth: .infinity)
-                    .padding(.vertical, 10)
                 }
                 }
+                .buttonStyle(.plain)
             }
             }
 
 
             // MARK: - Meal Section (for TRC only)
             // MARK: - Meal Section (for TRC only)
@@ -324,12 +312,6 @@ struct RemoteSettingsView: View {
                     message: Text(alertMessage ?? "Invalid input."),
                     message: Text(alertMessage ?? "Invalid input."),
                     dismissButton: .default(Text("OK"))
                     dismissButton: .default(Text("OK"))
                 )
                 )
-            case .qrCodeError:
-                return Alert(
-                    title: Text("QR Code Error"),
-                    message: Text(alertMessage ?? "An error occurred while processing the QR code."),
-                    dismissButton: .default(Text("OK"))
-                )
             case .urlTokenValidation:
             case .urlTokenValidation:
                 return Alert(
                 return Alert(
                     title: Text("URL/Token Validation"),
                     title: Text("URL/Token Validation"),
@@ -355,30 +337,6 @@ struct RemoteSettingsView: View {
                 viewModel.handleLoopAPNSQRCodeScanResult(result)
                 viewModel.handleLoopAPNSQRCodeScanResult(result)
             }
             }
         }
         }
-        .sheet(isPresented: $viewModel.isShowingQRCodeScanner) {
-            SimpleQRCodeScannerView { result in
-                viewModel.handleRemoteCommandQRCodeScanResult(result)
-            }
-        }
-        .sheet(isPresented: $viewModel.isShowingQRCodeDisplay) {
-            NavigationView {
-                VStack {
-                    if let qrCodeString = viewModel.generateQRCodeForCurrentSettings() {
-                        QRCodeDisplayView(qrCodeString: qrCodeString)
-                            .padding()
-                    } else {
-                        Text("Failed to generate QR code")
-                            .foregroundColor(.red)
-                            .padding()
-                    }
-                }
-                .navigationTitle("Share Remote Settings")
-                .navigationBarTitleDisplayMode(.inline)
-                .navigationBarItems(trailing: Button("Done") {
-                    viewModel.isShowingQRCodeDisplay = false
-                })
-            }
-        }
         .sheet(isPresented: $viewModel.showURLTokenValidation) {
         .sheet(isPresented: $viewModel.showURLTokenValidation) {
             NavigationView {
             NavigationView {
                 URLTokenValidationView(
                 URLTokenValidationView(
@@ -410,15 +368,6 @@ struct RemoteSettingsView: View {
             let now = Date().timeIntervalSince1970
             let now = Date().timeIntervalSince1970
             otpTimeRemaining = Int(otpPeriod - (now.truncatingRemainder(dividingBy: otpPeriod)))
             otpTimeRemaining = Int(otpPeriod - (now.truncatingRemainder(dividingBy: otpPeriod)))
         }
         }
-        .onReceive(viewModel.$qrCodeErrorMessage) { errorMessage in
-            if let errorMessage = errorMessage, !errorMessage.isEmpty {
-                handleQRCodeError(errorMessage)
-                // Clear the error message after showing the alert
-                DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
-                    viewModel.qrCodeErrorMessage = nil
-                }
-            }
-        }
         .onReceive(viewModel.$showURLTokenValidation) { showValidation in
         .onReceive(viewModel.$showURLTokenValidation) { showValidation in
             if showValidation {
             if showValidation {
                 // The sheet will be shown automatically due to the binding
                 // The sheet will be shown automatically due to the binding
@@ -459,14 +408,6 @@ struct RemoteSettingsView: View {
         showAlert = true
         showAlert = true
     }
     }
 
 
-    // MARK: - QR Code Error Handler
-
-    private func handleQRCodeError(_ message: String) {
-        alertMessage = message
-        alertType = .qrCodeError
-        showAlert = true
-    }
-
     private var guardrailsSection: some View {
     private var guardrailsSection: some View {
         Section(header: Text("Guardrails")) {
         Section(header: Text("Guardrails")) {
             HStack {
             HStack {

+ 0 - 48
LoopFollow/Remote/Settings/RemoteSettingsViewModel.swift

@@ -34,12 +34,6 @@ class RemoteSettingsViewModel: ObservableObject {
     @Published var isShowingLoopAPNSScanner: Bool = false
     @Published var isShowingLoopAPNSScanner: Bool = false
     @Published var loopAPNSErrorMessage: String?
     @Published var loopAPNSErrorMessage: String?
 
 
-    // MARK: - QR Code Sharing Properties
-
-    @Published var isShowingQRCodeScanner: Bool = false
-    @Published var isShowingQRCodeDisplay: Bool = false
-    @Published var qrCodeErrorMessage: String?
-
     // MARK: - URL/Token Validation Properties
     // MARK: - URL/Token Validation Properties
 
 
     @Published var pendingSettings: RemoteCommandSettings?
     @Published var pendingSettings: RemoteCommandSettings?
@@ -233,48 +227,6 @@ class RemoteSettingsViewModel: ObservableObject {
         }
         }
     }
     }
 
 
-    // MARK: - QR Code Sharing Methods
-
-    func handleRemoteCommandQRCodeScanResult(_ result: Result<String, Error>) {
-        DispatchQueue.main.async {
-            switch result {
-            case let .success(jsonString):
-                if let settings = RemoteCommandSettings.decodeFromJSON(jsonString) {
-                    if settings.isValid() {
-                        // Check URL and token compatibility
-                        let validation = settings.validateCompatibilityWithCurrentStorage()
-
-                        if validation.isCompatible {
-                            // No conflicts, apply settings directly
-                            settings.applyToStorage()
-                            self.updateViewModelFromStorage()
-                            LogManager.shared.log(category: .remote, message: "Remote command settings imported from QR code")
-                        } else {
-                            // Conflicts detected, show validation view
-                            self.pendingSettings = settings
-                            self.validationMessage = validation.message
-                            self.shouldPromptForURL = validation.shouldPromptForURL
-                            self.shouldPromptForToken = validation.shouldPromptForToken
-                            self.showURLTokenValidation = true
-                        }
-                    } else {
-                        self.qrCodeErrorMessage = "Invalid remote command settings in QR code"
-                    }
-                } else {
-                    self.qrCodeErrorMessage = "Failed to decode remote command settings from QR code"
-                }
-            case let .failure(error):
-                self.qrCodeErrorMessage = "Scanning failed: \(error.localizedDescription)"
-            }
-            self.isShowingQRCodeScanner = false
-        }
-    }
-
-    func generateQRCodeForCurrentSettings() -> String? {
-        let settings = RemoteCommandSettings.fromCurrentStorage()
-        return settings.encodeToJSON()
-    }
-
     // MARK: - Public Methods for View Access
     // MARK: - Public Methods for View Access
 
 
     /// Updates the view model properties from storage (accessible from view)
     /// Updates the view model properties from storage (accessible from view)

+ 14 - 0
LoopFollow/Settings/DexcomSettingsView.swift

@@ -33,9 +33,23 @@ struct DexcomSettingsView: View {
                     }
                     }
                     .pickerStyle(SegmentedPickerStyle())
                     .pickerStyle(SegmentedPickerStyle())
                 }
                 }
+
+                importSection
             }
             }
         }
         }
         .preferredColorScheme(Storage.shared.forceDarkMode.value ? .dark : nil)
         .preferredColorScheme(Storage.shared.forceDarkMode.value ? .dark : nil)
         .navigationBarTitle("Dexcom Settings", displayMode: .inline)
         .navigationBarTitle("Dexcom Settings", displayMode: .inline)
     }
     }
+
+    private var importSection: some View {
+        Section(header: Text("Import Settings")) {
+            NavigationLink(destination: ImportExportSettingsView()) {
+                HStack {
+                    Image(systemName: "square.and.arrow.down")
+                        .foregroundColor(.blue)
+                    Text("Import Settings from QR Code or iCloud")
+                }
+            }
+        }
+    }
 }
 }

+ 248 - 0
LoopFollow/Settings/ImportExport/AlarmSelectionView.swift

@@ -0,0 +1,248 @@
+// LoopFollow
+// AlarmSelectionView.swift
+
+import SwiftUI
+
+struct AlarmSelectionView: View {
+    @ObservedObject private var allAlarms = Storage.shared.alarms
+    @State private var selectedAlarmIds: Set<UUID> = []
+    @State private var showingCharacterLimitAlert = false
+
+    let exportedAlarmIds: Set<UUID>
+    let onConfirm: ([Alarm]) -> Void
+    let onCancel: () -> Void
+
+    // QR Code character limits (calibrated for alarm exports)
+    private let maxQRCharacters = 2000
+    private let maxRecommendedAlarms = 5
+
+    // Computed property for actual character count (used internally)
+    private var actualCharacterCount: Int {
+        let selectedAlarms = allAlarms.value.filter { selectedAlarmIds.contains($0.id) }
+        let testExport = AlarmSettingsExport(
+            version: AppVersionManager().version(),
+            alarms: selectedAlarms,
+            alarmConfiguration: Storage.shared.alarmConfiguration.value
+        )
+
+        if let jsonString = testExport.encodeToJSON() {
+            return jsonString.count
+        }
+        return 0
+    }
+
+    private var exceedsCharacterLimit: Bool {
+        return actualCharacterCount > maxQRCharacters
+    }
+
+    var body: some View {
+        NavigationView {
+            VStack {
+                // Character count indicator
+                characterCountView
+
+                List {
+                    ForEach(allAlarms.value) { alarm in
+                        AlarmSelectionRow(
+                            alarm: alarm,
+                            isSelected: selectedAlarmIds.contains(alarm.id),
+                            isExported: exportedAlarmIds.contains(alarm.id),
+                            isDisabled: !canSelectAlarm(alarm),
+                            onToggle: { toggleAlarm(alarm) }
+                        )
+                    }
+                }
+            }
+            .navigationTitle("Select Alarms to Export")
+            .navigationBarTitleDisplayMode(.inline)
+            .navigationBarItems(
+                leading: Button("Cancel") {
+                    onCancel()
+                },
+                trailing: Button("Export") {
+                    let selectedAlarms = allAlarms.value.filter { selectedAlarmIds.contains($0.id) }
+                    onConfirm(selectedAlarms)
+                }
+                .disabled(selectedAlarmIds.isEmpty)
+            )
+        }
+        .alert("Character Limit Reached", isPresented: $showingCharacterLimitAlert) {
+            Button("OK") {}
+        } message: {
+            Text("Adding this alarm would exceed the QR code character limit. Please remove some alarms first.")
+        }
+    }
+
+    private var characterCountView: some View {
+        VStack(spacing: 8) {
+            HStack {
+                Text("Selected Alarms: \(selectedAlarmIds.count)")
+                    .font(.headline)
+                Spacer()
+                Text("Max \(maxRecommendedAlarms) alarms at a time")
+                    .font(.caption)
+                    .foregroundColor(.secondary)
+            }
+
+            if selectedAlarmIds.count > 0 {
+                ProgressView(value: Double(selectedAlarmIds.count), total: Double(maxRecommendedAlarms))
+                    .progressViewStyle(LinearProgressViewStyle(tint: progressBarColor))
+            }
+        }
+        .padding()
+        .background(Color(.systemGray6))
+    }
+
+    private var progressBarColor: Color {
+        if exceedsCharacterLimit {
+            return .red
+        } else if selectedAlarmIds.count >= maxRecommendedAlarms {
+            return .blue
+        } else {
+            return .green
+        }
+    }
+
+    private func canSelectAlarm(_ alarm: Alarm) -> Bool {
+        let testSelection = selectedAlarmIds.union([alarm.id])
+        let testAlarms = allAlarms.value.filter { testSelection.contains($0.id) }
+
+        // Block if we're at or over the recommended alarm limit
+        if testAlarms.count > maxRecommendedAlarms {
+            return false
+        }
+
+        // Test actual character count to prevent exceeding QR code limit
+        let testExport = AlarmSettingsExport(
+            version: AppVersionManager().version(),
+            alarms: testAlarms,
+            alarmConfiguration: Storage.shared.alarmConfiguration.value
+        )
+
+        if let jsonString = testExport.encodeToJSON() {
+            return jsonString.count <= maxQRCharacters
+        }
+
+        return false
+    }
+
+    private func toggleAlarm(_ alarm: Alarm) {
+        if selectedAlarmIds.contains(alarm.id) {
+            selectedAlarmIds.remove(alarm.id)
+        } else {
+            if canSelectAlarm(alarm) {
+                selectedAlarmIds.insert(alarm.id)
+            } else {
+                showingCharacterLimitAlert = true
+            }
+        }
+    }
+}
+
+struct AlarmSelectionRow: View {
+    let alarm: Alarm
+    let isSelected: Bool
+    let isExported: Bool
+    let isDisabled: Bool
+    let onToggle: () -> Void
+
+    var body: some View {
+        Button(action: onToggle) {
+            HStack {
+                VStack(alignment: .leading, spacing: 4) {
+                    HStack {
+                        Text(alarm.name)
+                            .font(.headline)
+                            .foregroundColor(isDisabled ? .secondary : .primary)
+
+                        if isExported {
+                            Image(systemName: "checkmark.circle.fill")
+                                .foregroundColor(.green)
+                                .font(.caption)
+                        }
+                    }
+
+                    Text(alarmTypeDescription)
+                        .font(.caption)
+                        .foregroundColor(.secondary)
+
+                    if let aboveBG = alarm.aboveBG {
+                        Text("Above: \(String(format: "%.0f", aboveBG))")
+                            .font(.caption2)
+                            .foregroundColor(.secondary)
+                    }
+
+                    if let belowBG = alarm.belowBG {
+                        Text("Below: \(String(format: "%.0f", belowBG))")
+                            .font(.caption2)
+                            .foregroundColor(.secondary)
+                    }
+                }
+
+                Spacer()
+
+                Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
+                    .foregroundColor(isSelected ? .blue : (isDisabled ? .secondary : .primary))
+                    .font(.title2)
+            }
+            .padding(.vertical, 4)
+        }
+        .buttonStyle(.plain)
+        .disabled(isDisabled && !isSelected)
+    }
+
+    private var alarmTypeDescription: String {
+        switch alarm.type {
+        case .temporary:
+            return "Temporary Alert"
+        case .iob:
+            return "IOB Alert"
+        case .cob:
+            return "COB Alert"
+        case .low:
+            return "Low BG Alert"
+        case .high:
+            return "High BG Alert"
+        case .fastDrop:
+            return "Fast Drop Alert"
+        case .fastRise:
+            return "Fast Rise Alert"
+        case .missedReading:
+            return "Missed Reading Alert"
+        case .notLooping:
+            return "Not Looping Alert"
+        case .missedBolus:
+            return "Missed Bolus Alert"
+        case .sensorChange:
+            return "Sensor Change Alert"
+        case .pumpChange:
+            return "Pump Change Alert"
+        case .pump:
+            return "Pump Insulin Alert"
+        case .battery:
+            return "Low Battery"
+        case .batteryDrop:
+            return "Battery Drop"
+        case .recBolus:
+            return "Rec. Bolus"
+        case .overrideStart:
+            return "Override Started"
+        case .overrideEnd:
+            return "Override Ended"
+        case .tempTargetStart:
+            return "Temp Target Started"
+        case .tempTargetEnd:
+            return "Temp Target Ended"
+        case .buildExpire:
+            return "Looping app expiration"
+        }
+    }
+}
+
+#Preview {
+    AlarmSelectionView(
+        exportedAlarmIds: [],
+        onConfirm: { _ in },
+        onCancel: {}
+    )
+}

+ 360 - 0
LoopFollow/Settings/ImportExport/ExportableSettings.swift

@@ -0,0 +1,360 @@
+// LoopFollow
+// ExportableSettings.swift
+
+import Foundation
+import HealthKit
+
+// MARK: - Nightscout Settings Export
+
+struct NightscoutSettingsExport: Codable {
+    let version: String
+    let url: String
+    let token: String
+    let units: String
+
+    static func fromCurrentStorage() -> NightscoutSettingsExport {
+        let storage = Storage.shared
+        return NightscoutSettingsExport(
+            version: AppVersionManager().version(),
+            url: storage.url.value,
+            token: storage.token.value,
+            units: storage.units.value
+        )
+    }
+
+    func applyToStorage() {
+        let storage = Storage.shared
+        storage.url.value = url
+        storage.token.value = token
+        storage.units.value = units
+    }
+
+    func encodeToJSON() -> String? {
+        do {
+            let data = try JSONEncoder().encode(self)
+            return String(data: data, encoding: .utf8)
+        } catch {
+            return nil
+        }
+    }
+
+    func hasValidSettings() -> Bool {
+        return !url.isEmpty && !token.isEmpty
+    }
+}
+
+// MARK: - Dexcom Settings Export
+
+struct DexcomSettingsExport: Codable {
+    let version: String
+    let userName: String
+    let password: String
+    let server: String
+
+    static func fromCurrentStorage() -> DexcomSettingsExport {
+        let storage = Storage.shared
+        return DexcomSettingsExport(
+            version: AppVersionManager().version(),
+            userName: storage.shareUserName.value,
+            password: storage.sharePassword.value,
+            server: storage.shareServer.value
+        )
+    }
+
+    func applyToStorage() {
+        let storage = Storage.shared
+        storage.shareUserName.value = userName
+        storage.sharePassword.value = password
+        storage.shareServer.value = server
+    }
+
+    func encodeToJSON() -> String? {
+        do {
+            let data = try JSONEncoder().encode(self)
+            return String(data: data, encoding: .utf8)
+        } catch {
+            return nil
+        }
+    }
+
+    func hasValidSettings() -> Bool {
+        return !userName.isEmpty && !password.isEmpty
+    }
+}
+
+// MARK: - Alarm Settings Export
+
+struct AlarmSettingsExport: Codable {
+    let version: String
+    let alarms: [Alarm]
+    let alarmConfiguration: AlarmConfiguration
+
+    static func fromCurrentStorage() -> AlarmSettingsExport {
+        let storage = Storage.shared
+        return AlarmSettingsExport(
+            version: AppVersionManager().version(),
+            alarms: storage.alarms.value,
+            alarmConfiguration: storage.alarmConfiguration.value
+        )
+    }
+
+    static func fromSelectedAlarms(_ selectedAlarms: [Alarm]) -> AlarmSettingsExport {
+        let storage = Storage.shared
+        return AlarmSettingsExport(
+            version: AppVersionManager().version(),
+            alarms: selectedAlarms,
+            alarmConfiguration: storage.alarmConfiguration.value
+        )
+    }
+
+    func applyToStorage() {
+        let storage = Storage.shared
+        // When importing, merge with existing alarms instead of replacing
+        var existingAlarms = storage.alarms.value
+        var updatedAlarms: [Alarm] = []
+
+        // Keep existing alarms that aren't being imported
+        for existingAlarm in existingAlarms {
+            if !alarms.contains(where: { $0.id == existingAlarm.id }) {
+                updatedAlarms.append(existingAlarm)
+            }
+        }
+
+        // Add imported alarms
+        updatedAlarms.append(contentsOf: alarms)
+
+        storage.alarms.value = updatedAlarms
+        storage.alarmConfiguration.value = alarmConfiguration
+    }
+
+    func encodeToJSON() -> String? {
+        do {
+            let data = try JSONEncoder().encode(self)
+            return String(data: data, encoding: .utf8)
+        } catch {
+            return nil
+        }
+    }
+
+    func hasValidSettings() -> Bool {
+        return !alarms.isEmpty
+    }
+}
+
+// MARK: - Remote Settings Export
+
+struct RemoteSettingsExport: Codable {
+    let version: String
+    let remoteType: RemoteType
+    let user: String
+    let sharedSecret: String
+    let apnsKey: String
+    let keyId: String
+    let teamId: String?
+    let maxBolus: Double
+    let maxCarbs: Double
+    let maxProtein: Double
+    let maxFat: Double
+    let mealWithBolus: Bool
+    let mealWithFatProtein: Bool
+    let productionEnvironment: Bool
+    let loopAPNSQrCodeURL: String
+    let device: String
+
+    static func fromCurrentStorage() -> RemoteSettingsExport {
+        let storage = Storage.shared
+        return RemoteSettingsExport(
+            version: AppVersionManager().version(),
+            remoteType: storage.remoteType.value,
+            user: storage.user.value,
+            sharedSecret: storage.sharedSecret.value,
+            apnsKey: storage.apnsKey.value,
+            keyId: storage.keyId.value,
+            teamId: storage.teamId.value,
+            maxBolus: storage.maxBolus.value.doubleValue(for: .internationalUnit()),
+            maxCarbs: storage.maxCarbs.value.doubleValue(for: .gram()),
+            maxProtein: storage.maxProtein.value.doubleValue(for: .gram()),
+            maxFat: storage.maxFat.value.doubleValue(for: .gram()),
+            mealWithBolus: storage.mealWithBolus.value,
+            mealWithFatProtein: storage.mealWithFatProtein.value,
+            productionEnvironment: storage.productionEnvironment.value,
+            loopAPNSQrCodeURL: storage.loopAPNSQrCodeURL.value,
+            device: storage.device.value
+        )
+    }
+
+    func applyToStorage() {
+        let storage = Storage.shared
+
+        storage.remoteType.value = remoteType
+        storage.user.value = user
+        storage.sharedSecret.value = sharedSecret
+        storage.apnsKey.value = apnsKey
+        storage.keyId.value = keyId
+        storage.teamId.value = teamId
+        storage.maxBolus.value = HKQuantity(unit: .internationalUnit(), doubleValue: maxBolus)
+        storage.maxCarbs.value = HKQuantity(unit: .gram(), doubleValue: maxCarbs)
+        storage.maxProtein.value = HKQuantity(unit: .gram(), doubleValue: maxProtein)
+        storage.maxFat.value = HKQuantity(unit: .gram(), doubleValue: maxFat)
+        storage.mealWithBolus.value = mealWithBolus
+        storage.mealWithFatProtein.value = mealWithFatProtein
+        storage.productionEnvironment.value = productionEnvironment
+        storage.loopAPNSQrCodeURL.value = loopAPNSQrCodeURL
+
+        // Set device temporarily from import (will be overridden by Nightscout connection)
+        if !device.isEmpty {
+            storage.device.value = device
+        } else {
+            // Fallback to automatic device type based on remote type
+            switch remoteType {
+            case .loopAPNS:
+                storage.device.value = "Loop"
+            case .trc:
+                storage.device.value = "Trio"
+            case .nightscout:
+                // For Nightscout, we don't automatically set device type
+                // as it should be determined by the actual connection
+                break
+            case .none:
+                break
+            }
+        }
+    }
+
+    func encodeToJSON() -> String? {
+        do {
+            let data = try JSONEncoder().encode(self)
+            return String(data: data, encoding: .utf8)
+        } catch {
+            return nil
+        }
+    }
+
+    func hasValidSettings() -> Bool {
+        switch remoteType {
+        case .none:
+            return true
+        case .nightscout:
+            return !user.isEmpty
+        case .trc:
+            return !user.isEmpty && !sharedSecret.isEmpty && !apnsKey.isEmpty && !keyId.isEmpty
+        case .loopAPNS:
+            return !keyId.isEmpty && !apnsKey.isEmpty && teamId != nil && !loopAPNSQrCodeURL.isEmpty
+        }
+    }
+
+    /// Validates compatibility with current storage settings
+    func validateCompatibilityWithCurrentStorage() -> (isCompatible: Bool, shouldPromptForURL: Bool, shouldPromptForToken: Bool, message: String) {
+        let storage = Storage.shared
+        var message = ""
+        var shouldPromptForURL = false
+        var shouldPromptForToken = false
+
+        // Check if there are existing remote settings
+        let currentRemoteType = storage.remoteType.value
+        let currentUser = storage.user.value
+
+        // If remote type is changing, warn user
+        if currentRemoteType != .none && currentRemoteType != remoteType {
+            message += "Remote type is changing from \(currentRemoteType.rawValue) to \(remoteType.rawValue). This may affect your remote commands.\n"
+        }
+
+        // If user is changing, warn user
+        if !currentUser.isEmpty && currentUser != user {
+            message += "Remote user is changing from '\(currentUser)' to '\(user)'. This may affect your remote commands.\n"
+        }
+
+        // For TRC and LoopAPNS, check if key details are changing
+        if remoteType == .trc || remoteType == .loopAPNS {
+            let currentKeyId = storage.keyId.value
+            let currentApnsKey = storage.apnsKey.value
+
+            if !currentKeyId.isEmpty, currentKeyId != keyId {
+                message += "APNS Key ID is changing. This may affect your remote commands.\n"
+            }
+
+            if !currentApnsKey.isEmpty, currentApnsKey != apnsKey {
+                message += "APNS Key is changing. This may affect your remote commands.\n"
+            }
+        }
+
+        // For TRC, check shared secret
+        if remoteType == .trc {
+            let currentSharedSecret = storage.sharedSecret.value
+            if !currentSharedSecret.isEmpty, currentSharedSecret != sharedSecret {
+                message += "Shared secret is changing. This may affect your remote commands.\n"
+            }
+        }
+
+        // For LoopAPNS, check team ID and QR code URL
+        if remoteType == .loopAPNS {
+            let currentTeamId = storage.teamId.value
+            let currentQrCodeURL = storage.loopAPNSQrCodeURL.value
+
+            if let teamId = teamId, let currentTeamId = currentTeamId, teamId != currentTeamId {
+                message += "Team ID is changing. This may affect your remote commands.\n"
+            }
+
+            if !currentQrCodeURL.isEmpty, currentQrCodeURL != loopAPNSQrCodeURL {
+                message += "Loop APNS QR Code URL is changing. This may affect your remote commands.\n"
+            }
+        }
+
+        // If both have tokens but they don't match, show warning
+        let hasCurrentToken = !storage.token.value.isEmpty
+        if hasCurrentToken {
+            message += "Note: This import does not include Nightscout token settings. Your current Nightscout token will be preserved.\n"
+        }
+
+        let isCompatible = !shouldPromptForURL && !shouldPromptForToken
+
+        return (isCompatible, shouldPromptForURL, shouldPromptForToken, message)
+    }
+}
+
+// MARK: - Combined Settings Export
+
+struct CombinedSettingsExport: Codable {
+    let version: String
+    let appVersion: String
+    let nightscout: NightscoutSettingsExport?
+    let dexcom: DexcomSettingsExport?
+    let remote: RemoteSettingsExport?
+    let alarms: AlarmSettingsExport?
+    let exportType: String
+    let timestamp: Date
+
+    init(nightscout: NightscoutSettingsExport? = nil,
+         dexcom: DexcomSettingsExport? = nil,
+         remote: RemoteSettingsExport? = nil,
+         alarms: AlarmSettingsExport? = nil,
+         exportType: String)
+    {
+        version = "1.0"
+        appVersion = AppVersionManager().version()
+        self.nightscout = nightscout
+        self.dexcom = dexcom
+        self.remote = remote
+        self.alarms = alarms
+        self.exportType = exportType
+        timestamp = Date()
+    }
+
+    func encodeToJSON() -> String? {
+        do {
+            let data = try JSONEncoder().encode(self)
+            return String(data: data, encoding: .utf8)
+        } catch {
+            return nil
+        }
+    }
+
+    static func decodeFromJSON(_ jsonString: String) -> CombinedSettingsExport? {
+        guard let data = jsonString.data(using: .utf8) else { return nil }
+        do {
+            return try JSONDecoder().decode(CombinedSettingsExport.self, from: data)
+        } catch {
+            return nil
+        }
+    }
+}

+ 480 - 0
LoopFollow/Settings/ImportExport/ImportExportSettingsView.swift

@@ -0,0 +1,480 @@
+// LoopFollow
+// ImportExportSettingsView.swift
+
+import AVFoundation
+import SwiftUI
+import UIKit
+
+struct ImportExportSettingsView: View {
+    @StateObject private var viewModel = ImportExportSettingsViewModel()
+
+    var body: some View {
+        NavigationView {
+            List {
+                // MARK: - Import Section
+
+                Section("Import Settings") {
+                    Button(action: {
+                        viewModel.isShowingQRCodeScanner = true
+                    }) {
+                        HStack {
+                            Image(systemName: "qrcode.viewfinder")
+                                .foregroundColor(.blue)
+                            Text("Scan QR Code to Import Settings")
+                        }
+                    }
+                    .buttonStyle(.plain)
+                }
+
+                // MARK: - Export Section
+
+                Section("Export Settings To QR Code") {
+                    ForEach(ImportExportSettingsViewModel.ExportType.allCases, id: \.self) { exportType in
+                        Button(action: {
+                            if exportType == .alarms {
+                                viewModel.showAlarmSelection()
+                            } else {
+                                viewModel.exportType = exportType
+                                if let qrString = viewModel.generateQRCodeForExport() {
+                                    viewModel.qrCodeString = qrString
+                                    viewModel.isShowingQRCodeDisplay = true
+                                }
+                            }
+                        }) {
+                            HStack {
+                                Image(systemName: exportType.icon)
+                                    .foregroundColor(.blue)
+                                Text("Export \(exportType.rawValue)")
+                                Spacer()
+                                Image(systemName: exportType == .alarms ? "list.bullet" : "qrcode")
+                                    .foregroundColor(.secondary)
+                            }
+                        }
+                        .buttonStyle(.plain)
+                    }
+                }
+
+                // MARK: - iCloud Section
+
+                Section("iCloud Import") {
+                    Button(action: {
+                        viewModel.importFromiCloud()
+                    }) {
+                        HStack {
+                            Image(systemName: "icloud.and.arrow.down")
+                                .foregroundColor(.green)
+                            Text("Import Settings from iCloud")
+                        }
+                    }
+                    .buttonStyle(.plain)
+                }
+
+                Section("iCloud Export") {
+                    Button(action: {
+                        viewModel.exportToiCloud()
+                    }) {
+                        HStack {
+                            Image(systemName: "icloud.and.arrow.up")
+                                .foregroundColor(.blue)
+                            Text("Export All Settings to iCloud")
+                        }
+                    }
+                    .buttonStyle(.plain)
+                }
+
+                // MARK: - Status Message
+
+                if !viewModel.qrCodeErrorMessage.isEmpty {
+                    Section {
+                        let isSuccess = viewModel.qrCodeErrorMessage.contains("successfully") || viewModel.qrCodeErrorMessage.contains("Successfully imported")
+                        let displayText = isSuccess ? "✅ \(viewModel.qrCodeErrorMessage)" : viewModel.qrCodeErrorMessage
+
+                        Text(displayText)
+                            .foregroundColor(isSuccess ? .green : .red)
+                            .font(.caption)
+                    }
+                }
+            }
+            .navigationTitle("Import/Export Settings")
+            .navigationBarTitleDisplayMode(.inline)
+        }
+        .sheet(isPresented: $viewModel.isShowingQRCodeScanner) {
+            SimpleQRCodeScannerView { result in
+                viewModel.handleQRCodeScanResult(result)
+            }
+        }
+        .sheet(isPresented: $viewModel.isShowingQRCodeDisplay) {
+            NavigationView {
+                VStack {
+                    if !viewModel.qrCodeString.isEmpty {
+                        QRCodeDisplayView(
+                            qrCodeString: viewModel.qrCodeString,
+                            size: CGSize(width: 300, height: 300)
+                        )
+                        .padding()
+
+                        Text("Scan this QR code with another LoopFollow app to import \(viewModel.exportType.rawValue.lowercased())")
+                            .font(.caption)
+                            .foregroundColor(.secondary)
+                            .multilineTextAlignment(.center)
+                            .padding(.horizontal, 20)
+                    } else {
+                        Text("Failed to generate QR code")
+                            .foregroundColor(.red)
+                            .padding()
+                    }
+                }
+                .navigationTitle("Export \(viewModel.exportType.rawValue)")
+                .navigationBarTitleDisplayMode(.inline)
+                .navigationBarItems(trailing: Button("Done") {
+                    viewModel.isShowingQRCodeDisplay = false
+                })
+            }
+        }
+        .sheet(isPresented: $viewModel.isShowingAlarmSelection) {
+            AlarmSelectionView(
+                exportedAlarmIds: viewModel.exportedAlarmIds,
+                onConfirm: { selectedAlarms in
+                    viewModel.exportSelectedAlarms(selectedAlarms)
+                },
+                onCancel: {
+                    viewModel.cancelAlarmSelection()
+                }
+            )
+        }
+        .onDisappear {
+            viewModel.resetExportedAlarms()
+        }
+        .sheet(isPresented: $viewModel.showImportConfirmation) {
+            ImportConfirmationView(viewModel: viewModel)
+        }
+        .sheet(isPresented: $viewModel.showExportSuccessAlert) {
+            ExportSuccessView(viewModel: viewModel)
+        }
+        .sheet(isPresented: $viewModel.showImportNotFoundAlert) {
+            ImportNotFoundView(viewModel: viewModel)
+        }
+    }
+}
+
+struct ImportNotFoundView: View {
+    @ObservedObject var viewModel: ImportExportSettingsViewModel
+
+    var body: some View {
+        NavigationView {
+            VStack(spacing: 20) {
+                // Header
+                VStack(spacing: 12) {
+                    Image(systemName: "icloud.slash")
+                        .font(.system(size: 60))
+                        .foregroundColor(.orange)
+
+                    Text("No Settings Found")
+                        .font(.title2)
+                        .fontWeight(.semibold)
+
+                    Text(viewModel.importNotFoundMessage)
+                        .font(.subheadline)
+                        .foregroundColor(.secondary)
+                        .multilineTextAlignment(.center)
+                        .padding(.horizontal)
+                }
+                .padding(.top, 30)
+
+                Spacer()
+
+                // Done Button
+                Button(action: {
+                    viewModel.showImportNotFoundAlert = false
+                }) {
+                    HStack {
+                        Image(systemName: "xmark")
+                        Text("OK")
+                    }
+                    .font(.headline)
+                    .foregroundColor(.white)
+                    .frame(maxWidth: .infinity)
+                    .padding()
+                    .background(Color.blue)
+                    .cornerRadius(12)
+                }
+                .padding(.horizontal)
+                .padding(.bottom, 20)
+            }
+            .navigationBarHidden(true)
+        }
+    }
+}
+
+struct ExportSuccessView: View {
+    @ObservedObject var viewModel: ImportExportSettingsViewModel
+
+    var body: some View {
+        NavigationView {
+            VStack(spacing: 20) {
+                // Header
+                VStack(spacing: 12) {
+                    Image(systemName: "checkmark.icloud.fill")
+                        .font(.system(size: 60))
+                        .foregroundColor(.green)
+
+                    Text("Export Successful")
+                        .font(.title2)
+                        .fontWeight(.semibold)
+
+                    Text(viewModel.exportSuccessMessage)
+                        .font(.subheadline)
+                        .foregroundColor(.secondary)
+                        .multilineTextAlignment(.center)
+                }
+                .padding(.top, 30)
+
+                // Exported Settings Details
+                if !viewModel.exportSuccessDetails.isEmpty {
+                    VStack(alignment: .leading, spacing: 16) {
+                        Text("Exported Settings")
+                            .font(.headline)
+                            .padding(.horizontal)
+
+                        VStack(spacing: 12) {
+                            ForEach(viewModel.exportSuccessDetails, id: \.self) { detail in
+                                HStack(spacing: 12) {
+                                    Image(systemName: iconForDetail(detail))
+                                        .font(.title2)
+                                        .foregroundColor(colorForDetail(detail))
+                                        .frame(width: 30)
+
+                                    Text(detail)
+                                        .font(.subheadline)
+                                        .lineLimit(2)
+
+                                    Spacer()
+
+                                    Image(systemName: "checkmark.circle.fill")
+                                        .foregroundColor(.green)
+                                }
+                                .padding()
+                                .background(Color(.systemGray6))
+                                .cornerRadius(10)
+                            }
+                        }
+                        .padding(.horizontal)
+                    }
+                }
+
+                Spacer()
+
+                // Done Button
+                Button(action: {
+                    viewModel.showExportSuccessAlert = false
+                }) {
+                    HStack {
+                        Image(systemName: "checkmark")
+                        Text("Done")
+                    }
+                    .font(.headline)
+                    .foregroundColor(.white)
+                    .frame(maxWidth: .infinity)
+                    .padding()
+                    .background(Color.blue)
+                    .cornerRadius(12)
+                }
+                .padding(.horizontal)
+                .padding(.bottom, 20)
+            }
+            .navigationBarHidden(true)
+        }
+    }
+
+    private func iconForDetail(_ detail: String) -> String {
+        if detail.lowercased().contains("nightscout") {
+            return "network"
+        } else if detail.lowercased().contains("dexcom") {
+            return "person.circle"
+        } else if detail.lowercased().contains("remote") {
+            return "antenna.radiowaves.left.and.right"
+        } else if detail.lowercased().contains("alarm") {
+            return "bell"
+        }
+        return "gear"
+    }
+
+    private func colorForDetail(_ detail: String) -> Color {
+        if detail.lowercased().contains("nightscout") {
+            return .blue
+        } else if detail.lowercased().contains("dexcom") {
+            return .green
+        } else if detail.lowercased().contains("remote") {
+            return .orange
+        } else if detail.lowercased().contains("alarm") {
+            return .red
+        }
+        return .gray
+    }
+}
+
+struct ImportConfirmationView: View {
+    @ObservedObject var viewModel: ImportExportSettingsViewModel
+
+    var body: some View {
+        NavigationView {
+            VStack(spacing: 20) {
+                // Header
+                VStack(spacing: 8) {
+                    Image(systemName: "square.and.arrow.down")
+                        .font(.system(size: 50))
+                        .foregroundColor(.blue)
+
+                    Text("Import Settings")
+                        .font(.title2)
+                        .fontWeight(.semibold)
+
+                    Text("Review the settings that will be imported")
+                        .font(.subheadline)
+                        .foregroundColor(.secondary)
+                        .multilineTextAlignment(.center)
+                }
+                .padding(.top, 20)
+
+                // Settings Preview
+                if let preview = viewModel.importPreview {
+                    VStack(alignment: .leading, spacing: 16) {
+                        Text("Settings to Import")
+                            .font(.headline)
+                            .padding(.horizontal)
+
+                        VStack(spacing: 12) {
+                            if let url = preview.nightscoutURL, !url.isEmpty {
+                                SettingRowView(
+                                    icon: "network",
+                                    title: "Nightscout URL",
+                                    value: url,
+                                    color: .blue
+                                )
+                            }
+
+                            if let username = preview.dexcomUsername, !username.isEmpty {
+                                SettingRowView(
+                                    icon: "person.circle",
+                                    title: "Dexcom Username",
+                                    value: username,
+                                    color: .green
+                                )
+                            }
+
+                            if let remoteType = preview.remoteType, !remoteType.isEmpty, remoteType != "None" {
+                                SettingRowView(
+                                    icon: "antenna.radiowaves.left.and.right",
+                                    title: "Remote Type",
+                                    value: remoteType,
+                                    color: .orange
+                                )
+                            }
+
+                            if preview.alarmCount > 0 {
+                                SettingRowView(
+                                    icon: "bell",
+                                    title: "Alarms",
+                                    value: "\(preview.alarmCount) alarm(s): \(preview.alarmNames.joined(separator: ", "))",
+                                    color: .red
+                                )
+                            }
+                        }
+                        .padding(.horizontal)
+                    }
+                }
+
+                // Warning
+                VStack(spacing: 8) {
+                    HStack {
+                        Image(systemName: "exclamationmark.triangle")
+                            .foregroundColor(.orange)
+                        Text("Warning")
+                            .fontWeight(.semibold)
+                            .foregroundColor(.orange)
+                    }
+
+                    Text("This will overwrite your current settings")
+                        .font(.subheadline)
+                        .foregroundColor(.secondary)
+                        .multilineTextAlignment(.center)
+                }
+                .padding(.horizontal)
+
+                Spacer()
+
+                // Action Buttons
+                VStack(spacing: 12) {
+                    Button(action: {
+                        viewModel.confirmImport()
+                    }) {
+                        HStack {
+                            Image(systemName: "checkmark")
+                            Text("Import Settings")
+                        }
+                        .font(.headline)
+                        .foregroundColor(.white)
+                        .frame(maxWidth: .infinity)
+                        .padding()
+                        .background(Color.blue)
+                        .cornerRadius(12)
+                    }
+
+                    Button(action: {
+                        viewModel.cancelImport()
+                    }) {
+                        HStack {
+                            Image(systemName: "xmark")
+                            Text("Cancel")
+                        }
+                        .font(.headline)
+                        .foregroundColor(.primary)
+                        .frame(maxWidth: .infinity)
+                        .padding()
+                        .background(Color(.systemGray6))
+                        .cornerRadius(12)
+                    }
+                }
+                .padding(.horizontal)
+                .padding(.bottom, 20)
+            }
+            .navigationBarHidden(true)
+        }
+    }
+}
+
+struct SettingRowView: View {
+    let icon: String
+    let title: String
+    let value: String
+    let color: Color
+
+    var body: some View {
+        HStack(spacing: 12) {
+            Image(systemName: icon)
+                .font(.title2)
+                .foregroundColor(color)
+                .frame(width: 30)
+
+            VStack(alignment: .leading, spacing: 2) {
+                Text(title)
+                    .font(.subheadline)
+                    .fontWeight(.medium)
+
+                Text(value)
+                    .font(.caption)
+                    .foregroundColor(.secondary)
+                    .lineLimit(2)
+            }
+
+            Spacer()
+        }
+        .padding()
+        .background(Color(.systemGray6))
+        .cornerRadius(10)
+    }
+}
+
+#Preview {
+    ImportExportSettingsView()
+}

+ 439 - 0
LoopFollow/Settings/ImportExport/ImportExportSettingsViewModel.swift

@@ -0,0 +1,439 @@
+// LoopFollow
+// ImportExportSettingsViewModel.swift
+
+import Foundation
+import SwiftUI
+
+struct ImportPreview {
+    let nightscoutURL: String?
+    let dexcomUsername: String?
+    let remoteType: String?
+    let alarmCount: Int
+    let alarmNames: [String]
+}
+
+class ImportExportSettingsViewModel: ObservableObject {
+    // MARK: - Published Properties
+
+    @Published var isShowingQRCodeScanner = false
+    @Published var isShowingQRCodeDisplay = false
+    @Published var isShowingAlarmSelection = false
+    @Published var qrCodeErrorMessage = ""
+    @Published var qrCodeString = ""
+    @Published var exportType: ExportType = .nightscout
+    @Published var exportedAlarmIds: Set<UUID> = []
+    @Published var importPreview: ImportPreview?
+    @Published var showImportConfirmation = false
+    @Published var pendingImportSettings: CombinedSettingsExport?
+    @Published var pendingImportSource: String = ""
+    @Published var showExportSuccessAlert = false
+    @Published var exportSuccessMessage = ""
+    @Published var exportSuccessDetails: [String] = []
+    @Published var showImportNotFoundAlert = false
+    @Published var importNotFoundMessage = ""
+
+    // MARK: - Export Types
+
+    enum ExportType: String, CaseIterable {
+        case nightscout = "Nightscout Settings"
+        case remote = "Remote Settings"
+        case alarms = "Alarm Settings"
+
+        var icon: String {
+            switch self {
+            case .nightscout: return "network"
+            case .remote: return "antenna.radiowaves.left.and.right"
+            case .alarms: return "bell"
+            }
+        }
+    }
+
+    // MARK: - QR Code Methods
+
+    func handleQRCodeScanResult(_ result: Result<String, Error>) {
+        DispatchQueue.main.async {
+            switch result {
+            case let .success(jsonString):
+                self.processImportedSettings(jsonString)
+            case let .failure(error):
+                self.qrCodeErrorMessage = "Scanning failed: \(error.localizedDescription)"
+            }
+            self.isShowingQRCodeScanner = false
+        }
+    }
+
+    private func processImportedSettings(_ jsonString: String) {
+        do {
+            LogManager.shared.log(category: .general, message: "Processing QR code data: \(jsonString.prefix(200))...")
+
+            guard let data = jsonString.data(using: .utf8) else {
+                qrCodeErrorMessage = "Invalid QR code data"
+                return
+            }
+
+            LogManager.shared.log(category: .general, message: "QR code data converted to Data, size: \(data.count) bytes")
+
+            // Try to decode as JSON first to see what we get
+            do {
+                let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
+                LogManager.shared.log(category: .general, message: "JSON parsing successful: \(jsonObject)")
+            } catch {
+                LogManager.shared.log(category: .general, message: "JSON parsing failed: \(error.localizedDescription)")
+            }
+
+            // Use migration manager to handle version compatibility
+            guard let settings = SettingsMigrationManager.migrateSettings(data) else {
+                LogManager.shared.log(category: .general, message: "SettingsMigrationManager.migrateSettings returned nil")
+                qrCodeErrorMessage = "Failed to decode or migrate settings from QR code"
+                return
+            }
+
+            LogManager.shared.log(category: .general, message: "QR code decoded successfully. Components: nightscout=\(settings.nightscout != nil), remote=\(settings.remote != nil), alarms=\(settings.alarms != nil)")
+
+            // Check version compatibility
+            let currentVersion = AppVersionManager().version()
+            if !SettingsMigrationManager.isCompatibleVersion(settings.appVersion) {
+                qrCodeErrorMessage = SettingsMigrationManager.getCompatibilityMessage(for: settings.appVersion)
+                // Still try to apply settings, but warn user
+            }
+
+            // Store settings and create preview for confirmation
+            pendingImportSettings = settings
+            pendingImportSource = "QR code"
+            createImportPreview(from: settings)
+
+        } catch {
+            let currentVersion = AppVersionManager().version()
+            qrCodeErrorMessage = "Import failed. This might be due to a version change (current: \(currentVersion)). Please try exporting settings from the source device again."
+            LogManager.shared.log(category: .general, message: "QR code import failed: \(error.localizedDescription)")
+        }
+    }
+
+    private func applyImportedSettings(_ settings: CombinedSettingsExport, source: String) throws {
+        var importedComponents: [String] = []
+
+        // Apply settings based on what's available
+        if let nightscout = settings.nightscout {
+            // Check if Nightscout settings are already configured
+            let currentNightscout = NightscoutSettingsExport.fromCurrentStorage()
+            if currentNightscout.hasValidSettings() {
+                // Nightscout is already configured, warn user about overwrite
+                LogManager.shared.log(category: .general, message: "Warning: Nightscout settings are already configured. Import will overwrite existing Nightscout settings.")
+            }
+
+            nightscout.applyToStorage()
+            importedComponents.append("Nightscout settings")
+            LogManager.shared.log(category: .general, message: "Nightscout settings imported from \(source) (version: \(nightscout.version))")
+        }
+
+        if let dexcom = settings.dexcom {
+            // Check if Dexcom settings are already configured
+            let currentDexcom = DexcomSettingsExport.fromCurrentStorage()
+            if currentDexcom.hasValidSettings() {
+                LogManager.shared.log(category: .general, message: "Warning: Dexcom settings are already configured. Import will overwrite existing Dexcom settings.")
+            }
+
+            dexcom.applyToStorage()
+            importedComponents.append("Dexcom settings")
+            LogManager.shared.log(category: .general, message: "Dexcom settings imported from \(source) (version: \(dexcom.version))")
+        }
+
+        if let remote = settings.remote {
+            if remote.hasValidSettings() {
+                let validation = remote.validateCompatibilityWithCurrentStorage()
+                if validation.isCompatible {
+                    remote.applyToStorage()
+                    importedComponents.append("Remote settings (\(remote.remoteType.rawValue))")
+                    LogManager.shared.log(category: .general, message: "Remote settings imported from \(source) (version: \(remote.version), type: \(remote.remoteType.rawValue), device: \(remote.device))")
+                } else {
+                    throw NSError(domain: "SettingsImport", code: 1, userInfo: [NSLocalizedDescriptionKey: "Remote settings conflict: \(validation.message)"])
+                }
+            } else {
+                throw NSError(domain: "SettingsImport", code: 2, userInfo: [NSLocalizedDescriptionKey: "Invalid remote settings in \(source)"])
+            }
+        }
+
+        if let alarms = settings.alarms {
+            LogManager.shared.log(category: .general, message: "Attempting to import alarm settings: \(alarms.alarms.count) alarms")
+            alarms.applyToStorage()
+            importedComponents.append("Alarm settings (\(alarms.alarms.count) alarms)")
+            LogManager.shared.log(category: .general, message: "Alarm settings imported from \(source) (version: \(alarms.version), \(alarms.alarms.count) alarms)")
+        }
+
+        // Update the success message with what was imported
+        if !importedComponents.isEmpty {
+            let componentsList = importedComponents.joined(separator: ", ")
+            qrCodeErrorMessage = "Successfully imported: \(componentsList)"
+        }
+    }
+
+    func generateQRCodeForExport() -> String? {
+        let settings: CombinedSettingsExport?
+
+        switch exportType {
+        case .nightscout:
+            let nightscoutSettings = NightscoutSettingsExport.fromCurrentStorage()
+            if !nightscoutSettings.hasValidSettings() {
+                qrCodeErrorMessage = "Please configure your Nightscout settings first (URL and Token)"
+                return nil
+            }
+            settings = CombinedSettingsExport(
+                nightscout: nightscoutSettings,
+                exportType: exportType.rawValue
+            )
+        case .remote:
+            let remoteSettings = RemoteSettingsExport.fromCurrentStorage()
+            if !remoteSettings.hasValidSettings() {
+                let currentRemoteType = Storage.shared.remoteType.value
+                if currentRemoteType == .none {
+                    qrCodeErrorMessage = "Please configure your Remote settings first (select a remote type and configure required fields)"
+                } else {
+                    qrCodeErrorMessage = "Please complete your Remote settings configuration (check required fields for \(currentRemoteType.rawValue))"
+                }
+                return nil
+            }
+            settings = CombinedSettingsExport(
+                remote: remoteSettings,
+                exportType: exportType.rawValue
+            )
+        case .alarms:
+            let alarmSettings = AlarmSettingsExport.fromCurrentStorage()
+            LogManager.shared.log(category: .general, message: "Generating alarm export: \(alarmSettings.alarms.count) alarms")
+            if !alarmSettings.hasValidSettings() {
+                qrCodeErrorMessage = "Please configure your Alarm settings first"
+                return nil
+            }
+            settings = CombinedSettingsExport(
+                alarms: alarmSettings,
+                exportType: exportType.rawValue
+            )
+        }
+
+        return settings?.encodeToJSON()
+    }
+
+    func showAlarmSelection() {
+        exportType = .alarms
+        isShowingAlarmSelection = true
+    }
+
+    func exportSelectedAlarms(_ selectedAlarms: [Alarm]) {
+        let settings = AlarmSettingsExport.fromSelectedAlarms(selectedAlarms)
+        if let qrString = settings.encodeToJSON() {
+            qrCodeString = qrString
+            isShowingQRCodeDisplay = true
+
+            // Track which alarms were exported
+            let exportedIds = Set(selectedAlarms.map { $0.id })
+            exportedAlarmIds.formUnion(exportedIds)
+        }
+        isShowingAlarmSelection = false
+    }
+
+    func cancelAlarmSelection() {
+        isShowingAlarmSelection = false
+    }
+
+    func resetExportedAlarms() {
+        exportedAlarmIds.removeAll()
+    }
+
+    // MARK: - iCloud Methods
+
+    /// Returns the iCloud container URL for persistent storage (survives app deletion)
+    /// Falls back to local Documents if iCloud is not available
+    private func getICloudContainerURL() -> URL? {
+        if let iCloudURL = FileManager.default.url(forUbiquityContainerIdentifier: nil) {
+            let documentsURL = iCloudURL.appendingPathComponent("Documents")
+            if !FileManager.default.fileExists(atPath: documentsURL.path) {
+                try? FileManager.default.createDirectory(at: documentsURL, withIntermediateDirectories: true)
+            }
+            return documentsURL
+        }
+        return nil
+    }
+
+    func exportToiCloud() {
+        // Create a comprehensive settings export for iCloud
+        let nightscoutSettings = NightscoutSettingsExport.fromCurrentStorage()
+        let dexcomSettings = DexcomSettingsExport.fromCurrentStorage()
+        let remoteSettings = RemoteSettingsExport.fromCurrentStorage()
+        let alarmSettings = AlarmSettingsExport.fromCurrentStorage()
+
+        let allSettings = CombinedSettingsExport(
+            nightscout: nightscoutSettings,
+            dexcom: dexcomSettings,
+            remote: remoteSettings,
+            alarms: alarmSettings,
+            exportType: "All Settings"
+        )
+
+        guard let jsonData = allSettings.encodeToJSON()?.data(using: .utf8) else {
+            qrCodeErrorMessage = "Failed to prepare settings for iCloud export"
+            return
+        }
+
+        guard let iCloudDocumentsPath = getICloudContainerURL() else {
+            qrCodeErrorMessage = "iCloud is not available. Please sign in to iCloud in Settings and enable iCloud Drive."
+            LogManager.shared.log(category: .general, message: "iCloud ubiquity container not available")
+            return
+        }
+
+        let fileName = "\(AppConstants.appInstanceId)Settings.json"
+        let iCloudPath = iCloudDocumentsPath.appendingPathComponent(fileName)
+
+        LogManager.shared.log(category: .general, message: "Attempting to export settings to iCloud")
+        LogManager.shared.log(category: .general, message: "iCloud ubiquity container path: \(iCloudDocumentsPath.path)")
+        LogManager.shared.log(category: .general, message: "Saving settings file to: \(iCloudPath.path)")
+
+        do {
+            try jsonData.write(to: iCloudPath)
+
+            // Build export details for the success alert - show what's being exported
+            var details: [String] = []
+
+            // Nightscout - show if URL is configured
+            if !nightscoutSettings.url.isEmpty {
+                details.append("Nightscout: \(nightscoutSettings.url)")
+            }
+
+            // Dexcom - show if username is configured
+            if !dexcomSettings.userName.isEmpty {
+                details.append("Dexcom: \(dexcomSettings.userName)")
+            }
+
+            // Remote - only show if a remote type is actually configured (not "none")
+            if remoteSettings.remoteType != .none {
+                details.append("Remote: \(remoteSettings.remoteType.rawValue)")
+            }
+
+            // Alarms - show if any alarms exist
+            if !alarmSettings.alarms.isEmpty {
+                details.append("Alarms: \(alarmSettings.alarms.count) alarm(s)")
+            }
+
+            exportSuccessDetails = details
+            exportSuccessMessage = "Settings saved to iCloud"
+            showExportSuccessAlert = true
+            qrCodeErrorMessage = "" // Clear any previous error
+
+            LogManager.shared.log(category: .general, message: "All settings exported to iCloud successfully")
+        } catch {
+            qrCodeErrorMessage = "Failed to export to iCloud: \(error.localizedDescription)"
+        }
+    }
+
+    func importFromiCloud() {
+        // Try to read from iCloud ubiquity container (persists after app deletion)
+        guard let iCloudDocumentsPath = getICloudContainerURL() else {
+            importNotFoundMessage = "iCloud is not available.\n\nPlease sign in to iCloud in Settings and enable iCloud Drive."
+            showImportNotFoundAlert = true
+            LogManager.shared.log(category: .general, message: "iCloud ubiquity container not available for import")
+            return
+        }
+
+        let fileName = "\(AppConstants.appInstanceId)Settings.json"
+        let iCloudPath = iCloudDocumentsPath.appendingPathComponent(fileName)
+
+        LogManager.shared.log(category: .general, message: "Attempting to import settings from iCloud")
+        LogManager.shared.log(category: .general, message: "iCloud ubiquity container path: \(iCloudDocumentsPath.path)")
+        LogManager.shared.log(category: .general, message: "Looking for settings file: \(iCloudPath.path)")
+
+        guard FileManager.default.fileExists(atPath: iCloudPath.path) else {
+            LogManager.shared.log(category: .general, message: "Settings file not found at: \(iCloudPath.path)")
+            importNotFoundMessage = "No settings file found in iCloud.\n\nMake sure you have previously exported settings to iCloud from this app."
+            showImportNotFoundAlert = true
+            return
+        }
+
+        do {
+            LogManager.shared.log(category: .general, message: "Settings file found, attempting to read data")
+
+            let data = try Data(contentsOf: iCloudPath)
+            guard let settings = SettingsMigrationManager.migrateSettings(data) else {
+                qrCodeErrorMessage = "Failed to decode settings from iCloud"
+                return
+            }
+
+            // Check version compatibility
+            let currentVersion = AppVersionManager().version()
+            if !SettingsMigrationManager.isCompatibleVersion(settings.appVersion) {
+                qrCodeErrorMessage = SettingsMigrationManager.getCompatibilityMessage(for: settings.appVersion)
+                // Still try to apply settings, but warn user
+            }
+
+            // Store settings and create preview for confirmation
+            pendingImportSettings = settings
+            pendingImportSource = "iCloud"
+            createImportPreview(from: settings)
+
+        } catch {
+            let currentVersion = AppVersionManager().version()
+            qrCodeErrorMessage = "iCloud import failed. This might be due to a version change (current: \(currentVersion)). Please try exporting settings to iCloud again."
+            LogManager.shared.log(category: .general, message: "iCloud import failed: \(error.localizedDescription)")
+        }
+    }
+
+    private func createImportPreview(from settings: CombinedSettingsExport) {
+        let nightscoutURL = settings.nightscout?.url.isEmpty == false ? settings.nightscout?.url : nil
+        let dexcomUsername = settings.dexcom?.userName.isEmpty == false ? settings.dexcom?.userName : nil
+        let remoteType = settings.remote?.remoteType != .none ? settings.remote?.remoteType.rawValue : nil
+        let alarmCount = settings.alarms?.alarms.count ?? 0
+        let alarmNames = settings.alarms?.alarms.map { $0.name } ?? []
+
+        // Check if any settings are actually present
+        let hasAnySettings = (nightscoutURL != nil && !nightscoutURL!.isEmpty) ||
+            (dexcomUsername != nil && !dexcomUsername!.isEmpty) ||
+            (remoteType != nil && !remoteType!.isEmpty && remoteType != "None") ||
+            alarmCount > 0
+
+        LogManager.shared.log(category: .general, message: "Import preview check - nightscoutURL: \(nightscoutURL ?? "nil"), dexcomUsername: \(dexcomUsername ?? "nil"), remoteType: \(remoteType ?? "nil"), alarmCount: \(alarmCount), hasAnySettings: \(hasAnySettings)")
+
+        if hasAnySettings {
+            LogManager.shared.log(category: .general, message: "Creating import preview with settings")
+            importPreview = ImportPreview(
+                nightscoutURL: nightscoutURL,
+                dexcomUsername: dexcomUsername,
+                remoteType: remoteType,
+                alarmCount: alarmCount,
+                alarmNames: alarmNames
+            )
+            LogManager.shared.log(category: .general, message: "Created importPreview - nightscoutURL: \(importPreview?.nightscoutURL ?? "nil"), remoteType: \(importPreview?.remoteType ?? "nil"), alarmCount: \(importPreview?.alarmCount ?? 0)")
+            showImportConfirmation = true
+            LogManager.shared.log(category: .general, message: "Set showImportConfirmation = true")
+        } else {
+            LogManager.shared.log(category: .general, message: "No settings found, clearing import data")
+            // No settings found, show alert and clear any pending data
+            importNotFoundMessage = "The settings file exists but contains no valid settings to import."
+            showImportNotFoundAlert = true
+            showImportConfirmation = false
+            importPreview = nil
+            pendingImportSettings = nil
+            pendingImportSource = ""
+            LogManager.shared.log(category: .general, message: "Set showImportConfirmation = false")
+        }
+    }
+
+    func confirmImport() {
+        guard let settings = pendingImportSettings else { return }
+
+        do {
+            try applyImportedSettings(settings, source: pendingImportSource)
+        } catch {
+            qrCodeErrorMessage = "Import failed: \(error.localizedDescription)"
+        }
+
+        // Reset confirmation state
+        showImportConfirmation = false
+        importPreview = nil
+        pendingImportSettings = nil
+        pendingImportSource = ""
+    }
+
+    func cancelImport() {
+        showImportConfirmation = false
+        importPreview = nil
+        pendingImportSettings = nil
+        pendingImportSource = ""
+    }
+}

+ 92 - 0
LoopFollow/Settings/ImportExport/SettingsMigrationManager.swift

@@ -0,0 +1,92 @@
+// LoopFollow
+// SettingsMigrationManager.swift
+
+import Foundation
+
+class SettingsMigrationManager {
+    // MARK: - Current Version
+
+    static let currentVersion = "1.0"
+
+    // MARK: - Migration Methods
+
+    static func migrateSettings(_ data: Data) -> CombinedSettingsExport? {
+        // Try to decode with the current version
+        do {
+            let currentSettings = try JSONDecoder().decode(CombinedSettingsExport.self, from: data)
+            print("✅ Successfully decoded CombinedSettingsExport")
+            return currentSettings
+        } catch {
+            print("❌ Failed to decode CombinedSettingsExport: \(error)")
+            print("❌ Error details: \(error.localizedDescription)")
+
+            // Try to decode as individual components
+            return tryDecodeIndividualComponents(data)
+        }
+    }
+
+    private static func tryDecodeIndividualComponents(_ data: Data) -> CombinedSettingsExport? {
+        // Try to decode as AlarmSettingsExport
+        if let alarmSettings = try? JSONDecoder().decode(AlarmSettingsExport.self, from: data) {
+            print("✅ Successfully decoded as AlarmSettingsExport")
+            return CombinedSettingsExport(
+                alarms: alarmSettings,
+                exportType: "Alarm Settings"
+            )
+        }
+
+        // Try to decode as NightscoutSettingsExport
+        if let nightscoutSettings = try? JSONDecoder().decode(NightscoutSettingsExport.self, from: data) {
+            print("✅ Successfully decoded as NightscoutSettingsExport")
+            return CombinedSettingsExport(
+                nightscout: nightscoutSettings,
+                exportType: "Nightscout Settings"
+            )
+        }
+
+        // Try to decode as DexcomSettingsExport
+        if let dexcomSettings = try? JSONDecoder().decode(DexcomSettingsExport.self, from: data) {
+            print("✅ Successfully decoded as DexcomSettingsExport")
+            return CombinedSettingsExport(
+                dexcom: dexcomSettings,
+                exportType: "Dexcom Settings"
+            )
+        }
+
+        // Try to decode as RemoteSettingsExport
+        if let remoteSettings = try? JSONDecoder().decode(RemoteSettingsExport.self, from: data) {
+            print("✅ Successfully decoded as RemoteSettingsExport")
+            return CombinedSettingsExport(
+                remote: remoteSettings,
+                exportType: "Remote Settings"
+            )
+        }
+
+        print("❌ Failed to decode as any known component")
+        return nil
+    }
+
+    // MARK: - Version Compatibility
+
+    static func isCompatibleVersion(_ version: String) -> Bool {
+        let currentVersionComponents = currentVersion.split(separator: ".").compactMap { Int($0) }
+        let importVersionComponents = version.split(separator: ".").compactMap { Int($0) }
+
+        // For now, accept any version (can be made more strict later)
+        return true
+    }
+
+    static func getCompatibilityMessage(for version: String) -> String {
+        return "Settings from version \(version) may not be fully compatible with current version \(currentVersion). Some features may not work as expected."
+    }
+
+    // MARK: - Error Handling
+
+    enum SettingsImportError: Error {
+        case unsupportedVersion(String)
+        case migrationFailed(String)
+        case corruptedData
+        case incompatibleAlarmFormat
+        case unknownError
+    }
+}

+ 8 - 6
LoopFollow/Settings/SettingsMenuView.swift

@@ -54,6 +54,12 @@ struct SettingsMenuView: View {
                         showingTabCustomization = true
                         showingTabCustomization = true
                     }
                     }
 
 
+                    NavigationRow(title: "Import/Export Settings",
+                                  icon: "square.and.arrow.down")
+                    {
+                        settingsPath.value.append(Sheet.importExport)
+                    }
+
                     if !nightscoutURL.value.isEmpty {
                     if !nightscoutURL.value.isEmpty {
                         NavigationRow(title: "Information Display Settings",
                         NavigationRow(title: "Information Display Settings",
                                       icon: "info.circle")
                                       icon: "info.circle")
@@ -66,12 +72,6 @@ struct SettingsMenuView: View {
                         {
                         {
                             settingsPath.value.append(Sheet.remote)
                             settingsPath.value.append(Sheet.remote)
                         }
                         }
-                    } else {
-                        NavigationRow(title: "Import Settings",
-                                      icon: "square.and.arrow.down")
-                        {
-                            settingsPath.value.append(Sheet.remote)
-                        }
                     }
                     }
                 }
                 }
 
 
@@ -279,6 +279,7 @@ private enum Sheet: Hashable, Identifiable {
     case infoDisplay
     case infoDisplay
     case alarmsList, alarmSettings
     case alarmsList, alarmSettings
     case remote
     case remote
+    case importExport
     case calendar, contact
     case calendar, contact
     case advanced
     case advanced
     case viewLog
     case viewLog
@@ -297,6 +298,7 @@ private enum Sheet: Hashable, Identifiable {
         case .alarmsList: AlarmListView()
         case .alarmsList: AlarmListView()
         case .alarmSettings: AlarmSettingsView()
         case .alarmSettings: AlarmSettingsView()
         case .remote: RemoteSettingsView(viewModel: .init())
         case .remote: RemoteSettingsView(viewModel: .init())
+        case .importExport: ImportExportSettingsView()
         case .calendar: CalendarSettingsView()
         case .calendar: CalendarSettingsView()
         case .contact: ContactSettingsView(viewModel: .init())
         case .contact: ContactSettingsView(viewModel: .init())
         case .advanced: AdvancedSettingsView(viewModel: .init())
         case .advanced: AdvancedSettingsView(viewModel: .init())

+ 380 - 2
LoopFollow/ViewControllers/MainViewController.swift

@@ -45,6 +45,10 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     var refreshScrollView: UIScrollView!
     var refreshScrollView: UIScrollView!
     var refreshControl: UIRefreshControl!
     var refreshControl: UIRefreshControl!
 
 
+    // Setup buttons for first-time configuration
+    private var setupNightscoutButton: UIButton!
+    private var setupDexcomButton: UIButton!
+
     let speechSynthesizer = AVSpeechSynthesizer()
     let speechSynthesizer = AVSpeechSynthesizer()
 
 
     // Variables for BG Charts
     // Variables for BG Charts
@@ -118,6 +122,16 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
 
 
     private var cancellables = Set<AnyCancellable>()
     private var cancellables = Set<AnyCancellable>()
 
 
+    // Loading state management
+    private var loadingOverlay: UIView?
+    private var isInitialLoad = true
+    private var loadingStates: [String: Bool] = [
+        "bg": false,
+        "profile": false,
+        "deviceStatus": false,
+    ]
+    private var loadingTimeoutTimer: Timer?
+
     override func viewDidLoad() {
     override func viewDidLoad() {
         super.viewDidLoad()
         super.viewDidLoad()
 
 
@@ -153,7 +167,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
         dexShare = ShareClient(username: shareUserName, password: sharePassword, shareServer: shareServer)
         dexShare = ShareClient(username: shareUserName, password: sharePassword, shareServer: shareServer)
 
 
         // setup show/hide small graph and stats
         // setup show/hide small graph and stats
-        BGChartFull.isHidden = !Storage.shared.showSmallGraph.value
+        updateGraphVisibility()
         statsView.isHidden = !Storage.shared.showStats.value
         statsView.isHidden = !Storage.shared.showStats.value
 
 
         BGChart.delegate = self
         BGChart.delegate = self
@@ -259,7 +273,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
         Storage.shared.showSmallGraph.$value
         Storage.shared.showSmallGraph.$value
             .receive(on: DispatchQueue.main)
             .receive(on: DispatchQueue.main)
             .sink { [weak self] _ in
             .sink { [weak self] _ in
-                self?.BGChartFull.isHidden = !Storage.shared.showSmallGraph.value
+                self?.updateGraphVisibility()
             }
             }
             .store(in: &cancellables)
             .store(in: &cancellables)
 
 
@@ -309,6 +323,28 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
             .receive(on: DispatchQueue.main)
             .receive(on: DispatchQueue.main)
             .sink { [weak self] _ in
             .sink { [weak self] _ in
                 self?.updateNightscoutTabState()
                 self?.updateNightscoutTabState()
+                self?.checkAndShowImportButtonIfNeeded()
+            }
+            .store(in: &cancellables)
+
+        Storage.shared.token.$value
+            .receive(on: DispatchQueue.main)
+            .sink { [weak self] _ in
+                self?.checkAndShowImportButtonIfNeeded()
+            }
+            .store(in: &cancellables)
+
+        Storage.shared.shareUserName.$value
+            .receive(on: DispatchQueue.main)
+            .sink { [weak self] _ in
+                self?.checkAndShowImportButtonIfNeeded()
+            }
+            .store(in: &cancellables)
+
+        Storage.shared.sharePassword.$value
+            .receive(on: DispatchQueue.main)
+            .sink { [weak self] _ in
+                self?.checkAndShowImportButtonIfNeeded()
             }
             }
             .store(in: &cancellables)
             .store(in: &cancellables)
 
 
@@ -361,6 +397,115 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
         setupTabBar()
         setupTabBar()
 
 
         speechSynthesizer.delegate = self
         speechSynthesizer.delegate = self
+
+        // Check configuration and show appropriate UI
+        if isDataSourceConfigured() {
+            // Data source configured - show loading overlay
+            setupLoadingState()
+            showLoadingOverlay()
+        } else {
+            // No data source - hide all data UI and show setup buttons
+            hideAllDataUI()
+            isInitialLoad = false
+        }
+
+        checkAndShowImportButtonIfNeeded()
+    }
+
+    // MARK: - Loading Overlay
+
+    private func isDataSourceConfigured() -> Bool {
+        let isNightscoutConfigured = !Storage.shared.url.value.isEmpty
+        let isDexcomConfigured = !Storage.shared.shareUserName.value.isEmpty && !Storage.shared.sharePassword.value.isEmpty
+        return isNightscoutConfigured || isDexcomConfigured
+    }
+
+    private func setupLoadingState() {
+        // If Nightscout is not enabled, mark profile and deviceStatus as loaded
+        // since we only need BG data from Dexcom Share
+        if !IsNightscoutEnabled() {
+            loadingStates["profile"] = true
+            loadingStates["deviceStatus"] = true
+        }
+    }
+
+    private func showLoadingOverlay() {
+        guard loadingOverlay == nil else { return }
+
+        // Hide all data UI while loading
+        hideAllDataUI()
+
+        let overlay = UIView(frame: view.bounds)
+        overlay.backgroundColor = UIColor.systemBackground
+        overlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+
+        let activityIndicator = UIActivityIndicatorView(style: .large)
+        activityIndicator.translatesAutoresizingMaskIntoConstraints = false
+        activityIndicator.startAnimating()
+
+        let loadingLabel = UILabel()
+        loadingLabel.translatesAutoresizingMaskIntoConstraints = false
+        loadingLabel.text = "Loading..."
+        loadingLabel.textAlignment = .center
+        loadingLabel.font = UIFont.systemFont(ofSize: 17, weight: .medium)
+        loadingLabel.textColor = UIColor.secondaryLabel
+
+        overlay.addSubview(activityIndicator)
+        overlay.addSubview(loadingLabel)
+
+        NSLayoutConstraint.activate([
+            activityIndicator.centerXAnchor.constraint(equalTo: overlay.centerXAnchor),
+            activityIndicator.centerYAnchor.constraint(equalTo: overlay.centerYAnchor, constant: -20),
+
+            loadingLabel.centerXAnchor.constraint(equalTo: overlay.centerXAnchor),
+            loadingLabel.topAnchor.constraint(equalTo: activityIndicator.bottomAnchor, constant: 16),
+        ])
+
+        view.addSubview(overlay)
+        loadingOverlay = overlay
+
+        // Set a timeout to hide the loading overlay if data takes too long
+        loadingTimeoutTimer = Timer.scheduledTimer(withTimeInterval: 15.0, repeats: false) { [weak self] _ in
+            guard let self = self else { return }
+            if self.isInitialLoad {
+                LogManager.shared.log(category: .general, message: "Loading timeout reached, hiding overlay")
+                self.isInitialLoad = false
+                self.hideLoadingOverlay()
+            }
+        }
+    }
+
+    private func hideLoadingOverlay() {
+        guard let overlay = loadingOverlay else { return }
+
+        // Cancel the timeout timer
+        loadingTimeoutTimer?.invalidate()
+        loadingTimeoutTimer = nil
+
+        // Show all data UI now that loading is complete
+        showAllDataUI()
+
+        UIView.animate(withDuration: 0.3, animations: {
+            overlay.alpha = 0
+        }, completion: { _ in
+            overlay.removeFromSuperview()
+            self.loadingOverlay = nil
+        })
+    }
+
+    func markDataLoaded(_ key: String) {
+        guard isInitialLoad else { return }
+
+        loadingStates[key] = true
+
+        // Check if all critical data is loaded
+        let allLoaded = loadingStates.values.allSatisfy { $0 }
+        if allLoaded {
+            isInitialLoad = false
+            DispatchQueue.main.async {
+                self.hideLoadingOverlay()
+            }
+        }
     }
     }
 
 
     private func setupTabBar() {
     private func setupTabBar() {
@@ -694,6 +839,10 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     }
     }
 
 
     func showHideNSDetails() {
     func showHideNSDetails() {
+        if isInitialLoad || !isDataSourceConfigured() {
+            return
+        }
+
         var isHidden = false
         var isHidden = false
         if !IsNightscoutEnabled() {
         if !IsNightscoutEnabled() {
             isHidden = true
             isHidden = true
@@ -945,6 +1094,235 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
         Storage.shared.infoSort.value = sortArray
         Storage.shared.infoSort.value = sortArray
         Storage.shared.infoVisible.value = visibleArray
         Storage.shared.infoVisible.value = visibleArray
     }
     }
+
+    // MARK: - First Time Setup
+
+    private func checkAndShowImportButtonIfNeeded() {
+        // Check if this is first-time setup (no data source configured)
+        let isFirstTimeSetup = !isDataSourceConfigured()
+
+        if isFirstTimeSetup {
+            setupFirstTimeButtons()
+            hideAllDataUI()
+            // Hide loading overlay if it's showing and mark as not loading
+            if loadingOverlay != nil {
+                isInitialLoad = false
+                hideLoadingOverlay()
+            }
+        } else {
+            hideFirstTimeButtons()
+            // Only show data UI if we're not in initial loading state
+            if !isInitialLoad || loadingOverlay == nil {
+                showAllDataUI()
+            }
+        }
+    }
+
+    private func setupFirstTimeButtons() {
+        // Create Setup Nightscout button
+        if setupNightscoutButton == nil {
+            setupNightscoutButton = UIButton(type: .system)
+            setupNightscoutButton.setTitle("Setup Nightscout", for: .normal)
+            setupNightscoutButton.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .medium)
+            setupNightscoutButton.backgroundColor = UIColor.systemBlue
+            setupNightscoutButton.setTitleColor(.white, for: .normal)
+            setupNightscoutButton.layer.cornerRadius = 12
+            setupNightscoutButton.layer.shadowColor = UIColor.black.cgColor
+            setupNightscoutButton.layer.shadowOffset = CGSize(width: 0, height: 2)
+            setupNightscoutButton.layer.shadowOpacity = 0.3
+            setupNightscoutButton.layer.shadowRadius = 4
+            setupNightscoutButton.addTarget(self, action: #selector(setupNightscoutTapped), for: .touchUpInside)
+
+            view.addSubview(setupNightscoutButton)
+            setupNightscoutButton.translatesAutoresizingMaskIntoConstraints = false
+
+            NSLayoutConstraint.activate([
+                setupNightscoutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
+                setupNightscoutButton.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -30),
+                setupNightscoutButton.widthAnchor.constraint(equalToConstant: 200),
+                setupNightscoutButton.heightAnchor.constraint(equalToConstant: 50),
+            ])
+        }
+
+        // Create Setup Dexcom Share button
+        if setupDexcomButton == nil {
+            setupDexcomButton = UIButton(type: .system)
+            setupDexcomButton.setTitle("Setup Dexcom Share", for: .normal)
+            setupDexcomButton.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .medium)
+            setupDexcomButton.backgroundColor = UIColor.systemGreen
+            setupDexcomButton.setTitleColor(.white, for: .normal)
+            setupDexcomButton.layer.cornerRadius = 12
+            setupDexcomButton.layer.shadowColor = UIColor.black.cgColor
+            setupDexcomButton.layer.shadowOffset = CGSize(width: 0, height: 2)
+            setupDexcomButton.layer.shadowOpacity = 0.3
+            setupDexcomButton.layer.shadowRadius = 4
+            setupDexcomButton.addTarget(self, action: #selector(setupDexcomTapped), for: .touchUpInside)
+
+            view.addSubview(setupDexcomButton)
+            setupDexcomButton.translatesAutoresizingMaskIntoConstraints = false
+
+            NSLayoutConstraint.activate([
+                setupDexcomButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
+                setupDexcomButton.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 30),
+                setupDexcomButton.widthAnchor.constraint(equalToConstant: 200),
+                setupDexcomButton.heightAnchor.constraint(equalToConstant: 50),
+            ])
+        }
+
+        setupNightscoutButton.isHidden = false
+        setupDexcomButton.isHidden = false
+    }
+
+    private func hideFirstTimeButtons() {
+        setupNightscoutButton?.isHidden = true
+        setupDexcomButton?.isHidden = true
+    }
+
+    @objc private func setupNightscoutTapped() {
+        let nightscoutSettingsView = NightscoutSettingsView(viewModel: .init())
+        let hostingController = UIHostingController(rootView: nightscoutSettingsView)
+        let navController = UINavigationController(rootViewController: hostingController)
+
+        // Apply dark mode if needed
+        if Storage.shared.forceDarkMode.value {
+            hostingController.overrideUserInterfaceStyle = .dark
+            navController.overrideUserInterfaceStyle = .dark
+        }
+
+        // Add a Done button
+        hostingController.navigationItem.rightBarButtonItem = UIBarButtonItem(
+            barButtonSystemItem: .done,
+            target: self,
+            action: #selector(dismissModal)
+        )
+
+        navController.modalPresentationStyle = .pageSheet
+        present(navController, animated: true)
+    }
+
+    @objc private func setupDexcomTapped() {
+        let dexcomSettingsView = DexcomSettingsView(viewModel: .init())
+        let hostingController = UIHostingController(rootView: dexcomSettingsView)
+        let navController = UINavigationController(rootViewController: hostingController)
+
+        // Apply dark mode if needed
+        if Storage.shared.forceDarkMode.value {
+            hostingController.overrideUserInterfaceStyle = .dark
+            navController.overrideUserInterfaceStyle = .dark
+        }
+
+        // Add a Done button
+        hostingController.navigationItem.rightBarButtonItem = UIBarButtonItem(
+            barButtonSystemItem: .done,
+            target: self,
+            action: #selector(dismissModal)
+        )
+
+        navController.modalPresentationStyle = .pageSheet
+        present(navController, animated: true)
+    }
+
+    private func hideGraphs() {
+        BGChart.isHidden = true
+        BGChartFull.isHidden = true
+    }
+
+    private func showGraphs() {
+        updateGraphVisibility()
+    }
+
+    private func hideAllDataUI() {
+        // Hide graphs
+        BGChart.isHidden = true
+        BGChartFull.isHidden = true
+
+        // Hide BG display elements
+        BGText.isHidden = true
+        DeltaText.isHidden = true
+        DirectionText.isHidden = true
+        MinAgoText.isHidden = true
+        serverText.isHidden = true
+
+        // Hide info table and stats
+        infoTable.isHidden = true
+        statsView.isHidden = true
+
+        // Hide loop status and prediction
+        LoopStatusLabel.isHidden = true
+        PredictionLabel.isHidden = true
+    }
+
+    private func showAllDataUI() {
+        // Show BG display elements
+        BGText.isHidden = false
+        DeltaText.isHidden = false
+        DirectionText.isHidden = false
+        MinAgoText.isHidden = false
+        serverText.isHidden = false
+
+        // Show graphs based on settings
+        updateGraphVisibility()
+
+        // Show/hide info table and stats based on user settings
+        let isNightscoutEnabled = IsNightscoutEnabled()
+        if isNightscoutEnabled {
+            infoTable.isHidden = Storage.shared.hideInfoTable.value
+            LoopStatusLabel.isHidden = false
+            PredictionLabel.isHidden = IsNotLooping
+        } else {
+            infoTable.isHidden = true
+            LoopStatusLabel.isHidden = true
+            PredictionLabel.isHidden = true
+        }
+
+        statsView.isHidden = !Storage.shared.showStats.value
+    }
+
+    private func updateGraphVisibility() {
+        let isFirstTimeSetup = !isDataSourceConfigured()
+
+        if isFirstTimeSetup {
+            BGChart.isHidden = true
+            BGChartFull.isHidden = true
+        } else {
+            BGChart.isHidden = false
+            BGChartFull.isHidden = !Storage.shared.showSmallGraph.value
+        }
+    }
+
+    @objc private func importSettingsButtonTapped() {
+        presentImportSettingsView()
+    }
+
+    private func presentImportSettingsView() {
+        let importExportView = ImportExportSettingsView()
+        let hostingController = UIHostingController(rootView: importExportView)
+        hostingController.modalPresentationStyle = .pageSheet
+
+        present(hostingController, animated: true)
+    }
+
+    @objc private func dismissModal() {
+        dismiss(animated: true) { [weak self] in
+            guard let self = self else { return }
+
+            // Check if user just configured a data source
+            if self.isDataSourceConfigured(), self.loadingOverlay == nil {
+                // Reset loading states for fresh load
+                self.loadingStates = [
+                    "bg": false,
+                    "profile": false,
+                    "deviceStatus": false,
+                ]
+                self.isInitialLoad = true
+
+                // Show loading overlay and trigger refresh
+                self.setupLoadingState()
+                self.showLoadingOverlay()
+                self.refresh()
+            }
+        }
+    }
 }
 }
 
 
 extension MainViewController: AVSpeechSynthesizerDelegate {
 extension MainViewController: AVSpeechSynthesizerDelegate {