사용자 화면 문구·표본 데이터 · 퀘스트 참여 흐름 · HOME 진입 구획 · 미니맵 · 공통 확인 창 배치 수정

This commit is contained in:
Sn-Kinos
2026-10-05 00:44:39 +09:00
parent c8d29d032b
commit 8a0297eb92
66 changed files with 1812 additions and 959 deletions
@@ -5,6 +5,7 @@ import '../../core/data/invite_code.dart';
import '../../core/data/server_clock.dart';
import 'home_data.dart';
import '../quest/quest_data.dart' show QuestType;
import '../quest/quest_repository_firestore.dart' show questToday;
import '../../core/i18n/app_locale.dart';
/// 홈의 실 서버 구현 — 화면 대조 원장 F1.
@@ -137,10 +138,13 @@ class FirestoreHomeRepository implements HomeRepository {
.collection('heroGiftProducts')
.where('active', isEqualTo: true)
.get();
// 인기 K-INSIDE — 기능명세서 v1.4 §2.114-7 「최대 3개 소팅(미해결 + 고액 포인트 순)」.
// 미해결(`open`) 먼저, 그 안에서 현상금 큰 순. 정렬은 아래 [popularInsides].
// ponytail: 현상금 상위 20건 안에서만 고른다. (status, bountyPop) 색인을 배포하면 질의로 옮긴다.
final insidesF = db
.collection('questions')
.orderBy('createdAt', descending: true)
.limit(3)
.orderBy('bountyPop', descending: true)
.limit(20)
.get();
final wallet = (await walletF).data() ?? const <String, dynamic>{};
@@ -167,7 +171,9 @@ class FirestoreHomeRepository implements HomeRepository {
final campaigns = (await campaignF).docs
.where((d) => !blocked.contains(d.data()['creatorUid']))
.toList();
final insides = (await insidesF).docs;
final insides = popularInsides([
for (final doc in (await insidesF).docs) doc.data(),
]);
final progressById = {for (final doc in progress) doc.id: doc.data()};
final policy = (await policyF).data() ?? const <String, dynamic>{};
@@ -185,7 +191,7 @@ class FirestoreHomeRepository implements HomeRepository {
],
briefings: [for (final doc in briefings) ?_briefing(doc.data())],
campaign: campaigns.isEmpty ? null : _campaign(campaigns.first.data()),
insides: [for (final doc in insides) _inside(doc.data())],
insides: [for (final data in insides) _inside(data)],
items: [for (final doc in (await itemsF).docs) _item(doc.id, doc.data())],
);
}
@@ -284,7 +290,8 @@ class FirestoreHomeRepository implements HomeRepository {
members: validInt(fandom['memberCount']),
requiredMembers: targets?.members,
// Q-PN-01 A안 — 탄생은 창립자만.
isFounder: fandom['founderUid'] != null && fandom['founderUid'] == _backend.uid,
isFounder:
fandom['founderUid'] != null && fandom['founderUid'] == _backend.uid,
requiredSfpi: validNumber(next?['requiredSfpi']),
nebulaGoalPassed: goalPassed,
);
@@ -296,9 +303,10 @@ class FirestoreHomeRepository implements HomeRepository {
Map<String, dynamic>? progress,
Map<String, dynamic> policy,
) {
final done =
(progress?.intOr('count') ?? 0) >= quest.intOr('dailyLimit', 1);
final started = (progress?.intOr('count') ?? 0) > 0;
// 퀘스트 목록 · 상세와 같은 판정(`questToday`) — 오늘(KST) 행만 센다.
final (:count, :done) =
questToday(QuestType.parse(quest.strOr('type')), quest, progress, policy);
final started = count > 0;
return QuestSummary(
title: quest.strOr('title'),
rewardPop: QuestType.parse(quest.strOr('type')).rewardFrom(quest, policy),
@@ -354,6 +362,15 @@ String _timeAgo(DateTime at) {
return appL10n.timeDaysAgo('${gap.inDays}');
}
/// 인기 K-INSIDE 3건 — 미해결(`status == open`) 먼저, 같은 묶음 안에서는 현상금 큰 순.
/// 입력이 현상금 내림차순이라 미해결 묶음만 앞으로 옮기면 된다.
List<Map<String, dynamic>> popularInsides(
List<Map<String, dynamic>> byBounty,
) => [
...byBounty.where((q) => q['status'] == 'open'),
...byBounty.where((q) => q['status'] != 'open'),
].take(3).toList();
/// 오늘 날짜(KST `YYYY-MM-DD`) — 서버 `briefings.dayKey` 와 같은 규약이다.
///
/// 단말 시간대로 끊으면 해외 사용자의 「오늘」이 서버의 `dayKey` 와 달라 브리핑이