484 lines
14 KiB
C#
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);
|
|
}
|
|
} |