Figma 설명 프레임 반영 — 로그인·온보딩·스플래시 · HOME · GALAXY · 퀘스트·이클립스·알림

This commit is contained in:
Sn-Kinos
2026-10-06 03:03:20 +09:00
parent 64825e72b0
commit ecf5998725
77 changed files with 2703 additions and 640 deletions
+39 -14
View File
@@ -26,6 +26,7 @@ import 'quest_reward_screen.dart';
import 'widgets/quest_card_tile.dart';
import 'widgets/time_attack_countdown.dart';
import '../../core/i18n/app_locale.dart';
import '../../core/ui/sheet_route.dart';
/// 퀘스트 상세 — 화면 대조 원장 F4.
///
@@ -118,6 +119,9 @@ class _QuestDetailScreenState extends State<QuestDetailScreen> {
return Scaffold(
appBar: AppBar(
actions: [
// 명세 `548:19275` — 더보기는 *사용자 퀘스트 중 타인 퀘스트에서만 노출*. 내 퀘스트의
// 수정 · 취소는 확정 정책(C-07 · B-30)이 더한 항목이라 생성자에게도 둔다. 운영 Quest 는 없음.
if (ugc)
PopupMenuButton<String>(
tooltip: context.l10n.commonMore,
icon: const ImageIcon(
@@ -128,7 +132,7 @@ class _QuestDetailScreenState extends State<QuestDetailScreen> {
onSelected: (value) => switch (value) {
'edit' => _edit(detail),
'cancel' => _cancel(detail),
_ => _report(card.title),
_ => _report(card),
},
itemBuilder: (_) => [
// 공개 후 수정은 생성자의 제목 · 설명뿐이다(C-07).
@@ -146,10 +150,11 @@ class _QuestDetailScreenState extends State<QuestDetailScreen> {
value: 'cancel',
child: Text(context.l10n.questCancel),
),
PopupMenuItem(
value: 'report',
child: Text(context.l10n.reportTitle),
),
if (stage != QuestStage.mine)
PopupMenuItem(
value: 'report',
child: Text(context.l10n.reportTitle),
),
],
),
],
@@ -406,9 +411,11 @@ class _QuestDetailScreenState extends State<QuestDetailScreen> {
detail,
stage,
blocked,
// 정원이 찬 퀘스트도 종료다(명세 `523:18176` 종료 시점 1) — 시작 전인 사람만 막힌다.
closed:
detail.endsAt != null &&
!ServerClock.now().isBefore(detail.endsAt!),
(detail.endsAt != null &&
!ServerClock.now().isBefore(detail.endsAt!)) ||
(questFull(card) && stage == QuestStage.notStarted),
)
: TimeAttackCountdown(
endsAt: endsAt,
@@ -959,9 +966,13 @@ class _QuestDetailScreenState extends State<QuestDetailScreen> {
// 기본 보상은 이미 원장에 있다. 광고는 그 위의 선택적 증분이다(원장 B-03 · C-122) —
// 광고가 실패해도 기본 보상은 그대로다.
final bonus =
await showQuestAdBonus(context, bonusPop: detail.card.adBonusPop) ??
false;
// 일 한도를 다 쓴 사람에게는 모달을 띄우지 않는다(별첨 1 ⑥ 「일일 한도 도달 시 노출 제한」) —
// 띄우면 광고를 끝까지 보고도 증분 0 이다.
final adOpen = await widget.repository.adAvailable();
if (!mounted) return;
final bonus = adOpen &&
(await showQuestAdBonus(context, bonusPop: detail.card.adBonusPop) ??
false);
if (!mounted) return;
int? adPop;
if (bonus) {
@@ -979,6 +990,13 @@ class _QuestDetailScreenState extends State<QuestDetailScreen> {
if (mounted) _toast(error.message);
}
if (!mounted) return;
} else if (_adShown) {
// 명세 `518:88645` — 광고 종료(100% 시청 또는 skip) 후 *광고 시청 후 애니메이션*. 끝까지
// 보지 않았으니 증분은 없다(B-03) — 애니메이션만 거쳐 완료 화면으로 간다.
await _warp(QuestAdWarpScreen.after(
until: Future<void>.delayed(const Duration(milliseconds: 1200)),
));
if (!mounted) return;
}
}
@@ -1003,7 +1021,11 @@ class _QuestDetailScreenState extends State<QuestDetailScreen> {
/// 끝까지 봤으면 노출 ID, 아니면 null 이다. 불러오지 못했거나 중간에 닫았으면
/// 그 사실을 알리고 **기본 보상으로 넘어간다**(원장 B-03 — 실패 시 기본 보상 지급 ·
/// 횟수 미차감). 대기 중 「건너뛰기」를 누르면 늦게 온 광고는 띄우지 않고 버린다.
/// 이번 보상에서 광고가 실제로 화면에 떴는가 — skip 으로 닫았을 때도 시청 후 애니메이션을 거친다.
bool _adShown = false;
Future<String?> _watchAd(QuestReward reward) async {
_adShown = false;
RewardedAdHandle? ad;
BackendException? failure;
final loading = widget.repository
@@ -1030,6 +1052,7 @@ class _QuestDetailScreenState extends State<QuestDetailScreen> {
}
return null;
}
_adShown = true;
final impression = await loaded.show();
if (impression == null && mounted) {
_toast(context.l10n.adNotFinished);
@@ -1065,14 +1088,16 @@ class _QuestDetailScreenState extends State<QuestDetailScreen> {
rootNavigator: true,
).push<void>(MaterialPageRoute(builder: (_) => screen));
Future<void> _report(String title) async {
Future<void> _report(QuestCard card) async {
final reason = await Navigator.of(context, rootNavigator: true)
.push<String>(
MaterialPageRoute(
// 명세 `548:21389` — 신고 화면은 *페이지 아래 move out* 으로 닫힌다.
SheetRoute(
builder: (_) => ReportScreen(
// 신고 정보 영역 — *작성자 닉네임 · 게시물 제목*. 작성자 칸에 제목이 들어가던 것을 바로잡음.
target: ModerationTarget(
authorName: title,
title: context.l10n.questNamed(title),
authorName: card.authorName,
title: card.title,
),
),
),