생성 때 고른 행성 형태·색 표시 · HOME 주간 정보

This commit is contained in:
Sn-Kinos
2026-10-05 23:21:44 +09:00
parent 55424bd4a2
commit c8c1a1ae67
40 changed files with 688 additions and 40 deletions
@@ -184,6 +184,17 @@ class FirestoreHomeRepository implements HomeRepository {
.limit(20)
.get();
// HOME 주간 정보(A-37) — 주간 공식 Snapshot 최신 · 직전 두 개(순위 · 변동 · 성장 기준선).
final snapshotsF = fandomId.isEmpty
? null
: db
.collection('rankingSnapshots')
.orderBy('createdAt', descending: true)
.limit(2)
.get()
.then((q) => [for (final d in q.docs) d.data()])
.catchError((Object _) => <Map<String, dynamic>>[]);
final wallet = (await walletF).data() ?? const <String, dynamic>{};
final fandom = fandomF == null
? const <String, dynamic>{}
@@ -226,7 +237,23 @@ class FirestoreHomeRepository implements HomeRepository {
final progressById = {for (final doc in progress) doc.id: doc.data()};
HomeWeekly? weekly;
if (snapshotsF != null) {
final snapshots = await snapshotsF;
final since = snapshots.firstOrNull?['createdAt'] is Timestamp
? (snapshots.first['createdAt'] as Timestamp).toDate()
: kstWeekStart(DateTime.now());
weekly = HomeWeekly.from(
fandomId: fandomId,
snapshots: snapshots,
fandom: fandom,
myWeekPop: await _myWeekGrowth(uid, fandomId, since),
wallet: wallet,
);
}
return HomeSnapshot(
weekly: weekly,
popBalance: wallet.intOr('activityBalance'),
fandomId: fandomId,
liveTicker: fandom.strOr('liveTicker').isEmpty
@@ -300,6 +327,27 @@ class FirestoreHomeRepository implements HomeRepository {
);
}
/// 이번 주 내가 [fandomId] 행성 성장에 보탠 Growth POP — 원장과 같은 트랜잭션에서 쓰는
/// `users/{uid}/growthEvents`(역분개는 음수 행)의 [since] 이후 합. 읽지 못하면 null.
// ponytail: 한 주치 성장 행을 전부 읽어 합한다. 한 사용자가 주에 수천 건을 넘기면 서버 집계 필드로 옮긴다.
Future<num?> _myWeekGrowth(String uid, String fandomId, DateTime since) async {
try {
final q = await _backend.db
.collection('users/$uid/growthEvents')
.where('createdAt', isGreaterThanOrEqualTo: Timestamp.fromDate(since))
.get();
num sum = 0;
for (final d in q.docs) {
final data = d.data();
final amount = data['amount'];
if (data['fandomIdAtPayout'] == fandomId && amount is num) sum += amount;
}
return sum;
} catch (_) {
return null;
}
}
Future<PlanetGrowth> _growth(
Map<String, dynamic> fandom,
String fandomId,
@@ -529,6 +577,13 @@ String kstDayKey([DateTime? at]) => (at ?? DateTime.now().toUtc())
.toIso8601String()
.substring(0, 10);
/// 이번 주 시작 — KST 월요일 00:00(서버 `weekKeyOf` 와 같은 주 경계). 주간 Snapshot 이 아직 없을 때만 쓴다.
DateTime kstWeekStart(DateTime at) {
final kst = at.toUtc().add(const Duration(hours: 9));
final monday = DateTime.utc(kst.year, kst.month, kst.day - (kst.weekday - 1));
return monday.subtract(const Duration(hours: 9));
}
/// HOME TODAY'S QUEST 의 관리자 퀘스트 — `authorUid` 가 없는 것만, `createdAt` 최신순
/// (설명 프레임 `2097:16397` 「관리자 퀘스트 - 최신순으로 최대 3개 노출」).
List<T> adminQuestsLatest<T>(