import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/auth/auth_notifier.dart'; import '../../core/status/status_notifier.dart'; import '../../core/chat/chat_request_notifier.dart'; import '../../core/theme/halo_tokens.dart'; import '../../core/theme/widgets/widgets.dart'; import '../undangan/undangan_screen.dart' show undanganTabProvider; /// Bestie Home (mitra). Mirrors /// `figma-bestie/project/screens/v4.jsx::BestieHome` (online variant) + /// `figma-bestie/project/screens/v5.jsx::BestieHomeOffline` (offline variant). /// /// Lives inside the Home branch of the shell (`router.dart`), so the /// `BestieTabBar` is rendered by `ShellScreen` — this screen owns body /// content only. class HomeScreen extends ConsumerWidget { const HomeScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final authState = ref.watch(mitraAuthProvider); final authData = authState.valueOrNull; final displayName = authData is MitraAuthAuthenticatedData ? (authData.profile['display_name'] as String? ?? 'Bestie') : 'Bestie'; final statusState = ref.watch(onlineStatusProvider); final isOnline = statusState is StatusLoadedData && statusState.isOnline; // Boot chat-request listener whenever mitra is online (existing logic). if (statusState is StatusLoadedData && statusState.isOnline) { final requestState = ref.watch(chatRequestProvider); if (requestState is ChatRequestIdleData) { Future.microtask(() { ref.read(chatRequestProvider.notifier).startListening(); ref.read(chatRequestProvider.notifier).loadPendingRequests(); }); } } ref.listen(onlineStatusProvider, (prev, next) { if (next is StatusLoadedData && next.isOnline) { ref.read(chatRequestProvider.notifier).startListening(); ref.read(chatRequestProvider.notifier).loadPendingRequests(); } else if (next is StatusLoadedData && !next.isOnline) { ref.read(chatRequestProvider.notifier).stopListening(); } }); return Scaffold( backgroundColor: HaloTokens.bg, body: SafeArea( child: isOnline ? _OnlineHome(displayName: displayName) : _OfflineHome(displayName: displayName), ), ); } } // ─── Online variant — matches v4.jsx:417 ────────────────────────────── class _OnlineHome extends StatelessWidget { final String displayName; const _OnlineHome({required this.displayName}); @override Widget build(BuildContext context) { return SingleChildScrollView( padding: const EdgeInsets.fromLTRB(20, 20, 20, 28), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _Header(displayName: displayName, isOnline: true), const SizedBox(height: 18), const _TilesGrid(), const SizedBox(height: 14), const _StatusCard(isOnline: true), const SizedBox(height: 10), const _GantiStatusButton(), const SizedBox(height: 22), const _Pengingat(), ], ), ); } } // ─── Offline variant — matches v5.jsx:188 ───────────────────────────── class _OfflineHome extends StatelessWidget { final String displayName; const _OfflineHome({required this.displayName}); @override Widget build(BuildContext context) { return SingleChildScrollView( padding: const EdgeInsets.fromLTRB(20, 20, 20, 28), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _Header(displayName: displayName, isOnline: false), const SizedBox(height: 28), Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: const Color(0xFFFCE8E8), borderRadius: HaloRadius.lg, border: Border.all(color: const Color(0xFFF5B5B5), width: 1.5), ), child: const Column( children: [ Text('😴', style: TextStyle(fontSize: 44)), SizedBox(height: 10), Text( 'Kamu lagi OFFLINE', textAlign: TextAlign.center, style: TextStyle( fontFamily: HaloTokens.fontDisplay, fontSize: 17, fontWeight: FontWeight.w700, color: Color(0xFF7A2828), ), ), SizedBox(height: 10), SizedBox( width: 260, child: Text( 'Gak terima curhat dulu. Istirahat dulu ya — nyalain ONLINE kalo udah siap lagi 💛', textAlign: TextAlign.center, style: TextStyle( fontFamily: HaloTokens.fontBody, fontSize: 13, height: 1.5, color: Color(0xFF9C4040), ), ), ), ], ), ), const SizedBox(height: 16), const _GantiStatusButton(offlineLabel: 'Nyalain Status (Online)'), ], ), ); } } /// Home header — greeting block. Logout moved to Profil tab in Stage 4 /// (was a `more_horiz` bottom-sheet menu here pre-Stage-4). class _Header extends StatelessWidget { final String displayName; final bool isOnline; const _Header({required this.displayName, required this.isOnline}); @override Widget build(BuildContext context) { final greetingSuffix = isOnline ? '🌸' : '🌙'; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Hei,', style: TextStyle( fontFamily: HaloTokens.fontBody, fontSize: 13, color: HaloTokens.inkSoft, ), ), Text( 'Bestie $displayName $greetingSuffix', style: const TextStyle( fontFamily: HaloTokens.fontDisplay, fontSize: 24, fontWeight: FontWeight.w700, color: HaloTokens.brandDark, letterSpacing: -0.4, ), ), ], ); } } class _TilesGrid extends ConsumerWidget { const _TilesGrid(); @override Widget build(BuildContext context, WidgetRef ref) { ref.watch(chatRequestProvider); final undanganCount = ref.read(chatRequestProvider.notifier).activeRequestCount; final shell = StatefulNavigationShell.of(context); // Both tiles route to the Chat tab's Undangan screen, differing only in // which sub-tab they pre-select via `undanganTabProvider`. Tiles must // stay tappable even at count=0 (destination shows its empty state). return Row( children: [ Expanded( child: _DarkTile( icon: '📨', label: 'Undangan', subtitle: undanganCount > 0 ? 'Menunggu: $undanganCount' : 'Belum ada', badgeCount: undanganCount, onTap: () { ref.read(undanganTabProvider.notifier).state = 0; shell.goBranch(1); }, ), ), const SizedBox(width: 10), // Perpanjang tile — `MitraExtension` notifier holds per-flow state // only (idle/responding/...), no pending-list. Keep static "Belum ada" // until backend exposes a pending-extension count. // TODO(stage-2): wire extension count when extension_notifier // exposes it (or once a dedicated pending-extensions provider exists). Expanded( child: _DarkTile( icon: '⚡', label: 'Perpanjang', subtitle: 'Belum ada', badgeCount: 0, onTap: () { ref.read(undanganTabProvider.notifier).state = 1; shell.goBranch(1); }, ), ), ], ); } } class _DarkTile extends StatelessWidget { final String icon; final String label; final String subtitle; final int badgeCount; final VoidCallback? onTap; const _DarkTile({ required this.icon, required this.label, required this.subtitle, required this.badgeCount, required this.onTap, }); @override Widget build(BuildContext context) { final card = Container( padding: const EdgeInsets.all(14), decoration: const BoxDecoration( color: Color(0xFF2A1820), borderRadius: HaloRadius.lg, ), child: Stack( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text(icon, style: const TextStyle(fontSize: 18)), const SizedBox(height: 6), Text( label, style: const TextStyle( fontFamily: HaloTokens.fontBody, fontSize: 14, fontWeight: FontWeight.w700, color: Colors.white, ), ), const SizedBox(height: 2), Text( subtitle, style: const TextStyle( fontFamily: HaloTokens.fontBody, fontSize: 11, color: Color(0xB3FFFFFF), ), ), ], ), if (badgeCount > 0) Positioned( top: 0, right: 0, child: Container( width: 18, height: 18, alignment: Alignment.center, decoration: const BoxDecoration( color: Color(0xFFFF4D6A), shape: BoxShape.circle, ), child: Text( '$badgeCount', style: const TextStyle( fontFamily: HaloTokens.fontBody, color: Colors.white, fontSize: 11, fontWeight: FontWeight.w700, ), ), ), ), ], ), ); return Material( color: Colors.transparent, child: InkWell( borderRadius: HaloRadius.lg, onTap: onTap, child: card, ), ); } } class _StatusCard extends StatelessWidget { final bool isOnline; const _StatusCard({required this.isOnline}); @override Widget build(BuildContext context) { final bgColor = isOnline ? const Color(0xFFE8F7EE) : const Color(0xFFFCE8E8); final borderColor = isOnline ? const Color(0xFF9DD9B1) : const Color(0xFFF5B5B5); final titleColor = isOnline ? const Color(0xFF1F6B3B) : const Color(0xFF7A2828); final subColor = isOnline ? const Color(0xFF3F8956) : const Color(0xFF9C4040); final dot = isOnline ? '🟢' : '🔴'; return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: bgColor, borderRadius: HaloRadius.md, border: Border.all(color: borderColor), ), child: Row( children: [ Text(dot, style: const TextStyle(fontSize: 14)), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Kamu lagi ${isOnline ? 'ONLINE' : 'OFFLINE'}', style: TextStyle( fontFamily: HaloTokens.fontBody, fontSize: 13, fontWeight: FontWeight.w700, color: titleColor, ), ), Text( isOnline ? 'siap menerima curhat baru' : 'gak terima curhat dulu', style: TextStyle( fontFamily: HaloTokens.fontBody, fontSize: 11, color: subColor, ), ), ], ), ), ], ), ); } } class _GantiStatusButton extends ConsumerWidget { /// Optional label override for the offline variant (v5.jsx uses /// "Nyalain Status (Online)"). Defaults to "Ganti Status" for the online /// variant (v4.jsx). final String? offlineLabel; const _GantiStatusButton({this.offlineLabel}); @override Widget build(BuildContext context, WidgetRef ref) { final statusState = ref.watch(onlineStatusProvider); final isOnline = statusState is StatusLoadedData && statusState.isOnline; final isLoading = statusState is StatusLoadingData; final label = isLoading ? 'memproses...' : (isOnline ? 'Ganti Status' : (offlineLabel ?? 'Ganti Status')); return HaloButton( label: label, fullWidth: true, onPressed: isLoading ? null : () { final notifier = ref.read(onlineStatusProvider.notifier); if (isOnline) { notifier.toggleOffline(); } else { notifier.toggleOnline(); } }, ); } } class _Pengingat extends StatelessWidget { const _Pengingat(); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding( padding: EdgeInsets.only(bottom: 8), child: Text( 'Pengingat', style: TextStyle( fontFamily: HaloTokens.fontBody, fontSize: 13, fontWeight: FontWeight.w700, color: HaloTokens.brandDark, ), ), ), Container( padding: const EdgeInsets.all(12), decoration: const BoxDecoration( color: Color(0xFFEEE7F5), borderRadius: HaloRadius.md, ), child: const Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('💜', style: TextStyle(fontSize: 16)), SizedBox(width: 10), Expanded( child: Text.rich( TextSpan( style: TextStyle( fontFamily: HaloTokens.fontBody, fontSize: 12.5, color: HaloTokens.ink, height: 1.45, ), children: [ TextSpan( text: 'Opening protocol: ', style: TextStyle(fontWeight: FontWeight.w700), ), TextSpan( text: 'selalu mulai dengan pertanyaan terbuka yang hangat ya, Bestie.', ), ], ), ), ), ], ), ), ], ); } }