갤럭시 검색 결과에서 행성 상세로 이동

This commit is contained in:
Sn-Kinos
2026-10-05 17:39:23 +09:00
parent 54d0b23260
commit 8a1b824307
11 changed files with 394 additions and 171 deletions
@@ -73,12 +73,20 @@ class GalaxyField extends StatelessWidget {
/// 90개가 사라진다. 바깥 궤도가 [edgeMargin] 안쪽에 서도록 간격을 나눈다.
static double ringOf(int rank, int outerRank) {
final rings = math.max(1, outerRank - 1);
final gap = math.min(ringGap, (extent / 2 - innerRing - edgeMargin) / rings);
final gap = math.min(
ringGap,
(extent / 2 - innerRing - edgeMargin) / rings,
);
return innerRing + (rank - 1) * gap;
}
/// 행성의 논리 좌표. 그리기·히트 테스트·HUD 가 같은 식을 써야 어긋나지 않는다.
static Offset positionOf(PlanetNode p, int index, double spin, int outerRank) {
static Offset positionOf(
PlanetNode p,
int index,
double spin,
int outerRank,
) {
if (p.isCenter) return const Offset(extent / 2, extent / 2);
// 순위가 낮을수록 멀어진다. 중심 행성을 뺀 나머지를 고리에 흩는다.
final ring = ringOf(p.rank, outerRank);
@@ -101,7 +109,10 @@ class GalaxyField extends StatelessWidget {
/// -1(뒤·화면 위) ~ +1(앞·화면 아래) 의 깊이.
static double depthOf(Offset at) =>
((at.dy - extent / 2) / (extent / 2 * GalaxyMotion.tilt)).clamp(-1.0, 1.0);
((at.dy - extent / 2) / (extent / 2 * GalaxyMotion.tilt)).clamp(
-1.0,
1.0,
);
/// 2.5D 깊이 배율. 궤도 앞쪽일수록 크다.
static double depthScaleOf(Offset at) =>
@@ -125,7 +136,9 @@ class GalaxyField extends StatelessWidget {
/// 행성 일러스트. 내 행성은 홈과 같은 그림, 나머지는 시안 행성 2종을 돌려 쓴다.
static String imageOf(PlanetNode p, int index) => p.isMine
? 'assets/figma/planet-home.png'
: (index.isEven ? 'assets/figma/planet-1.png' : 'assets/figma/planet-2.png');
: (index.isEven
? 'assets/figma/planet-1.png'
: 'assets/figma/planet-2.png');
@override
Widget build(BuildContext context) {
@@ -147,7 +160,10 @@ class GalaxyField extends StatelessWidget {
Positioned.fill(
child: RepaintBoundary(
child: CustomPaint(
painter: _BackdropPainter(planets: planets, outerRank: outerRank),
painter: _BackdropPainter(
planets: planets,
outerRank: outerRank,
),
isComplex: true,
willChange: false,
),
@@ -169,7 +185,9 @@ class GalaxyField extends StatelessWidget {
opacity: depthOpacityOf(at),
selected: selected?.id == p.id,
onTap: () => onSelect(p),
onOpenHistory: onOpenHistory == null ? null : () => onOpenHistory!(p),
onOpenHistory: onOpenHistory == null
? null
: () => onOpenHistory!(p),
),
],
),
@@ -243,8 +261,17 @@ class _PlanetHit extends StatelessWidget {
),
),
// 3~5. 본체 일러스트(표면 장식·구름 포함).
LivePlanet25D(onOpenHistory: onOpenHistory, fandomId: planet.id, watch: watchPlanet, image: image, size: side, stage: planet.stage, glow: glow,
animate: selected, lowEffects: !selected),
LivePlanet25D(
onOpenHistory: onOpenHistory,
fandomId: planet.id,
watch: watchPlanet,
image: image,
size: side,
stage: planet.stage,
glow: glow,
animate: selected,
lowEffects: !selected,
),
Positioned(
bottom: -4,
child: Text(
@@ -257,7 +284,9 @@ class _PlanetHit extends StatelessWidget {
fontWeight: planet.isMine
? FontWeight.w600
: FontWeight.w400,
shadows: const [Shadow(color: Colors.black, blurRadius: 6)],
shadows: const [
Shadow(color: Colors.black, blurRadius: 6),
],
),
),
),
@@ -309,7 +338,8 @@ class _BackdropPainter extends CustomPainter {
static bool _sameRanks(List<PlanetNode> a, List<PlanetNode> b) {
for (var i = 0; i < a.length; i++) {
if (a[i].rank != b[i].rank || a[i].isCenter != b[i].isCenter) return false;
if (a[i].rank != b[i].rank || a[i].isCenter != b[i].isCenter)
return false;
}
return true;
}
@@ -148,7 +148,8 @@ class _GalaxyWebViewState extends State<GalaxyWebView>
}
bool get _visible {
if (!mounted || !_foreground || !TickerMode.valuesOf(context).enabled) return false;
if (!mounted || !_foreground || !TickerMode.valuesOf(context).enabled)
return false;
if (ModalRoute.of(context)?.isCurrent == false) return false;
final box = context.findRenderObject();
if (box is! RenderBox || !box.attached || !box.hasSize) return true;
@@ -80,7 +80,8 @@ class _ZapButtonState extends State<ZapButton> {
}
bool get _canZap =>
widget.blockedReason == null && (widget.isHeroTime || widget.myPop >= widget.zapPop);
widget.blockedReason == null &&
(widget.isHeroTime || widget.myPop >= widget.zapPop);
@override
Widget build(BuildContext context) => AnimatedSize(
@@ -180,7 +181,9 @@ class _ZapButtonState extends State<ZapButton> {
icon: const Icon(Icons.bolt),
label: Text(
_canZap
? widget.isHeroTime ? 'ZAP+ · HERO TIME' : 'ZAP ${widget.zapPop} POP'
? widget.isHeroTime
? 'ZAP+ · HERO TIME'
: 'ZAP ${widget.zapPop} POP'
: (widget.blockedReason ?? context.l10n.zapNotEnoughPop),
),
),