@@ -122,6 +122,16 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
122122
123123 private var cancellables = Set < AnyCancellable > ( )
124124
125+ // Loading state management
126+ private var loadingOverlay : UIView ?
127+ private var isInitialLoad = true
128+ private var loadingStates : [ String : Bool ] = [
129+ " bg " : false ,
130+ " profile " : false ,
131+ " deviceStatus " : false ,
132+ ]
133+ private var loadingTimeoutTimer : Timer ?
134+
125135 override func viewDidLoad( ) {
126136 super. viewDidLoad ( )
127137
@@ -388,10 +398,116 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
388398
389399 speechSynthesizer. delegate = self
390400
391- // Check if this is first-time setup and show import button
401+ // Check configuration and show appropriate UI
402+ if isDataSourceConfigured ( ) {
403+ // Data source configured - show loading overlay
404+ setupLoadingState ( )
405+ showLoadingOverlay ( )
406+ } else {
407+ // No data source - hide all data UI and show setup buttons
408+ hideAllDataUI ( )
409+ isInitialLoad = false
410+ }
411+
392412 checkAndShowImportButtonIfNeeded ( )
393413 }
394414
415+ // MARK: - Loading Overlay
416+
417+ private func isDataSourceConfigured( ) -> Bool {
418+ let isNightscoutConfigured = !Storage. shared. url. value. isEmpty
419+ let isDexcomConfigured = !Storage. shared. shareUserName. value. isEmpty && !Storage. shared. sharePassword. value. isEmpty
420+ return isNightscoutConfigured || isDexcomConfigured
421+ }
422+
423+ private func setupLoadingState( ) {
424+ // If Nightscout is not enabled, mark profile and deviceStatus as loaded
425+ // since we only need BG data from Dexcom Share
426+ if !IsNightscoutEnabled( ) {
427+ loadingStates [ " profile " ] = true
428+ loadingStates [ " deviceStatus " ] = true
429+ }
430+ }
431+
432+ private func showLoadingOverlay( ) {
433+ guard loadingOverlay == nil else { return }
434+
435+ // Hide all data UI while loading
436+ hideAllDataUI ( )
437+
438+ let overlay = UIView ( frame: view. bounds)
439+ overlay. backgroundColor = UIColor . systemBackground
440+ overlay. autoresizingMask = [ . flexibleWidth, . flexibleHeight]
441+
442+ let activityIndicator = UIActivityIndicatorView ( style: . large)
443+ activityIndicator. translatesAutoresizingMaskIntoConstraints = false
444+ activityIndicator. startAnimating ( )
445+
446+ let loadingLabel = UILabel ( )
447+ loadingLabel. translatesAutoresizingMaskIntoConstraints = false
448+ loadingLabel. text = " Loading... "
449+ loadingLabel. textAlignment = . center
450+ loadingLabel. font = UIFont . systemFont ( ofSize: 17 , weight: . medium)
451+ loadingLabel. textColor = UIColor . secondaryLabel
452+
453+ overlay. addSubview ( activityIndicator)
454+ overlay. addSubview ( loadingLabel)
455+
456+ NSLayoutConstraint . activate ( [
457+ activityIndicator. centerXAnchor. constraint ( equalTo: overlay. centerXAnchor) ,
458+ activityIndicator. centerYAnchor. constraint ( equalTo: overlay. centerYAnchor, constant: - 20 ) ,
459+
460+ loadingLabel. centerXAnchor. constraint ( equalTo: overlay. centerXAnchor) ,
461+ loadingLabel. topAnchor. constraint ( equalTo: activityIndicator. bottomAnchor, constant: 16 ) ,
462+ ] )
463+
464+ view. addSubview ( overlay)
465+ loadingOverlay = overlay
466+
467+ // Set a timeout to hide the loading overlay if data takes too long
468+ loadingTimeoutTimer = Timer . scheduledTimer ( withTimeInterval: 15.0 , repeats: false ) { [ weak self] _ in
469+ guard let self = self else { return }
470+ if self . isInitialLoad {
471+ LogManager . shared. log ( category: . general, message: " Loading timeout reached, hiding overlay " )
472+ self . isInitialLoad = false
473+ self . hideLoadingOverlay ( )
474+ }
475+ }
476+ }
477+
478+ private func hideLoadingOverlay( ) {
479+ guard let overlay = loadingOverlay else { return }
480+
481+ // Cancel the timeout timer
482+ loadingTimeoutTimer? . invalidate ( )
483+ loadingTimeoutTimer = nil
484+
485+ // Show all data UI now that loading is complete
486+ showAllDataUI ( )
487+
488+ UIView . animate ( withDuration: 0.3 , animations: {
489+ overlay. alpha = 0
490+ } , completion: { _ in
491+ overlay. removeFromSuperview ( )
492+ self . loadingOverlay = nil
493+ } )
494+ }
495+
496+ func markDataLoaded( _ key: String ) {
497+ guard isInitialLoad else { return }
498+
499+ loadingStates [ key] = true
500+
501+ // Check if all critical data is loaded
502+ let allLoaded = loadingStates. values. allSatisfy { $0 }
503+ if allLoaded {
504+ isInitialLoad = false
505+ DispatchQueue . main. async {
506+ self . hideLoadingOverlay ( )
507+ }
508+ }
509+ }
510+
395511 private func setupTabBar( ) {
396512 guard let tabBarController = tabBarController else { return }
397513
@@ -723,6 +839,10 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
723839 }
724840
725841 func showHideNSDetails( ) {
842+ if isInitialLoad || !isDataSourceConfigured( ) {
843+ return
844+ }
845+
726846 var isHidden = false
727847 if !IsNightscoutEnabled( ) {
728848 isHidden = true
@@ -978,17 +1098,23 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
9781098 // MARK: - First Time Setup
9791099
9801100 private func checkAndShowImportButtonIfNeeded( ) {
981- // Check if this is first-time setup (no Nightscout URL configured AND no Dexcom configured)
982- let isNightscoutConfigured = !Storage. shared. url. value. isEmpty && !Storage. shared. token. value. isEmpty
983- let isDexcomConfigured = !Storage. shared. shareUserName. value. isEmpty && !Storage. shared. sharePassword. value. isEmpty
984- let isFirstTimeSetup = !isNightscoutConfigured && !isDexcomConfigured
1101+ // Check if this is first-time setup (no data source configured)
1102+ let isFirstTimeSetup = !isDataSourceConfigured( )
9851103
9861104 if isFirstTimeSetup {
9871105 setupFirstTimeButtons ( )
988- hideGraphs ( )
1106+ hideAllDataUI ( )
1107+ // Hide loading overlay if it's showing and mark as not loading
1108+ if loadingOverlay != nil {
1109+ isInitialLoad = false
1110+ hideLoadingOverlay ( )
1111+ }
9891112 } else {
9901113 hideFirstTimeButtons ( )
991- showGraphs ( )
1114+ // Only show data UI if we're not in initial loading state
1115+ if !isInitialLoad || loadingOverlay == nil {
1116+ showAllDataUI ( )
1117+ }
9921118 }
9931119 }
9941120
@@ -1105,8 +1231,55 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
11051231 updateGraphVisibility ( )
11061232 }
11071233
1234+ private func hideAllDataUI( ) {
1235+ // Hide graphs
1236+ BGChart . isHidden = true
1237+ BGChartFull . isHidden = true
1238+
1239+ // Hide BG display elements
1240+ BGText . isHidden = true
1241+ DeltaText . isHidden = true
1242+ DirectionText . isHidden = true
1243+ MinAgoText . isHidden = true
1244+ serverText. isHidden = true
1245+
1246+ // Hide info table and stats
1247+ infoTable. isHidden = true
1248+ statsView. isHidden = true
1249+
1250+ // Hide loop status and prediction
1251+ LoopStatusLabel . isHidden = true
1252+ PredictionLabel . isHidden = true
1253+ }
1254+
1255+ private func showAllDataUI( ) {
1256+ // Show BG display elements
1257+ BGText . isHidden = false
1258+ DeltaText . isHidden = false
1259+ DirectionText . isHidden = false
1260+ MinAgoText . isHidden = false
1261+ serverText. isHidden = false
1262+
1263+ // Show graphs based on settings
1264+ updateGraphVisibility ( )
1265+
1266+ // Show/hide info table and stats based on user settings
1267+ let isNightscoutEnabled = IsNightscoutEnabled ( )
1268+ if isNightscoutEnabled {
1269+ infoTable. isHidden = Storage . shared. hideInfoTable. value
1270+ LoopStatusLabel . isHidden = false
1271+ PredictionLabel . isHidden = IsNotLooping
1272+ } else {
1273+ infoTable. isHidden = true
1274+ LoopStatusLabel . isHidden = true
1275+ PredictionLabel . isHidden = true
1276+ }
1277+
1278+ statsView. isHidden = !Storage. shared. showStats. value
1279+ }
1280+
11081281 private func updateGraphVisibility( ) {
1109- let isFirstTimeSetup = Storage . shared . url . value . isEmpty && Storage . shared . token . value . isEmpty
1282+ let isFirstTimeSetup = !isDataSourceConfigured ( )
11101283
11111284 if isFirstTimeSetup {
11121285 BGChart . isHidden = true
@@ -1130,7 +1303,25 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
11301303 }
11311304
11321305 @objc private func dismissModal( ) {
1133- dismiss ( animated: true )
1306+ dismiss ( animated: true ) { [ weak self] in
1307+ guard let self = self else { return }
1308+
1309+ // Check if user just configured a data source
1310+ if self . isDataSourceConfigured ( ) , self . loadingOverlay == nil {
1311+ // Reset loading states for fresh load
1312+ self . loadingStates = [
1313+ " bg " : false ,
1314+ " profile " : false ,
1315+ " deviceStatus " : false ,
1316+ ]
1317+ self . isInitialLoad = true
1318+
1319+ // Show loading overlay and trigger refresh
1320+ self . setupLoadingState ( )
1321+ self . showLoadingOverlay ( )
1322+ self . refresh ( )
1323+ }
1324+ }
11341325 }
11351326}
11361327
0 commit comments