Figma 설명 프레임 반영 — 로그인·온보딩·스플래시 · HOME · GALAXY · 퀘스트·이클립스·알림
This commit is contained in:
@@ -16,6 +16,7 @@ import 'package:go_router/go_router.dart';
|
||||
import '../../core/format.dart';
|
||||
import '../../core/i18n/app_locale.dart';
|
||||
import '../../core/theme/tokens.dart';
|
||||
import '../../core/ui/app_dialog.dart' show slideUpRoute;
|
||||
import '../../core/ui/app_icons.dart';
|
||||
import '../../core/data/planet_visual_state.dart';
|
||||
import '../../core/ui/live_stream.dart';
|
||||
@@ -167,7 +168,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
||||
final created = await Navigator.of(
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).push(MaterialPageRoute<String>(builder: (_) => screen));
|
||||
).push(slideUpRoute<String>((_) => screen));
|
||||
if (created == null || !mounted) return;
|
||||
if (widget.galaxyRepository case final repository?) {
|
||||
setState(() {
|
||||
@@ -287,7 +288,7 @@ class _LiveTickerSlot extends StatelessWidget {
|
||||
}
|
||||
|
||||
/// 홈 헤더 — 성장 완료 · 첫 사용자 상태 화면도 같은 헤더를 쓴다.
|
||||
class HomeHeader extends StatelessWidget {
|
||||
class HomeHeader extends StatefulWidget {
|
||||
const HomeHeader({
|
||||
super.key,
|
||||
required this.pop,
|
||||
@@ -306,71 +307,116 @@ class HomeHeader extends StatelessWidget {
|
||||
final List<ItemProduct> items;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Padding(
|
||||
// 사본 `Top Navigation` — 높이 54 · 패딩 6/16 · 오른쪽 묶음은 알약 + 42 아이콘 두 개.
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpace.s4, vertical: 6),
|
||||
child: SizedBox(
|
||||
height: 42,
|
||||
child: Row(
|
||||
children: [
|
||||
const Text(
|
||||
'FANUZU',
|
||||
style: TextStyle(
|
||||
fontFamily: 'Pretendard',
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w900,
|
||||
height: 1.4,
|
||||
letterSpacing: -0.5,
|
||||
color: AppColors.cosmicAccent,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
_PopBalance(value: pop),
|
||||
IconButton(
|
||||
constraints: const BoxConstraints.tightFor(width: 42, height: 42),
|
||||
// 사본 `icon` 42 칸 — 테마 최소 크기(48)로 늘어나 POP 알약이 왼쪽으로 밀리지 않게.
|
||||
style: IconButton.styleFrom(
|
||||
minimumSize: const Size.square(42),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
icon: const Image(
|
||||
image: AssetImage('assets/figma/icon-wallet.png'),
|
||||
width: 42,
|
||||
height: 42,
|
||||
),
|
||||
// 사본 `252:159367` — 홈 주 프레임에 열린 「아이템 구입」 시트.
|
||||
onPressed: () => showItemSheet(
|
||||
context,
|
||||
items: items,
|
||||
purchase: true,
|
||||
fandomId: fandomId,
|
||||
),
|
||||
tooltip: context.l10n.homeItemsTooltip,
|
||||
),
|
||||
IconButton(
|
||||
constraints: const BoxConstraints.tightFor(width: 42, height: 42),
|
||||
// 사본 `icon` 42 칸 — 테마 최소 크기(48)로 늘어나 POP 알약이 왼쪽으로 밀리지 않게.
|
||||
style: IconButton.styleFrom(
|
||||
minimumSize: const Size.square(42),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
// 사본 `icon` 42 칸 안 lucide/bell 20.
|
||||
iconSize: 20,
|
||||
icon: const ImageIcon(AppIcons.bell, color: AppColors.onCosmic),
|
||||
// 알림 — 화면 대조 원장 F0 · `229:11265`.
|
||||
onPressed: () => Navigator.of(context, rootNavigator: true).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => NotificationScreen(repository: notifications),
|
||||
State<HomeHeader> createState() => _HomeHeaderState();
|
||||
}
|
||||
|
||||
class _HomeHeaderState extends State<HomeHeader> {
|
||||
/// 안 읽은 알림이 있는가 — 설명 프레임 `2097:16385` 「알림 - 알림 아이콘 / 알림 상태값 ·
|
||||
/// 알림 존재 시 배찌 알림으로 변경」. 알림함은 닫힐 때 읽음 상태를 돌려주므로(`518:88347`)
|
||||
/// 돌아오면 다시 읽는다.
|
||||
bool _unread = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_checkUnread();
|
||||
}
|
||||
|
||||
Future<void> _checkUnread() async {
|
||||
try {
|
||||
final items = await widget.notifications.load();
|
||||
if (mounted) setState(() => _unread = items.any((i) => !i.read));
|
||||
} catch (_) {
|
||||
// 읽지 못하면 배지를 띄우지 않는다 — 알림함 자체는 열린다.
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final pop = widget.pop;
|
||||
final items = widget.items;
|
||||
final fandomId = widget.fandomId;
|
||||
final notifications = widget.notifications;
|
||||
return Padding(
|
||||
// 사본 `Top Navigation` — 높이 54 · 패딩 6/16 · 오른쪽 묶음은 알약 + 42 아이콘 두 개.
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpace.s4, vertical: 6),
|
||||
child: SizedBox(
|
||||
height: 42,
|
||||
child: Row(
|
||||
children: [
|
||||
const Text(
|
||||
'FANUZU',
|
||||
style: TextStyle(
|
||||
fontFamily: 'Pretendard',
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w900,
|
||||
height: 1.4,
|
||||
letterSpacing: -0.5,
|
||||
color: AppColors.cosmicAccent,
|
||||
),
|
||||
),
|
||||
tooltip: context.l10n.homeNotificationsTooltip,
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
_PopBalance(value: pop),
|
||||
IconButton(
|
||||
constraints: const BoxConstraints.tightFor(width: 42, height: 42),
|
||||
// 사본 `icon` 42 칸 — 테마 최소 크기(48)로 늘어나 POP 알약이 왼쪽으로 밀리지 않게.
|
||||
style: IconButton.styleFrom(
|
||||
minimumSize: const Size.square(42),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
icon: const Image(
|
||||
image: AssetImage('assets/figma/icon-wallet.png'),
|
||||
width: 42,
|
||||
height: 42,
|
||||
),
|
||||
// 사본 `252:159367` — 홈 주 프레임에 열린 「아이템 구입」 시트.
|
||||
onPressed: () => showItemSheet(
|
||||
context,
|
||||
items: items,
|
||||
purchase: true,
|
||||
fandomId: fandomId,
|
||||
),
|
||||
tooltip: context.l10n.homeItemsTooltip,
|
||||
),
|
||||
IconButton(
|
||||
constraints: const BoxConstraints.tightFor(width: 42, height: 42),
|
||||
// 사본 `icon` 42 칸 — 테마 최소 크기(48)로 늘어나 POP 알약이 왼쪽으로 밀리지 않게.
|
||||
style: IconButton.styleFrom(
|
||||
minimumSize: const Size.square(42),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
// 사본 `icon` 42 칸 안 lucide/bell 20.
|
||||
iconSize: 20,
|
||||
// 배지 모양은 Figma 에 그림이 없어 테마 기본 점 배지(강조색)를 쓴다.
|
||||
icon: Badge(
|
||||
key: const ValueKey('home-bell-badge'),
|
||||
isLabelVisible: _unread,
|
||||
smallSize: 6,
|
||||
backgroundColor: AppColors.cosmicAccent,
|
||||
child: const ImageIcon(
|
||||
AppIcons.bell,
|
||||
color: AppColors.onCosmic,
|
||||
),
|
||||
),
|
||||
// 알림 — 화면 대조 원장 F0 · `229:11265`.
|
||||
onPressed: () async {
|
||||
await Navigator.of(context, rootNavigator: true).push(
|
||||
MaterialPageRoute<Object?>(
|
||||
builder: (_) =>
|
||||
NotificationScreen(repository: notifications),
|
||||
),
|
||||
);
|
||||
await _checkUnread();
|
||||
},
|
||||
tooltip: context.l10n.homeNotificationsTooltip,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HomeBody extends StatelessWidget {
|
||||
@@ -488,35 +534,7 @@ class _HomeBody extends StatelessWidget {
|
||||
fallback: data.liveTicker,
|
||||
),
|
||||
onClose: () => onMap(false),
|
||||
onSearch: () =>
|
||||
Navigator.of(
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => GalaxySearchScreen(
|
||||
repository: zap!.repository,
|
||||
onOpen: (p) =>
|
||||
Navigator.of(
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) =>
|
||||
PlanetDetailScreen(
|
||||
repository:
|
||||
planetRepository!(
|
||||
p.id,
|
||||
),
|
||||
watchPlanet:
|
||||
watchPlanet,
|
||||
eclipse: eclipse,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
onSearch: () => _openSearch(context),
|
||||
)
|
||||
: HomeHero(
|
||||
key: const ValueKey('home-hero'),
|
||||
@@ -604,9 +622,8 @@ class _HomeBody extends StatelessWidget {
|
||||
myPop: data.popBalance,
|
||||
),
|
||||
),
|
||||
// 행성 가입은 GALAXY 에서 한다(사본 시트 「행성 가입」).
|
||||
onJoin: () =>
|
||||
GoRouter.maybeOf(context)?.go('/galaxy'),
|
||||
// 설명 프레임 `516:87335` 「행성 가입 버튼 - 클릭 시 Galaxy 검색 화면 이동」.
|
||||
onJoin: () => _openSearch(context),
|
||||
zapButtonKey: zapKey,
|
||||
onHeroParticipate: _heroParticipate(context),
|
||||
eclipseButton: data.fandomId.isEmpty
|
||||
@@ -614,6 +631,7 @@ class _HomeBody extends StatelessWidget {
|
||||
: EclipseEventButton(
|
||||
repository: eclipse,
|
||||
fandomId: data.fandomId,
|
||||
nebula: (growth ?? data.growth).isNebula,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -842,6 +860,33 @@ class _HomeBody extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// 갤럭시 검색 — 확장 맵 돋보기(`2008:14560`)와 첫 사용자 시트 「행성 가입」(`516:87335`).
|
||||
/// 지도 저장소가 없으면(표본 모드) GALAXY 탭으로 물러난다.
|
||||
void _openSearch(BuildContext context) {
|
||||
final galaxyRepository = zap?.repository;
|
||||
if (galaxyRepository == null || planetRepository == null) {
|
||||
GoRouter.maybeOf(context)?.go('/galaxy');
|
||||
return;
|
||||
}
|
||||
final navigator = Navigator.of(context, rootNavigator: true);
|
||||
navigator.push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => GalaxySearchScreen(
|
||||
repository: galaxyRepository,
|
||||
onOpen: (p) => navigator.push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => PlanetDetailScreen(
|
||||
repository: planetRepository!(p.id),
|
||||
watchPlanet: watchPlanet,
|
||||
eclipse: eclipse,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
VoidCallback? _heroParticipate(BuildContext context) =>
|
||||
planetRepository == null || data.fandomId.isEmpty
|
||||
? null
|
||||
|
||||
Reference in New Issue
Block a user