격려문구 알림 팝업 자동 종료 안정성 개선
Some checks failed
Release Gate / gate (push) Has been cancelled

- ReminderPopupWindow의 자동 닫힘 경로를 점검하고 UI 타이머와 실제 종료 타이머를 분리함
- 남은 시간 계산을 절대 시각 기준으로 바꾸고 Task.Delay 기반 종료 fail-safe를 추가해 팝업이 남는 경우를 줄임
- 창 종료 시 CancellationToken과 타이머를 함께 정리해 후속 종료 처리도 안전하게 맞춤
- README와 DEVELOPMENT 문서에 변경 목적과 검증 결과를 로컬 시각 기준으로 기록함
- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ (경고 0, 오류 0)
This commit is contained in:
2026-04-06 15:41:27 +09:00
parent 71fd5f0bb7
commit 2ae56b2510
3 changed files with 44 additions and 7 deletions

View File

@@ -1,4 +1,6 @@
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;
@@ -26,7 +28,9 @@ public partial class ReminderPopupWindow : Window
// ─── 타이머 ───────────────────────────────────────────────────────────────
private readonly DispatcherTimer _timer;
private readonly EventHandler _tickHandler;
private int _remaining;
private readonly CancellationTokenSource _autoCloseCts = new();
private readonly DateTime _closeAtUtc;
private readonly int _displaySeconds;
public ReminderPopupWindow(
string quoteText,
@@ -56,9 +60,10 @@ public partial class ReminderPopupWindow : Window
: "오늘 방금 시작했습니다";
// ── 카운트다운 ──
_remaining = Math.Max(3, cfg.DisplaySeconds);
CountdownBar.Maximum = _remaining;
CountdownBar.Value = _remaining;
_displaySeconds = Math.Max(3, cfg.DisplaySeconds);
_closeAtUtc = DateTime.UtcNow.AddSeconds(_displaySeconds);
CountdownBar.Maximum = _displaySeconds;
CountdownBar.Value = _displaySeconds;
// ── 위치: 레이아웃 완료 후 설정 ──
Loaded += (_, _) =>
@@ -71,13 +76,15 @@ public partial class ReminderPopupWindow : Window
// ── 타이머 ──
_tickHandler = (_, _) =>
{
_remaining--;
CountdownBar.Value = _remaining;
if (_remaining <= 0) Close();
var remainingSeconds = Math.Max(0, (_closeAtUtc - DateTime.UtcNow).TotalSeconds);
CountdownBar.Value = remainingSeconds;
if (remainingSeconds <= 0)
Close();
};
_timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_timer.Tick += _tickHandler;
_timer.Start();
_ = StartAutoCloseAsync(_autoCloseCts.Token);
// ── Esc 키 닫기 ──
KeyDown += (_, e) =>
@@ -132,10 +139,34 @@ public partial class ReminderPopupWindow : Window
private void CloseBtn_Click(object sender, RoutedEventArgs e) => Close();
private async Task StartAutoCloseAsync(CancellationToken cancellationToken)
{
try
{
await Task.Delay(TimeSpan.FromSeconds(_displaySeconds), cancellationToken);
if (cancellationToken.IsCancellationRequested)
return;
await Dispatcher.InvokeAsync(() =>
{
if (IsVisible)
Close();
}, DispatcherPriority.Background, cancellationToken);
}
catch (TaskCanceledException)
{
}
catch (OperationCanceledException)
{
}
}
protected override void OnClosed(EventArgs e)
{
_autoCloseCts.Cancel();
_timer.Stop();
_timer.Tick -= _tickHandler;
_autoCloseCts.Dispose();
base.OnClosed(e);
}
}