import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../services/odoo_service.dart'; import '../services/notification_service.dart'; import '../services/theme_manager.dart'; import 'notifications_screen.dart'; import 'loyalty_dashboard.dart'; import 'branches_screen.dart'; import 'orders_screen.dart'; import 'account_screen.dart'; class MainShell extends StatefulWidget { final int partnerId; const MainShell({super.key, required this.partnerId}); @override State createState() => _MainShellState(); } class _MainShellState extends State { int _currentIndex = 0; int _unreadNotificationCount = 0; Timer? _notificationTimer; late final List _pages; @override void initState() { super.initState(); _pages = [ LoyaltyDashboard(partnerId: widget.partnerId), const BranchesScreen(), const OrdersScreen(), const AccountScreen(), ]; _fetchNotificationCount(); _notificationTimer = Timer.periodic(const Duration(seconds: 30), (_) { _fetchNotificationCount(); }); _loadAppConfig(); } Future _loadAppConfig() async { try { await OdooService.getAppConfig(); } catch (_) {} } @override void dispose() { _notificationTimer?.cancel(); super.dispose(); } Future _fetchNotificationCount() async { try { if (OdooService().client == null) return; final response = await OdooService().callRPC( '/api/loyalty/fetch_notifications', 'call', {'last_id': 0}, ); if (response != null && response['status'] == 'success') { final List notifs = response['data'] ?? []; final prefs = await SharedPreferences.getInstance(); final partnerId = widget.partnerId; final keyLastNotified = 'last_device_notified_id_$partnerId'; final keyReadNotificationIds = 'read_notification_ids_$partnerId'; final lastNotifiedId = prefs.getInt(keyLastNotified) ?? 0; final readIds = prefs.getStringList(keyReadNotificationIds); int unreadCount = 0; if (readIds == null) { final initialRead = notifs.map((n) => (n['id'] as int? ?? 0).toString()).toList(); await prefs.setStringList(keyReadNotificationIds, initialRead); unreadCount = 0; } else { unreadCount = notifs .where((n) => !readIds.contains((n['id'] as int? ?? 0).toString())) .length; } int highestNewId = lastNotifiedId; final List toNotify = []; for (var notif in notifs) { final id = notif['id'] as int? ?? 0; if (id > lastNotifiedId) { toNotify.add(notif); if (id > highestNewId) highestNewId = id; } } if (toNotify.isNotEmpty) { final notifService = NotificationService(); for (final notif in toNotify) { await notifService.showNotification( id: notif['id'] as int, title: notif['title'] ?? 'Mie Mapan', body: notif['body'] ?? '', ); } await prefs.setInt(keyLastNotified, highestNewId); } await NotificationService().setBadge(unreadCount); if (mounted) { setState(() => _unreadNotificationCount = unreadCount); } } } catch (e) { // ignore } } @override Widget build(BuildContext context) { final navLabels = ['Home', 'Branches', 'Orders', 'Account']; final navIcons = [ Icons.home_rounded, Icons.location_on_rounded, Icons.receipt_long_rounded, Icons.person_rounded, ]; return ListenableBuilder( listenable: ThemeManager.instance, builder: (context, _) { final colorScheme = Theme.of(context).colorScheme; return Scaffold( appBar: AppBar( title: ThemeManager.instance.brandLogo.isNotEmpty ? Image.memory( base64Decode(ThemeManager.instance.brandLogo), height: 36, fit: BoxFit.contain, errorBuilder: (context, error, stackTrace) => const Text('Mie Mapan'), ) : const Text('Mie Mapan'), actions: [ Stack( clipBehavior: Clip.none, children: [ IconButton( icon: const Icon(Icons.notifications_rounded), tooltip: 'Notifications', onPressed: () async { await Navigator.push( context, MaterialPageRoute(builder: (_) => const NotificationsScreen()), ); _fetchNotificationCount(); }, ), if (_unreadNotificationCount > 0) Positioned( right: 6, top: 6, child: Container( padding: const EdgeInsets.all(3), decoration: const BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), constraints: const BoxConstraints(minWidth: 18, minHeight: 18), child: Text( _unreadNotificationCount > 99 ? '99+' : '$_unreadNotificationCount', style: const TextStyle( color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), ), ), ], ), const SizedBox(width: 4), ], ), body: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ ThemeManager.instance.backgroundColor, ThemeManager.instance.backgroundColor, ThemeManager.instance.backgroundGradientColor, ], ), ), child: IndexedStack( index: _currentIndex, children: _pages, ), ), bottomNavigationBar: NavigationBar( selectedIndex: _currentIndex, onDestinationSelected: (index) { setState(() => _currentIndex = index); }, backgroundColor: colorScheme.surfaceContainerLowest, indicatorColor: colorScheme.primary, destinations: List.generate(4, (i) { return NavigationDestination( icon: Icon(navIcons[i], color: i == _currentIndex ? colorScheme.onSecondaryContainer : colorScheme.onSurfaceVariant), selectedIcon: Icon(navIcons[i], color: colorScheme.onSecondaryContainer), label: navLabels[i], ); }), ), ); }, ); } }