MainWindow.xaml.cs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. using System;
  2. using System.IO;
  3. using System.Text.Json;
  4. using System.Timers;
  5. using System.Windows;
  6. using System.Windows.Forms;
  7. using XafPet.Desktop.PetEngine;
  8. namespace XafPet.Desktop;
  9. public partial class MainWindow : Window
  10. {
  11. private NotifyIcon? _trayIcon;
  12. private ApiClient? _api;
  13. private Timer? _taskTimer;
  14. private Timer? _energyTimer;
  15. private Timer? _waterTimer;
  16. private string _baseUrl = "http://localhost:9082";
  17. public MainWindow()
  18. {
  19. InitializeComponent();
  20. RootGrid.MouseLeftButtonDown += (s, e) =>
  21. {
  22. if (e.ButtonState == MouseButtonState.Pressed) DragMove();
  23. };
  24. PetHost.InteractionReceived += OnInteractionReceived;
  25. Loaded += OnLoaded;
  26. Closed += OnClosed;
  27. }
  28. private void OnLoaded(object sender, RoutedEventArgs e)
  29. {
  30. var token = TokenStore.Load();
  31. _api = new ApiClient(new HttpClient(), _baseUrl, token ?? "dev-token");
  32. StartPolling();
  33. SetupTray();
  34. _ = SyncMascotAsync();
  35. }
  36. private void StartPolling()
  37. {
  38. _taskTimer = new Timer(30_000) { AutoReset = true };
  39. _taskTimer.Elapsed += async (_, _) => await PollTasksAsync();
  40. _taskTimer.Start();
  41. _energyTimer = new Timer(60_000) { AutoReset = true };
  42. _energyTimer.Elapsed += async (_, _) => await PollHealthAsync();
  43. _energyTimer.Start();
  44. StartWaterReminder();
  45. }
  46. private async Task PollTasksAsync()
  47. {
  48. if (_api == null) return;
  49. try
  50. {
  51. var tasks = await _api.GetTodayTasksAsync();
  52. var state = StateManager.MapState(tasks ?? new List<CfcTask>(), null, null, false);
  53. PushState(state, "tasks");
  54. }
  55. catch (ApiException ex) { PushBubble($"服务暂时不可用({ex.ApiCode})"); }
  56. catch (Exception) { PushBubble("连接后端失败,请检查服务"); }
  57. }
  58. private async Task PollHealthAsync()
  59. {
  60. if (_api == null) return;
  61. try
  62. {
  63. var energy = await _api.GetEnergyOverviewAsync();
  64. var checkin = await _api.GetDailyCheckinAsync();
  65. var state = StateManager.MapState(null, energy, checkin, false);
  66. PushState(state, "health");
  67. }
  68. catch (Exception) { /* 静默 */ }
  69. }
  70. private void PushState(PetState state, string reason)
  71. {
  72. var payload = new PetStatePayload(StateManager.StateToCamelCase(state), reason, null);
  73. PetHost.NotifyState(JsonSerializer.Serialize(payload));
  74. }
  75. private void PushBubble(string text)
  76. {
  77. PetHost.ShowBubble(text, 5000);
  78. }
  79. private void SetupTray()
  80. {
  81. var menu = new ContextMenuStrip();
  82. menu.Items.Add("设置", null, (_, _) => ShowSettings());
  83. menu.Items.Add("显示/隐藏", null, (_, _) => ToggleVisible());
  84. menu.Items.Add("退出", null, (_, _) => { _trayIcon?.Dispose(); Application.Current.Shutdown(); });
  85. _trayIcon = new NotifyIcon
  86. {
  87. Text = "浠艾福桌面宠物",
  88. Icon = SystemIcons.Application,
  89. ContextMenuStrip = menu,
  90. Visible = true
  91. };
  92. _trayIcon.DoubleClick += (_, _) => ToggleVisible();
  93. }
  94. private void ToggleVisible()
  95. {
  96. Visibility = Visibility == Visibility.Visible ? Visibility.Hidden : Visibility.Visible;
  97. }
  98. private void ShowSettings()
  99. {
  100. var win = new SettingsWindow(TokenStore.Load(), _baseUrl) { Owner = this };
  101. if (win.ShowDialog() == true)
  102. {
  103. _baseUrl = win.BaseUrl;
  104. if (!string.IsNullOrEmpty(win.Token)) TokenStore.Save(win.Token);
  105. _api = new ApiClient(new HttpClient(), _baseUrl, TokenStore.Load() ?? "dev-token");
  106. StartPolling();
  107. }
  108. }
  109. private async void OnClosed(object? sender, EventArgs e)
  110. {
  111. _taskTimer?.Stop(); _energyTimer?.Stop(); _waterTimer?.Stop();
  112. _taskTimer?.Dispose(); _energyTimer?.Dispose(); _waterTimer?.Dispose();
  113. _trayIcon?.Dispose();
  114. }
  115. // ── 喝水提醒 ─────────────────────────────────────────────
  116. private void StartWaterReminder()
  117. {
  118. _waterTimer = new Timer(60 * 60 * 1000) { AutoReset = true };
  119. _waterTimer.Elapsed += async (_, _) => await CheckWaterAsync();
  120. _waterTimer.Start();
  121. _ = CheckWaterAsync();
  122. }
  123. private async Task CheckWaterAsync()
  124. {
  125. if (_api == null) return;
  126. var hour = DateTime.Now.Hour;
  127. if (hour < 10 || hour > 20) return;
  128. try
  129. {
  130. var checkin = await _api.GetDailyCheckinAsync();
  131. if (checkin != null && !checkin.Items.Contains("water"))
  132. {
  133. PushState(PetState.WaitingForInteraction, "drink_water");
  134. PetHost.ShowBubble("该喝水啦~点我确认完成!", 15000);
  135. }
  136. }
  137. catch (Exception) { }
  138. }
  139. private void OnInteractionReceived(string action, string? dataJson)
  140. {
  141. switch (action)
  142. {
  143. case "click":
  144. _ = HandleClickAsync();
  145. break;
  146. case "pet":
  147. PetHost.ShowBubble("嘻嘻~", 2000);
  148. break;
  149. }
  150. }
  151. private async Task HandleClickAsync()
  152. {
  153. if (_api == null) return;
  154. try
  155. {
  156. var result = await _api.CompleteDrinkWaterAsync();
  157. if (result?.IsSuccess == true)
  158. {
  159. PushState(PetState.Happy, "drink_complete");
  160. PetHost.ShowBubble("喝水打卡成功!太棒了~", 4000);
  161. }
  162. }
  163. catch (Exception)
  164. {
  165. PushBubble("打卡失败,请稍后再试");
  166. }
  167. }
  168. // ── 吉祥物同步 ───────────────────────────────────────────
  169. private async Task SyncMascotAsync()
  170. {
  171. if (_api == null) return;
  172. try
  173. {
  174. var mascot = await _api.GetMascotAsync();
  175. var petName = mascot?.MascotId ?? "xibao";
  176. var cfgPath = Path.Combine(AppContext.BaseDirectory, "DefaultPets", petName, "pet-config.json");
  177. if (File.Exists(cfgPath))
  178. {
  179. var json = File.ReadAllText(cfgPath);
  180. var parsed = PetConfig.Parse(json);
  181. PetHost.LoadPetConfig(JsonSerializer.Serialize(parsed));
  182. }
  183. }
  184. catch (Exception) { /* 默认浠宝 */ }
  185. }
  186. }