Files
fanuzu/app/lib/features/my/tier_screens.dart
T

282 lines
10 KiB
Dart

import 'package:flutter/material.dart';
import '../../core/theme/tokens.dart';
import '../../core/ui/planet_25d.dart' show PlanetArt;
import '../home/home_screen.dart' show formatPop;
import '../planet/planet_data.dart' show PlanetRepositoryFactory;
import '../planet/planet_history_screen.dart';
import 'my_data.dart';
import 'widgets/my_cards.dart';
import '../../core/i18n/app_locale.dart';
import '../../core/ui/bottom_inset.dart';
/// 티어 정보 — 화면 대조 원장 F6 · Figma `373:633256`.
///
/// 명세 `549:22185` — *티어별 이미지 · 티어명 · 기준치 · **티어가 낮은 순으로 소팅***
///
/// C-18 — 기준치는 **레벨 구간**이다. 누적 기여 POP 문턱과 칭호별 권한·보상은 적지
/// 않는다. Passport Tier(B-65)는 별개 체계라 따로 늘어놓는다.
class TierInfoScreen extends StatelessWidget {
const TierInfoScreen({super.key});
@override
Widget build(BuildContext context) {
// 시안 `373:633256` 한 줄 — 새싹 80 · 간격 17 · 이름 20/500 · 기준 12/400 #84868e ·
// 위아래 12 · 밑줄 #1b1c1c.
Widget row(String label, String note, {bool sprout = true}) => Container(
margin: const EdgeInsets.only(bottom: 20),
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: Color(0xFF1B1C1C))),
),
child: Row(
children: [
SizedBox(
width: 80,
height: 80,
child: sprout
? Image.asset('assets/figma/icon-tier-sprout.png')
: null,
),
const SizedBox(width: 17),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: myText(20, FontWeight.w500)),
if (note.isNotEmpty)
Text(
note,
style: myText(
12,
FontWeight.w400,
color: const Color(0xFF84868E),
),
),
],
),
),
],
),
);
return Scaffold(
appBar: AppBar(title: Text(context.l10n.tierInfoTitle)),
body: ListView(
padding: withBottomInset(context, const EdgeInsets.fromLTRB(20, 20, 20, 0)),
children: [
Text.rich(
TextSpan(
style: myText(18, FontWeight.w500),
children: [
TextSpan(text: context.l10n.tierPromptPrefix),
TextSpan(
text: context.l10n.tierPromptRank,
style: TextStyle(color: AppColors.cosmicAccent),
),
TextSpan(text: context.l10n.tierPromptSuffix),
],
),
),
const SizedBox(height: 20),
// 낮은 순으로. LevelTitle 선언 순서가 곧 낮은 순이다. 기준치는 레벨 구간(C-18).
for (final title in LevelTitle.values)
row('${title.label} TIER', title.rangeLabel),
// Passport Tier 는 레벨과 별개 체계다(B-65 · FR-MY-01). 시안에 없는 구획이다.
Text('PASSPORT', style: myText(16, FontWeight.w600)),
const SizedBox(height: 4),
Text(
context.l10n.tierPassportNote,
style: myText(12, FontWeight.w400, color: const Color(0xFF84868E)),
),
const SizedBox(height: 8),
for (final tier in PassportTier.values)
row(tier.label, '', sprout: false),
],
),
);
}
}
/// 티어 상세 — 화면 대조 원장 F6 · Figma `494:16177`.
///
/// 명세 `549:22206` — *갤럭시에서 선택된 기본 행성의 티어를 메인으로 상단에 위치 ·*
/// *나머지 행성 티어는 티어 높은순으로 소팅*
///
/// 칭호는 개인 레벨에서만 나오므로(C-18) 행성별로는 **그 팬덤 Passport Tier 와 기여 실적**을 보인다
/// (10/3 #10 사용자 × 팬덤). 탈퇴한 팬덤도 기록을 지우지 않고 아래에 둔다(9/21 5절 · FX §7.1 ARCHIVED).
/// 아래 BADGE 는 별첨 1 ②-5 「배지 부여·프로필 표시」 — 앱 Figma 에 Badge 화면이 없어 이 화면 끝에 둔다.
class TierDetailScreen extends StatelessWidget {
const TierDetailScreen({
super.key,
required this.tiers,
this.badges = const [],
this.planetRepository,
});
final List<PlanetTier> tiers;
final List<MyBadge> badges;
/// 행성 카드를 누르면 그 팬덤의 Passport History(팬덤 역사 · 내 기록)를 연다. null 이면 열지 않는다.
final PlanetRepositoryFactory? planetRepository;
/// 기본 행성이 맨 앞, 나머지는 가입 중 → 탈퇴 순, 같은 구분 안에서는 Tier · 기여 POP 높은 순.
List<PlanetTier> get ordered {
final primary = tiers.where((p) => p.isPrimary).toList();
final rest = tiers.where((p) => !p.isPrimary).toList()
..sort((a, b) {
if (a.joined != b.joined) return a.joined ? -1 : 1;
final byTier = b.passportTier.index.compareTo(a.passportTier.index);
return byTier != 0 ? byTier : b.contributedPop.compareTo(a.contributedPop);
});
return [...primary, ...rest];
}
@override
Widget build(BuildContext context) {
final list = ordered;
final top = list.fold<int>(
0,
(m, p) => p.contributedPop > m ? p.contributedPop : m,
);
// 시안 `494:16177` — 기본 행성 카드(#19191c · 분홍 획) · 구분선 #2c2c2c · 나머지 카드.
// 행성별 칭호는 두지 않는다(C-18) — 시안의 「GLOW TIER」 줄은 비우고 기여 실적만 그린다.
return Scaffold(
appBar: AppBar(title: const Text('TIER')),
body: ListView(
padding: withBottomInset(context, const EdgeInsets.all(16)),
children: [
for (final (i, planet) in list.indexed) ...[
if (i > 0) const SizedBox(height: 16),
InkWell(
onTap: planetRepository == null || planet.fandomId.isEmpty
? null
: () => Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => PlanetHistoryScreen(
repository: planetRepository!(planet.fandomId),
fandomName: planet.fandomName,
),
),
),
child: _PlanetCard(
planet: planet,
progress: top == 0 ? 0 : planet.contributedPop / top,
),
),
if (i == 0 && planet.isPrimary && list.length > 1) ...[
const SizedBox(height: 16),
const SizedBox(
height: 1,
child: ColoredBox(color: MyColors.divider),
),
],
],
if (badges.isNotEmpty) ...[
const SizedBox(height: 24),
Text('BADGE', style: myText(16, FontWeight.w600)),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
for (final badge in badges)
Chip(label: Text(badge.label, style: myText(12, FontWeight.w500))),
],
),
],
],
),
);
}
}
/// 행성 한 장 — 행성 60 + 이름 12/600 · 진행 바 · 「누적 기여 / N POP」.
class _PlanetCard extends StatelessWidget {
const _PlanetCard({required this.planet, required this.progress});
final PlanetTier planet;
final double progress;
@override
Widget build(BuildContext context) => Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: planet.isPrimary
? BoxDecoration(
color: MyColors.card,
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(color: AppColors.cosmicAccent),
)
: null,
child: Row(
children: [
SizedBox(
width: 60,
child: Column(
children: [
PlanetArt(
fallback: 'assets/figma/my-planet-1.png',
planetType: planet.planetType,
colorKey: planet.colorKey,
width: 60,
height: 60,
),
Text(
planet.fandomName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontFamily: AppFonts.body,
fontSize: 12,
fontWeight: FontWeight.w600,
height: 1,
color: AppColors.onCosmic,
),
),
],
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'PASSPORT ${planet.passportTier.label}',
style: myText(
14,
FontWeight.w500,
color: planet.joined ? AppColors.onCosmic : const Color(0xFF84868E),
),
),
const SizedBox(height: 4),
MyProgressBar(value: progress),
const SizedBox(height: 8),
Row(
children: [
Text(
context.l10n.tierTotalContribution,
style: myText(
12,
FontWeight.w500,
color: AppColors.cosmicAccent,
),
),
const Spacer(),
Text(
'${formatPop(planet.contributedPop)} POP',
style: myText(
12,
FontWeight.w500,
color: AppColors.cosmicAccent,
),
),
],
),
],
),
),
],
),
);
}