UX·FX v1.5 — 햅틱 단계 · 라이브 띠 · HERO 전광판 · Push 우선순위 · Shield · 이클립스 성공·실패 연출

This commit is contained in:
Sn-Kinos
2026-10-06 20:12:44 +09:00
parent a04626b003
commit 62b9637db9
30 changed files with 851 additions and 118 deletions
+45 -21
View File
@@ -247,11 +247,21 @@ class _LiveTickerSlot extends StatelessWidget {
required this.eclipse,
required this.fandomId,
required this.fallback,
required this.fandomName,
required this.watchPlanet,
this.onHero,
});
final EclipseRepository eclipse;
final String fandomId;
final String? fallback;
final String fandomName;
/// HERO TIME 여부 · 공개 닉네임 — 행성 FX 와 같은 서버 확정 원천.
final PlanetWatch watchPlanet;
/// HERO TIME 줄을 눌렀을 때 — 이 행성의 진행 중 HERO TIME 참여로 바로 간다(v1.5 §6 「진입」).
final VoidCallback? onHero;
@override
Widget build(BuildContext context) => LiveStreamBuilder<EclipseState?>(
@@ -261,28 +271,36 @@ class _LiveTickerSlot extends StatelessWidget {
create: () => fandomId.isEmpty
? Stream<EclipseState?>.value(null)
: eclipse.watch(fandomId: fandomId),
builder: (context, eclipseState) => LiveStreamBuilder<List<LiveEvent>>(
key: ValueKey('live-$fandomId'),
// 띠는 한 줄만 그리므로 최신 몇 건만 받는다.
builder: (context, eclipseState) => LiveStreamBuilder<PlanetVisualState>(
key: ValueKey('live-planet-$fandomId'),
create: () => fandomId.isEmpty
? Stream.value(const <LiveEvent>[])
: eclipse.liveEvents(fandomId: fandomId, limit: 5),
builder: (context, events) {
final event = (events == null || events.isEmpty) ? null : events.first;
final started = eclipseState?.startAt;
// 발생 소식은 그 뒤에 온 이벤트(방어 참여 등)가 없을 때 띠에 오른다.
final eclipseNews =
eclipseState != null &&
eclipseState.result == null &&
(event == null || started == null || event.at.isBefore(started));
final latest = eclipseNews
? context.l10n.eclipseLiveOccurred(eclipseState.fandomName)
: event == null
? fallback
: liveEventMessage(event);
if (latest == null) return const SizedBox.shrink();
return LiveTicker(text: latest);
},
? const Stream<PlanetVisualState>.empty()
: watchPlanet(fandomId),
builder: (context, planet) => LiveStreamBuilder<List<LiveEvent>>(
key: ValueKey('live-$fandomId'),
// 띠는 한 줄만 그리므로 최신 몇 건만 받는다.
create: () => fandomId.isEmpty
? Stream.value(const <LiveEvent>[])
: eclipse.liveEvents(fandomId: fandomId, limit: 5),
builder: (context, events) {
final line = tickerLine(
eclipse: eclipseState,
heroTime: planet?.isHeroTime == true,
heroNicknames: planet?.heroNicknames ?? const [],
events: events ?? const [],
fandomName: fandomName,
now: DateTime.now(),
fallback: fallback,
);
if (line == null) return const SizedBox.shrink();
final hero = line.hero && onHero != null;
return LiveTicker(
text: line.text,
action: hero ? context.l10n.heroParticipate : null,
onTap: hero ? onHero : null,
);
},
),
),
);
}
@@ -532,6 +550,9 @@ class _HomeBody extends StatelessWidget {
eclipse: eclipse,
fandomId: data.fandomId,
fallback: data.liveTicker,
fandomName: data.growth.fandomName,
watchPlanet: watchPlanet,
onHero: _heroParticipate(context),
),
onClose: () => onMap(false),
onSearch: () => _openSearch(context),
@@ -547,6 +568,9 @@ class _HomeBody extends StatelessWidget {
eclipse: eclipse,
fandomId: data.fandomId,
fallback: data.liveTicker,
fandomName: data.growth.fandomName,
watchPlanet: watchPlanet,
onHero: _heroParticipate(context),
),
growth: growth ?? data.growth,
fandomId: data.fandomId,