MainWindow.xaml.cs 6.8 KB

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