259 lines
9.2 KiB
Dart
259 lines
9.2 KiB
Dart
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';
|
||
|
||
/// 행성 상세 히어로 — 시안 `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.onOpenHistory,
|
||
this.fandomId = '',
|
||
this.watchPlanet = noPlanetWatch,
|
||
this.stage,
|
||
this.ownerName,
|
||
this.ownerPhoto,
|
||
this.sizeLabel,
|
||
});
|
||
|
||
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;
|
||
|
||
/// 성운 상세(`373:442708`)는 규모 칩과 FPI 줄이 없다.
|
||
final bool nebula;
|
||
|
||
/// 개설자 표시명. 서버에 읽을 자리가 없으면 null 이고 줄을 그리지 않는다 —
|
||
/// 예전에는 시안의 `BTS_Maker` 가 모든 행성에 박혀 있었다(인계 결함).
|
||
final String? ownerName;
|
||
|
||
/// 개설자 사진(URL 또는 에셋). 없으면 회색 원.
|
||
final String? ownerPhoto;
|
||
|
||
/// 팬덤 규모 라벨. 정책 §P-08 등급명이 붙기 전까지 시안 문구를 쓴다.
|
||
final String? sizeLabel;
|
||
|
||
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」. 총 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?) ...[
|
||
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) ...[
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 6,
|
||
vertical: 4,
|
||
),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0x1A54C07D),
|
||
border: Border.all(color: const Color(0xFF54C07D)),
|
||
borderRadius: BorderRadius.circular(4),
|
||
),
|
||
child: Text(
|
||
sizeLabel ?? context.l10n.planetSizeMedium,
|
||
style: _body(12, const Color(0xFF54C07D)),
|
||
),
|
||
),
|
||
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),
|
||
],
|
||
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),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
}
|