import '../eclipse_data.dart'; import '../../../core/i18n/app_locale.dart'; /// 티커 한 줄의 문구. /// /// Figma 홈 상단 라이브 띠의 문장 형식을 그대로 쓴다 — /// *「[LIVE] …님의 아이템 사용으로 버프가 발동되었습니다!」*. /// 이클립스 방어는 **합류 1회**만 싣고 Shake 횟수는 싣지 않는다 — UX·FX v1.5 §5.1 「다른 팬의 행동은 /// 개별 Shake 횟수를 모두 노출하지 않고 참여자 수 변화 중심」. 집약 수는 방어 화면의 유효 참여자 표시다. /// /// **문구를 만드는 곳을 한 곳에 둔다.** 홈 티커와 갤럭시 섬광 배너가 각자 문장을 /// 조립하면 같은 이벤트가 두 화면에서 다르게 읽힌다. /// /// 별첨 B 「Global Ticker」로 Eclipse 외의 종류가 뒤에 들어오므로 종류마다 한 줄씩 /// 늘려 간다. 모르는 종류는 공지로 읽어 티커가 멈추지 않게 한다. String liveEventMessage(LiveEvent event) => switch (event.kind) { LiveEventKind.eclipseDefense => appL10n.liveEclipseDefense(event.nickname), LiveEventKind.artistHotTime => appL10n.liveHotTime(event.nickname), LiveEventKind.heroGift => appL10n.liveHeroGift(event.nickname), LiveEventKind.campaign => appL10n.liveCampaign(event.nickname), LiveEventKind.notice => '[LIVE] ${event.nickname}', }; /// 홈 라이브 띠(전광판)에 올릴 한 줄. [hero] 면 띠가 HERO TIME 참여 진입이 된다. /// [push] 는 같은 사건을 알리는 Push 의 `kind` — 띠가 떠 있으면 앱 안 Push 를 겹쳐 띄우지 않는다(v1.5 §1.1). typedef TickerLine = ({String text, bool hero, String? push}); /// 지금 화면에 보이는 HOME 라이브 띠의 사건 — `liveTickerKey(kind, fandomId)`. 띠가 그릴 때 적는다. /// ponytail: 전역 하나. HOME 이 띠를 하나만 그리므로 충분하다 — 띠가 여러 곳에 생기면 집합으로. String? liveTickerShowing; /// 띠 한 줄 고르기 — 원장 C-78 순서(위기 → 성취)대로 핵심 이벤트가 먼저 오른다. /// /// 1. 이클립스 발생 소식 — 그 뒤 이벤트(방어 참여 등)가 아직 없을 때. /// 2. 방어 중 집약 — UX·FX v1.5 §1.1 「개인 이벤트를 연속 티커로 쌓기보다 집단 상태로 집약」 · /// §5.1 「참여자 수 변화 중심」 · 기능명세 §2.10~§2.12 「지금 N 명이 함께 방어 중」. N 은 서버가 /// 인정한 고유 참여자 수([EclipseState.defense])이며 2명 이상일 때 집약한다(「다수」 해석 — 해석 확인 항목). /// 3. HERO TIME — v1.5 §6 전광판 「공개 닉네임 + 대상 팬덤 + 진행 중 CTA」. /// 4. 최신 이벤트 한 건, 없으면 [fallback]. TickerLine? tickerLine({ required EclipseState? eclipse, required bool heroTime, required List heroNicknames, required List events, required String fandomName, required DateTime now, String? fallback, }) { final event = events.isEmpty ? null : events.first; if (eclipse != null && eclipse.result == null) { final started = eclipse.startAt; if (event == null || started == null || event.at.isBefore(started)) { return ( text: appL10n.eclipseLiveOccurred(eclipse.fandomName), hero: false, push: 'eclipse', ); } if (eclipse.canShake(now) && eclipse.defense >= 2) { return ( text: appL10n.liveEclipseDefending('${eclipse.defense}'), hero: false, push: 'eclipse', ); } } if (heroTime) { return ( text: heroNicknames.isEmpty ? appL10n.liveHeroTime(fandomName) : appL10n.liveHeroTimeBy(fandomName, heroNicknames.join(' · ')), hero: true, // HERO TIME 발동 알림(영웅의 선물)은 알림함 `item_used` 로 온다. push: 'item_used', ); } if (event != null) { return (text: liveEventMessage(event), hero: false, push: null); } return fallback == null ? null : (text: fallback, hero: false, push: null); }