FPI 랭킹 내 행성·내 국가 판정 · 콜드 스타트 약관 깜빡임 · 로그아웃 상한

This commit is contained in:
Sn-Kinos
2026-10-06 04:01:15 +09:00
parent abe5f1ae50
commit 1539ae283e
9 changed files with 205 additions and 45 deletions
+47 -10
View File
@@ -10,7 +10,7 @@ import '../theme/tokens.dart';
/// 단일 버튼 알림(Figma `511:86714` · `554:23103`)은 실행 버튼 하나가 폭을 다 쓴다.
///
/// [content] 는 입력 칸이 있는 창(반려 사유 · 인증 제출 등)에서 문구 대신 넣는다.
class AppDialog extends StatelessWidget {
class AppDialog extends StatefulWidget {
const AppDialog({
super.key,
required this.title,
@@ -18,6 +18,7 @@ class AppDialog extends StatelessWidget {
this.content,
this.cancelLabel,
required this.confirmLabel,
this.onConfirm,
});
final String title;
@@ -28,6 +29,17 @@ class AppDialog extends StatelessWidget {
final String? cancelLabel;
final String confirmLabel;
/// 실행 버튼을 누르면 창을 닫기 **전에** 돈다. 도는 동안 실행 버튼은 진행 중
/// 표시로 바뀌고 두 버튼 모두 눌리지 않는다(로그아웃처럼 정리가 걸리는 실행).
final Future<void> Function()? onConfirm;
@override
State<AppDialog> createState() => _AppDialogState();
}
class _AppDialogState extends State<AppDialog> {
bool _busy = false;
static const _shape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(AppRadius.sm)),
);
@@ -55,21 +67,42 @@ class AppDialog extends StatelessWidget {
filled ? AppColors.onCosmicAccent : AppColors.onCosmic,
).copyWith(height: 1),
);
void pop() => Navigator.of(context).pop(value);
Future<void> pop() async {
final run = widget.onConfirm;
if (value && run != null) {
setState(() => _busy = true);
try {
await run();
} finally {
if (mounted) setState(() => _busy = false);
}
}
if (context.mounted) Navigator.of(context).pop(value);
}
return Expanded(
child: filled
? FilledButton(
onPressed: pop,
onPressed: _busy ? null : pop,
style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight(48),
padding: const EdgeInsets.symmetric(horizontal: 12),
backgroundColor: AppColors.cosmicAccent,
disabledBackgroundColor: AppColors.cosmicAccent,
shape: _shape,
),
child: text,
child: _busy
? const SizedBox.square(
dimension: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: AppColors.onCosmicAccent,
),
)
: text,
)
: OutlinedButton(
onPressed: pop,
onPressed: _busy ? null : pop,
style: OutlinedButton.styleFrom(
minimumSize: const Size.fromHeight(48),
padding: const EdgeInsets.symmetric(horizontal: 12),
@@ -98,11 +131,11 @@ class AppDialog extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
Text(
title,
widget.title,
textAlign: TextAlign.center,
style: _text(16, FontWeight.w600, AppColors.onCosmic),
),
if (body case final b?) ...[
if (widget.body case final b?) ...[
const SizedBox(height: 16),
Text(
b,
@@ -111,7 +144,7 @@ class AppDialog extends StatelessWidget {
.copyWith(height: 1.4),
),
],
if (content case final c?) ...[
if (widget.content case final c?) ...[
const SizedBox(height: 16),
c,
],
@@ -122,11 +155,11 @@ class AppDialog extends StatelessWidget {
const SizedBox(height: 16),
Row(
children: [
if (cancelLabel case final c?) ...[
if (widget.cancelLabel case final c?) ...[
button(c, false, filled: false),
const SizedBox(width: 8),
],
button(confirmLabel, true, filled: true),
button(widget.confirmLabel, true, filled: true),
],
),
],
@@ -146,16 +179,20 @@ Future<bool> showConfirmDialog(
Widget? content,
String? cancelLabel,
String? confirmLabel,
Future<void> Function()? onConfirm,
}) async {
final r = await showDialog<bool>(
context: context,
barrierColor: _barrier,
// 실행이 도는 동안 바깥을 눌러 닫히면 진행 중 표시가 사라진 채 실행만 남는다.
barrierDismissible: onConfirm == null,
builder: (context) => AppDialog(
title: title,
body: body,
content: content,
cancelLabel: cancelLabel ?? context.l10n.commonCancel,
confirmLabel: confirmLabel ?? context.l10n.commonChange,
onConfirm: onConfirm,
),
);
return r ?? false;