odoo_loyalty_app/lib/screens/main_shell.dart

228 lines
7.2 KiB
Dart

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<MainShell> createState() => _MainShellState();
}
class _MainShellState extends State<MainShell> {
int _currentIndex = 0;
int _unreadNotificationCount = 0;
Timer? _notificationTimer;
static const _kLastNotified = 'last_device_notified_id';
late final List<Widget> _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<void> _loadAppConfig() async {
try {
await OdooService.getAppConfig();
} catch (_) {}
}
@override
void dispose() {
_notificationTimer?.cancel();
super.dispose();
}
Future<void> _fetchNotificationCount() async {
try {
final client = OdooService().client;
if (client == null) return;
final response = await client.callRPC(
'/api/loyalty/fetch_notifications',
'call',
{'last_id': 0},
);
if (response != null && response['status'] == 'success') {
final List<dynamic> notifs = response['data'] ?? [];
final prefs = await SharedPreferences.getInstance();
final lastNotifiedId = prefs.getInt(_kLastNotified) ?? 0;
final readIds = prefs.getStringList('read_notification_ids');
int unreadCount = 0;
if (readIds == null) {
final initialRead = notifs.map((n) => (n['id'] as int? ?? 0).toString()).toList();
await prefs.setStringList('read_notification_ids', initialRead);
unreadCount = 0;
} else {
unreadCount = notifs
.where((n) => !readIds.contains((n['id'] as int? ?? 0).toString()))
.length;
}
int highestNewId = lastNotifiedId;
final List<dynamic> 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(_kLastNotified, 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],
);
}),
),
);
},
);
}
}