import '../../../core/ui/hero_time_status.dart'; import 'package:flutter/material.dart'; import '../../../core/theme/tokens.dart'; import '../../../core/ui/live_planet.dart'; import '../../../core/data/planet_visual_state.dart'; import '../../home/home_screen.dart' show formatPop; import '../../../core/i18n/app_locale.dart'; import 'fandom_size_badge.dart'; /// 행성 상세 히어로 — 시안 `373:441117` 의 `Frame 1984081117`(375×543). /// /// 우주 배경 사진(`373:441126` IMAGE 채움) 위, 상단 104(상태 표시줄 48 + 내비 56) /// 아래 영역에 개설자(아바타 26 · ABeeZee 16), 행성 일러스트 200, 규모 칩과 멤버 수, /// 행성 이름 ABeeZee 46, 「FPI : n | 1인당 화력 : n」, 「가입중」 알약을 25 · 12 간격으로 /// 쌓는다. 뒤로가기 · 더보기는 화면의 앱 바가 그린다(스크롤하면 앱 바만 남는다). class PlanetDetailHero extends StatelessWidget { const PlanetDetailHero({ super.key, required this.name, required this.members, required this.totalFpi, required this.joined, this.nebula = false, this.onHeroParticipate, this.onJoin, this.isOwner = false, this.onOpenHistory, this.fandomId = '', this.watchPlanet = noPlanetWatch, this.stage, this.ownerName, this.ownerPhoto, this.onOpenOwner, }); /// 개설자 줄을 눌렀을 때 — Figma `544:19765` 「프로필 및 닉네임 클릭 시 해당 사용자의 MY로 이동」. /// 타인 프로필 화면(`549:21969`)이 생기면 연결한다(3차 MY). null 이면 누르지 않는다. final VoidCallback? onOpenOwner; final VoidCallback? onOpenHistory; final String fandomId; final PlanetWatch watchPlanet; final int? stage; final String name; final int members; final int totalFpi; final bool joined; final VoidCallback? onHeroParticipate; /// 알약을 눌렀을 때 — 「가입하기」면 가입, 「가입중」이면 탈퇴 시트. null 이면 상태만 보인다. final VoidCallback? onJoin; /// 내가 만든 행성이면 가입 알약을 숨긴다(Figma `544:19765` 「가입 버튼 - 내가 생성했을 경우 hide」). final bool isOwner; /// 성운 상세(`373:442708`)는 규모 칩과 FPI 줄이 없다. final bool nebula; /// 개설자 표시명. 서버에 읽을 자리가 없으면 null 이고 줄을 그리지 않는다 — /// 예전에는 시안의 `BTS_Maker` 가 모든 행성에 박혀 있었다(인계 결함). final String? ownerName; /// 개설자 사진(URL 또는 에셋). 없으면 회색 원. final String? ownerPhoto; static const double designHeight = 543; /// 시안의 상태 표시줄 + 내비 높이. 본문은 그 아래 439 안에 가운데 놓인다. static const double topBar = 104; static TextStyle _body(double size, Color color) => TextStyle( fontFamily: AppFonts.body, fontSize: size, fontWeight: FontWeight.w500, height: 1, color: color, ); @override Widget build(BuildContext context) { // 1인당 화력 — 시안 「1.44」. Figma `544:19765` 정의(「모든 포인트 획득, 소비 활동 및 1인당 활동 값을 // 집계한 값」)와 FPI 원천(C-34 활동 기준)이 갈려 해석 확인 중(전수대조 영역A Q-A23). 회신 전 임시로 총 FPI ÷ 멤버 수. final perHead = members == 0 ? 0.0 : totalFpi / members; const info = Color(0xFFDEDEDE); return Stack( fit: StackFit.expand, children: [ LivePlanetLighting( fandomId: fandomId, watch: watchPlanet, child: Image.asset( 'assets/figma/bg-space-detail.png', fit: BoxFit.cover, alignment: Alignment.topCenter, ), ), const DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, stops: [0, 0.7, 1], colors: [ Color(0x00141414), Color(0x22141414), AppColors.cosmicVoid, ], ), ), ), Padding( padding: const EdgeInsets.fromLTRB(16, topBar, 16, 0), child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ if (ownerName case final owner?) ...[ GestureDetector( onTap: onOpenOwner, child: Row( mainAxisSize: MainAxisSize.min, children: [ CircleAvatar( radius: 13, backgroundColor: const Color(0xFFD9D9D9), backgroundImage: switch (ownerPhoto) { final p? when p.startsWith('http') => NetworkImage( p, ), final p? => AssetImage(p), null => null, }, ), const SizedBox(width: 9), Text( owner, style: const TextStyle( fontFamily: AppFonts.display, fontWeight: FontWeight.w400, fontSize: 16, height: 1.3, letterSpacing: -0.4, color: AppColors.onCosmic, ), ), ], ), ), const SizedBox(height: 25), ], LivePlanet25D( onOpenHistory: onOpenHistory, fandomId: fandomId, watch: watchPlanet, image: 'assets/figma/planet-detail-hero.png', size: 200, glow: AppColors.neonViolet, stage: stage, ), HeroTimeStatus( fandomId: fandomId, watch: watchPlanet, onParticipate: onHeroParticipate, ), const SizedBox(height: 25), // 규모 칩 + 멤버 수 Row( mainAxisSize: MainAxisSize.min, children: [ if (!nebula) ...[ // 소형 · 중형 · 대형 — 가입자 수 기준(Figma `544:19765`). FandomSizeBadge(members: members, large: true), const SizedBox(width: 4), ], Image.asset( 'assets/figma/icon-person.png', width: 20, height: 20, ), const SizedBox(width: 3), Text( formatPop(members), style: _body(14, AppColors.onCosmic), ), ], ), const SizedBox(height: 12), Text( name, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontFamily: AppFonts.display, fontWeight: FontWeight.w400, fontSize: 46, height: 1, color: AppColors.onCosmic, ), ), const SizedBox(height: 12), if (!nebula) ...[ // 값이 커지면(백만 단위 FPI) 한 줄을 줄여 맞춘다. FittedBox( fit: BoxFit.scaleDown, child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( 'FPI : ${formatPop(totalFpi)}', style: _body(14, info), ), const SizedBox(width: 4), Text('|', style: _body(14, info)), const SizedBox(width: 4), Text( context.l10n.planetPerHead( perHead.toStringAsFixed(2), ), style: _body(14, info), ), ], ), ), const SizedBox(height: 12), ], if (!isOwner) Semantics( button: onJoin != null, child: GestureDetector( onTap: onJoin, child: Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8, ), decoration: BoxDecoration( color: const Color(0xFF1A1A1A), borderRadius: BorderRadius.circular(AppRadius.full), ), child: Text( joined ? context.l10n.planetJoined : context.l10n.planetJoin, style: _body( 14, const Color(0xFFD5D7D9), ).copyWith(letterSpacing: -0.35), ), ), ), ), ], ), ), ), ], ); } }