import 'package:flutter/material.dart'; import '../../core/theme/tokens.dart'; import 'station_data.dart'; import 'station_suggest_screen.dart'; import '../../core/i18n/app_locale.dart'; /// STATION 탭 루트 — S-S-HUB. /// /// **Figma 에 이 화면의 시안이 없다.** 근거는 견적서 v2.1 의 5탭 구성과 /// Firestore 설계서의 `stationLinks` · `stationSuggestions` 다. 그래서 데이터가 /// 정의한 것까지만 그렸고, 시안이 확정되면 그때 맞춘다. class StationScreen extends StatefulWidget { const StationScreen({ super.key, this.repository = const SampleStationRepository(), }); final StationRepository repository; @override State createState() => _StationScreenState(); } class _StationScreenState extends State { StationSnapshot? _snapshot; bool _loading = true; String? _error; StationCategory? _filter; @override void initState() { super.initState(); _load(); } Future _load() async { setState(() { _loading = true; _error = null; }); try { final snap = await widget.repository.load(); if (!mounted) return; setState(() { _snapshot = snap; _loading = false; }); } on Object { if (!mounted) return; setState(() { _loading = false; _error = context.l10n.stationLoadFailed; }); } } List get _visible { final links = _snapshot?.links ?? const []; final filter = _filter; if (filter == null) return links; return links.where((l) => l.category == filter).toList(); } @override Widget build(BuildContext context) { final t = Theme.of(context).textTheme; final snapshot = _snapshot; return Scaffold( appBar: AppBar( title: const Text('STATION'), bottom: PreferredSize( preferredSize: const Size.fromHeight(48), child: SingleChildScrollView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: AppSpace.s4), child: Row( children: [ ChoiceChip( label: Text(context.l10n.commonAll), selected: _filter == null, onSelected: (_) => setState(() => _filter = null), ), const SizedBox(width: AppSpace.s2), for (final c in StationCategory.values) ...[ ChoiceChip( label: Text(c.label), selected: _filter == c, onSelected: (_) => setState(() => _filter = c), ), const SizedBox(width: AppSpace.s2), ], ], ), ), ), ), floatingActionButton: (snapshot?.canSuggest ?? false) ? FloatingActionButton.extended( onPressed: () => Navigator.of(context, rootNavigator: true).push( MaterialPageRoute(builder: (_) => const StationSuggestScreen()), ), icon: const Icon(Icons.add_link), label: Text(context.l10n.stationSuggest), ) : null, body: switch ((_loading, _error, _visible.isEmpty)) { (true, _, _) => const Center(child: CircularProgressIndicator()), (_, final String message, _) => Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(message, style: t.titleMedium), const SizedBox(height: AppSpace.s4), FilledButton(onPressed: _load, child: Text(context.l10n.commonRetry)), ], ), ), (_, _, true) => Center( child: Padding( padding: const EdgeInsets.all(AppSpace.s5), child: Text( snapshot == null || snapshot.links.isEmpty ? context.l10n.stationEmpty : context.l10n.stationCategoryEmpty, style: t.bodyMedium, textAlign: TextAlign.center, ), ), ), _ => RefreshIndicator( onRefresh: _load, child: ListView.separated( padding: const EdgeInsets.fromLTRB( AppSpace.s4, AppSpace.s4, AppSpace.s4, AppSpace.s8, ), itemCount: _visible.length, separatorBuilder: (_, _) => const SizedBox(height: AppSpace.s3), itemBuilder: (context, i) => StationLinkTile(link: _visible[i]), ), ), }, ); } } /// 링크 한 줄. class StationLinkTile extends StatelessWidget { const StationLinkTile({super.key, required this.link, this.onTap}); final StationLink link; final VoidCallback? onTap; @override Widget build(BuildContext context) { final t = Theme.of(context).textTheme; return Material( color: AppColors.cosmicSurfaceContainer, borderRadius: BorderRadius.circular(AppRadius.md), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(AppRadius.md), child: Padding( padding: const EdgeInsets.all(AppSpace.s4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text(link.category.label, style: t.labelSmall), if (link.verified) ...[ const SizedBox(width: AppSpace.s2), Icon( Icons.verified_outlined, size: 14, color: AppColors.neonCyan, ), ], ], ), const SizedBox(height: AppSpace.s2), Text(link.title, style: t.titleMedium), if (link.description.isNotEmpty) ...[ const SizedBox(height: AppSpace.s1), Text(link.description, style: t.bodySmall), ], ], ), ), const SizedBox(width: AppSpace.s3), Icon(Icons.open_in_new, size: 18, color: t.labelSmall?.color), ], ), ), ), ); } }