LSFE/LSFE.MobApp/ViewModels/Utility/UtilitiesVM.cs
2026-07-30 10:31:04 +08:00

484 lines
14 KiB
C#

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<Entities.Planning.MRRawPlan> _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<SyncItem> SyncItems { get; } = new ObservableCollection<SyncItem>();
public UtilitiesVM(
ISyncService syncService,
IRepository<Entities.Planning.MRRawPlan> mrRawPlanRepository)
{
_syncService = syncService;
_mrRawPlanRepository = mrRawPlanRepository;
ViewCrashLogCommand = new Command<UtilitiesVM>(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<long> 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<bool> 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<bool> 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<bool> 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<bool> 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<Task<bool>> SyncAction { get; set; } = () => Task.FromResult(false);
}
}