using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using LSFE.MobApp.Contracts; using System.Collections.ObjectModel; using System.Diagnostics; using System.Windows.Input; using System.Net.NetworkInformation; namespace LSFE.MobApp.ViewModels.Utility { public partial class UtilitiesVM : ObservableObject { private readonly ISyncService _syncService; private readonly IRepository _mrRawPlanRepository; private CancellationTokenSource _networkCheckCts; [ObservableProperty] private bool _isSyncing; [ObservableProperty] private string _syncStatus = "Ready to sync"; [ObservableProperty] private DateTime _lastSyncTime; // Network status properties [ObservableProperty] private bool _isConnected; [ObservableProperty] private string _networkStatusText = "Checking..."; [ObservableProperty] private Color _networkStatusColor = Color.FromArgb("#6B7280"); [ObservableProperty] private string _networkIcon = "📶"; [ObservableProperty] private long _latencyMs = -1; [ObservableProperty] private bool _isCheckingNetwork; public ICommand ViewCrashLogCommand { get; } public ObservableCollection SyncItems { get; } = new ObservableCollection(); public UtilitiesVM( ISyncService syncService, IRepository mrRawPlanRepository) { _syncService = syncService; _mrRawPlanRepository = mrRawPlanRepository; ViewCrashLogCommand = new Command(async (doctor) => await ViewCrashLogAsync()); InitializeSyncItems(); StartNetworkMonitoring(); } private void InitializeSyncItems() { SyncItems.Clear(); SyncItems.Add(new SyncItem { Title = "Attendance logs", Description = "Sync attendance records", Icon = "clock_outline.png", IconColor = Color.FromArgb("#6366F1"), SyncAction = SyncAttendanceAsync }); SyncItems.Add(new SyncItem { Title = "Coverage Plan", Description = "Sync today's coverage plans", Icon = "calendar_outline.png", IconColor = Color.FromArgb("#10B981"), SyncAction = SyncCoveragePlanAsync }); SyncItems.Add(new SyncItem { Title = "Actual Coverage", Description = "Sync doctor coverage data", Icon = "doctor_outline.png", IconColor = Color.FromArgb("#8B5CF6"), SyncAction = SyncActualCoverageAsync }); Debug.WriteLine($"[ViewModel] Initialized {SyncItems.Count} sync items"); } #region Network Monitoring private void StartNetworkMonitoring() { Connectivity.Current.ConnectivityChanged += OnConnectivityChanged; CheckNetworkStatus(); } private void OnConnectivityChanged(object sender, ConnectivityChangedEventArgs e) { CheckNetworkStatus(); } [RelayCommand] private void RefreshNetwork() { CheckNetworkStatus(); } private async void CheckNetworkStatus() { // Cancel any previous check _networkCheckCts?.Cancel(); _networkCheckCts = new CancellationTokenSource(); IsCheckingNetwork = true; try { var access = Connectivity.Current.NetworkAccess; if (access != NetworkAccess.Internet) { IsConnected = false; NetworkStatusText = "No Internet"; NetworkStatusColor = Color.FromArgb("#EF4444"); NetworkIcon = "📵"; LatencyMs = -1; return; } IsConnected = true; // Measure latency by pinging Google's DNS var latency = await MeasureLatencyAsync(_networkCheckCts.Token); if (_networkCheckCts.Token.IsCancellationRequested) return; LatencyMs = latency; UpdateNetworkIndicator(latency); } catch (OperationCanceledException) { // Cancelled, ignore } catch (Exception ex) { Debug.WriteLine($"[Network] Check failed: {ex.Message}"); IsConnected = false; NetworkStatusText = "Check failed"; NetworkStatusColor = Color.FromArgb("#EF4444"); NetworkIcon = "⚠️"; } finally { IsCheckingNetwork = false; } } private async Task MeasureLatencyAsync(CancellationToken ct) { try { var ping = new Ping(); // Ping Google DNS - reliable and fast var reply = await ping.SendPingAsync("8.8.8.8", 5000); if (ct.IsCancellationRequested) return -1; if (reply.Status == IPStatus.Success) { return reply.RoundtripTime; } return -1; // Ping failed } catch { return -1; } } private void UpdateNetworkIndicator(long latencyMs) { if (latencyMs < 0) { // Ping failed but internet access exists NetworkStatusText = "Connected"; NetworkStatusColor = Color.FromArgb("#F59E0B"); NetworkIcon = "📶"; return; } if (latencyMs <= 50) { // Excellent: 0-50ms NetworkStatusText = $"Excellent ({latencyMs}ms)"; NetworkStatusColor = Color.FromArgb("#10B981"); // Green NetworkIcon = "📶"; } else if (latencyMs <= 150) { // Good: 51-150ms NetworkStatusText = $"Good ({latencyMs}ms)"; NetworkStatusColor = Color.FromArgb("#10B981"); // Green NetworkIcon = "📶"; } else if (latencyMs <= 300) { // Fair: 151-300ms NetworkStatusText = $"Fair ({latencyMs}ms)"; NetworkStatusColor = Color.FromArgb("#F59E0B"); // Amber NetworkIcon = "📶"; } else { // Poor: 300ms+ NetworkStatusText = $"Poor ({latencyMs}ms)"; NetworkStatusColor = Color.FromArgb("#EF4444"); // Red NetworkIcon = "📶"; } } #endregion #region Crash Log private async Task ViewCrashLogAsync() { try { var logPath = Path.Combine(FileSystem.AppDataDirectory, "crash_log.txt"); if (File.Exists(logPath)) { var content = await File.ReadAllTextAsync(logPath); await Application.Current.MainPage.DisplayAlert("Crash Log", content, "OK"); await Share.RequestAsync(new ShareFileRequest { Title = "Share Crash Log", File = new ShareFile(logPath) }); } else { await Application.Current.MainPage.DisplayAlert("Info", "No crash log found", "OK"); } } catch (Exception ex) { await Application.Current.MainPage.DisplayAlert("Error", ex.Message, "OK"); } } #endregion #region Sync Commands [RelayCommand] private async Task SyncAllAsync() { if (IsSyncing) return; if (!IsConnected) { await Application.Current.MainPage.DisplayAlert( "No Connection", "Please check your internet connection before syncing.", "OK"); return; } IsSyncing = true; SyncStatus = "Syncing all data..."; try { int successCount = 0; int failCount = 0; foreach (var item in SyncItems) { var result = await ExecuteSyncItem(item); if (result) successCount++; else failCount++; } LastSyncTime = DateTime.Now; if (failCount == 0) SyncStatus = $"✓ All {successCount} items synced successfully!"; else if (successCount > 0) SyncStatus = $"⚠ {successCount} succeeded, {failCount} failed"; else SyncStatus = "✗ Sync failed. Please try again."; await Task.Delay(3000); if (!IsSyncing) SyncStatus = "Ready to sync"; } catch (Exception ex) { SyncStatus = $"Error: {ex.Message}"; Debug.WriteLine($"Sync All error: {ex}"); await Task.Delay(3000); SyncStatus = "Ready to sync"; } finally { IsSyncing = false; } } [RelayCommand] private async Task SyncItemAsync(SyncItem item) { if (IsSyncing || item.IsSyncing) return; if (!IsConnected) { await Application.Current.MainPage.DisplayAlert( "No Connection", "Please check your internet connection before syncing.", "OK"); return; } IsSyncing = true; SyncStatus = $"Syncing {item.Title}..."; try { await ExecuteSyncItem(item); LastSyncTime = DateTime.Now; await Task.Delay(1000); SyncStatus = "Ready to sync"; } catch (Exception ex) { Debug.WriteLine($"Sync item error: {ex}"); SyncStatus = "Ready to sync"; } finally { IsSyncing = false; } } private async Task ExecuteSyncItem(SyncItem item) { item.IsSyncing = true; item.Status = "Syncing..."; try { var result = await item.SyncAction(); item.Status = result ? "✓ Synced" : "✗ Failed"; item.LastSynced = DateTime.Now; await Task.Delay(1500); item.Status = ""; return result; } catch (Exception ex) { item.Status = "✗ Error"; Debug.WriteLine($"Sync error for {item.Title}: {ex.Message}"); await Task.Delay(1500); item.Status = ""; return false; } finally { item.IsSyncing = false; } } #endregion #region Sync Methods private async Task SyncAttendanceAsync() { try { await _syncService.SyncPostAttendanceAsync(); Debug.WriteLine("Attendance sync completed successfully"); return true; } catch (Exception ex) { Debug.WriteLine($"Attendance sync failed: {ex.Message}"); return false; } } private async Task SyncActualCoverageAsync() { try { var localActualCoveragePlan = await _mrRawPlanRepository.GetAllAsync(); var unsyncedPlans = localActualCoveragePlan .Where(p => p.IsSync == false && p.Status == 2) .ToList(); if (unsyncedPlans == null || !unsyncedPlans.Any()) { Debug.WriteLine("No unsynced actual coverage plans found"); return true; } int successCount = 0; int totalCount = unsyncedPlans.Count; foreach (var coverage in unsyncedPlans) { var result = await _syncService.SyncPostCoverageTodayAsync(coverage.MRRawPlanId); if (result) successCount++; } Debug.WriteLine($"Actual coverage sync: {successCount}/{totalCount} succeeded"); return successCount == totalCount; } catch (Exception ex) { Debug.WriteLine($"Actual coverage sync failed: {ex.Message}"); return false; } } private async Task SyncCoveragePlanAsync() { try { await _syncService.SyncPullCoveragePlanTodayAsync(); Debug.WriteLine("Coverage plan sync completed successfully"); return true; } catch (Exception ex) { Debug.WriteLine($"Coverage plan sync failed: {ex.Message}"); return false; } } #endregion } public partial class SyncItem : ObservableObject { [ObservableProperty] private string _title = string.Empty; [ObservableProperty] private string _description = string.Empty; [ObservableProperty] private string _icon = string.Empty; [ObservableProperty] private Color _iconColor = Colors.Gray; [ObservableProperty] private bool _isSyncing; [ObservableProperty] private string _status = string.Empty; [ObservableProperty] private DateTime? _lastSynced; public Func> SyncAction { get; set; } = () => Task.FromResult(false); } }