성운 상세 실시간 기여 랭킹에 실제 기여자 표시

This commit is contained in:
Sn-Kinos
2026-10-05 18:07:44 +09:00
parent 96a95a12d1
commit be623d2e46
9 changed files with 514 additions and 229 deletions
+65 -21
View File
@@ -6,6 +6,7 @@
/// - 팬덤 라운지 리스트 — *최신순 소팅 · 최초 5개, 스크롤 시 5개씩 호출*
library;
import '../campaign/campaign_data.dart' show ContributorRank;
import '../my/my_data.dart' show PickedPhoto, PhotoRejected, checkProfilePhoto;
import '../../core/i18n/app_locale.dart';
@@ -69,10 +70,11 @@ List<(T, int)> rankWithTies<T>(
num Function(T) value,
String Function(T) id,
) {
final sorted = [...items]..sort((a, b) {
final byValue = value(b).compareTo(value(a));
return byValue != 0 ? byValue : id(a).compareTo(id(b));
});
final sorted = [...items]
..sort((a, b) {
final byValue = value(b).compareTo(value(a));
return byValue != 0 ? byValue : id(a).compareTo(id(b));
});
final out = <(T, int)>[];
for (var i = 0; i < sorted.length; i++) {
final tied = i > 0 && value(sorted[i]) == value(sorted[i - 1]);
@@ -304,12 +306,26 @@ class HeroDropReceipt {
final int amount;
}
enum PlanetHistoryKind { evolution, hibernation, wakeUp, zap, questReward, companyAdBonus, funding }
enum PlanetHistoryKind {
evolution,
hibernation,
wakeUp,
zap,
questReward,
companyAdBonus,
funding,
}
class PlanetHistoryEntry {
const PlanetHistoryEntry({required this.id, required this.kind,
required this.occurredAt, this.fromStage, this.toStage,
this.participationConfirmed = false, this.amount});
const PlanetHistoryEntry({
required this.id,
required this.kind,
required this.occurredAt,
this.fromStage,
this.toStage,
this.participationConfirmed = false,
this.amount,
});
final String id;
final PlanetHistoryKind kind;
final DateTime occurredAt;
@@ -340,7 +356,10 @@ abstract interface class PlanetRepository {
Future<List<LoungePost>> morePosts({required int offset, int limit = 5});
/// 글 쓰기. 사진은 먼저 Storage 에 올리고 URL 을 글에 붙인다(B-44).
Future<LoungePost> createPost(String body, {List<PickedPhoto> photos = const []});
Future<LoungePost> createPost(
String body, {
List<PickedPhoto> photos = const [],
});
/// 내 글 본문 수정. 사진은 바꾸지 않는다.
Future<LoungePost> updatePost(LoungePost post, String body);
@@ -377,7 +396,13 @@ abstract interface class PlanetRepository {
});
/// 지표별 **국가** 랭킹 — Figma `546:14713`(랭킹 필터 「국가별」).
Future<FpiRanking> countryRanking({required FpiMetric metric, int limit = 30});
Future<FpiRanking> countryRanking({
required FpiMetric metric,
int limit = 30,
});
/// 성운 「실시간 기여 랭킹」 — 멤버 누적 기여 POP(`members.contributedPop`) 상위.
Future<List<ContributorRank>> contributors({int limit = 3});
}
/// 행성 ID 로 저장소를 만드는 함수. 갤럭시 화면이 어느 행성으로 들어가든
@@ -398,14 +423,21 @@ class SamplePlanetRepository implements PlanetRepository {
@override
Future<void> join() async {}
@override
Future<List<ContributorRank>> contributors({int limit = 3}) async => const [];
@override
Future<void> leave() async {}
@override
Future<PlanetHistoryPage> history({String? cursor, bool personal = false}) async => const PlanetHistoryPage(items: []);
Future<PlanetHistoryPage> history({
String? cursor,
bool personal = false,
}) async => const PlanetHistoryPage(items: []);
@override
Future<HeroDropReceipt> claimHeroDrop(String dropId) =>
Future.error(StateError('실제 지급 서버가 연결되지 않았습니다.'));
/// [fandomId] 는 갤럭시에서 누른 행성이다. 주지 않으면 내 행성으로 본다.
const SamplePlanetRepository({String? fandomId})
: fandomId = fandomId ?? _myFandomId;
@@ -428,8 +460,10 @@ class SamplePlanetRepository implements PlanetRepository {
/// ponytail: 표본에 없는 ID(빈 문자열 포함)면 첫 행으로 떨어진다. 목 모드에서
/// ID 를 주는 곳은 [SampleGalaxyRepository] 의 6행성뿐이라 닿지 않는 길이고,
/// 여기서 던지면 발주사 확인용 화면이 빈다. 표본 행성이 늘어나면 두 표를 함께 늘린다.
(String, String, List<int>) get _row =>
_planets.firstWhere((p) => p.$1 == fandomId, orElse: () => _planets.first);
(String, String, List<int>) get _row => _planets.firstWhere(
(p) => p.$1 == fandomId,
orElse: () => _planets.first,
);
/// 행성별 5축 값. 순서는 [FpiMetric.values] 와 같다 — 별첨 B 확정 순서인
/// `bpm · health · referral · contribution · community`.
@@ -532,9 +566,12 @@ class SamplePlanetRepository implements PlanetRepository {
// 전체 순위 = 행성 랭킹에서 **이 행성**의 순위. 랭킹 화면과 같은 계산이다.
rank: nebula
? 0
: _rank(_planets, metric, myId: row.$1, limit: _planets.length)
.mine!
.rank,
: _rank(
_planets,
metric,
myId: row.$1,
limit: _planets.length,
).mine!.rank,
trend: nebula ? 0 : axes[metric.index] ~/ 60 - metric.index * 40,
// 내 기여도는 **내 행성에만 있다.** 남의 행성에서 내 등수를 지어내면
// 화면이 그것을 사실로 띄운다(대조표 3-5 — 없으면 null).
@@ -546,8 +583,10 @@ class SamplePlanetRepository implements PlanetRepository {
}
@override
Future<List<LoungePost>> morePosts({required int offset, int limit = 5}) async =>
_posts(offset, limit);
Future<List<LoungePost>> morePosts({
required int offset,
int limit = 5,
}) async => _posts(offset, limit);
/// 표본 댓글. 서버가 없어도 쓰고 지운 댓글이 상세를 다시 열 때 남아 있게 한다.
static final _comments = <String, List<LoungeComment>>{};
@@ -613,8 +652,10 @@ class SamplePlanetRepository implements PlanetRepository {
Future<void> blockAuthor(LoungePost post) async {}
@override
Future<FpiRanking> planetRanking({required FpiMetric metric, int limit = 30}) async =>
_rank(_planets, metric, myId: _myFandomId, limit: limit);
Future<FpiRanking> planetRanking({
required FpiMetric metric,
int limit = 30,
}) async => _rank(_planets, metric, myId: _myFandomId, limit: limit);
/// 표본 사용자는 **내 행성 소속**이다. 남의 행성 랭킹에 「나」를 끼우면
/// 「나의 랭킹 영역」이 가입하지도 않은 행성에서 뜬다. 대상 행성은 인터페이스가
@@ -636,7 +677,10 @@ class SamplePlanetRepository implements PlanetRepository {
);
@override
Future<FpiRanking> countryRanking({required FpiMetric metric, int limit = 30}) async =>
Future<FpiRanking> countryRanking({
required FpiMetric metric,
int limit = 30,
}) async =>
// 「나의 랭킹 영역」은 국가별 필터에서도 뜬다 — 내 국가 행이다(546:14713).
_rank(_countries, metric, myId: 'KR', limit: limit);