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
+2 -2
View File
@@ -8,7 +8,6 @@ import '../../../core/ui/hero_time_status.dart';
import 'growth_conditions.dart';
import 'dart:math' as math;
import 'package:flutter/services.dart' show HapticFeedback;
import 'dart:ui' show ImageFilter;
import 'package:flutter/material.dart';
@@ -21,6 +20,7 @@ import '../../../core/data/planet_visual_state.dart';
import '../home_data.dart';
import '../home_screen.dart' show formatPop;
import '../../../core/i18n/app_locale.dart';
import '../../../core/ui/fx_intensity.dart';
/// 홈 상단 히어로 — Figma 사본 `252:159354` 홈의 `Frame 1984081117`.
///
@@ -1073,7 +1073,7 @@ class _TapShakeState extends State<_TapShake> with SingleTickerProviderStateMixi
Future<void> _tap() async {
final onTap = widget.onTap;
if (onTap == null || _shake.isAnimating) return;
HapticFeedback.lightImpact();
fxHaptic(FxIntensity.micro);
if (!MediaQuery.disableAnimationsOf(context)) await _shake.forward(from: 0);
if (mounted) onTap();
}
+49 -15
View File
@@ -10,13 +10,59 @@ import '../../../core/theme/tokens.dart';
///
/// 명세 `516:87664` *동작: 이벤트 발생 시 텍스트 우측에서 좌측으로 1회 슬라이드* — 문구가
/// 바뀔 때마다 오른쪽 밖에서 한 번 미끄러져 들어와 멈춘다.
///
/// [action] 이 있으면 문구 뒤에 행동 문구를 붙이고 띠 전체를 [onTap] 으로 누를 수 있다 —
/// UX·FX v1.5 §6 전광판 「HERO TIME 진행 중 CTA · 탭 시 해당 Planet 의 진행 중 HERO TIME 으로 이동」.
class LiveTicker extends StatelessWidget {
const LiveTicker({super.key, required this.text});
const LiveTicker({super.key, required this.text, this.action, this.onTap});
final String text;
final String? action;
final VoidCallback? onTap;
static const _style = TextStyle(
fontFamily: AppFonts.body,
fontSize: 12,
fontWeight: FontWeight.w500,
height: 14 / 12,
letterSpacing: -0.3,
color: AppColors.onCosmic,
);
@override
Widget build(BuildContext context) => ClipRect(
Widget build(BuildContext context) {
final line = Text.rich(
TextSpan(
text: text,
children: [
if (action case final cta?)
TextSpan(
text: ' $cta',
style: const TextStyle(
fontWeight: FontWeight.w700,
color: AppColors.cosmicAccent,
),
),
],
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: _style,
);
final ticker = _slide(line);
return onTap == null
? ticker
: Semantics(
button: true,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: ticker,
),
);
}
Widget _slide(Widget line) => ClipRect(
child: Container(
height: 22,
padding: const EdgeInsets.symmetric(horizontal: AppSpace.s4),
@@ -33,19 +79,7 @@ class LiveTicker extends StatelessWidget {
child: child,
),
),
child: Text(
text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontFamily: AppFonts.body,
fontSize: 12,
fontWeight: FontWeight.w500,
height: 14 / 12,
letterSpacing: -0.3,
color: AppColors.onCosmic,
),
),
child: line,
),
),
);