282 lines
10 KiB
Dart
282 lines
10 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../../core/theme/tokens.dart';
|
|
import '../../core/ui/planet_25d.dart' show PlanetArt, planetFallbackImage;
|
|
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: planetFallbackImage(planet.fandomId),
|
|
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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|