216 lines
6.8 KiB
Dart
216 lines
6.8 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 '../theme/app_theme.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: IndexedStack(
|
|
index: _currentIndex,
|
|
children: _pages,
|
|
),
|
|
bottomNavigationBar: NavigationBar(
|
|
selectedIndex: _currentIndex,
|
|
onDestinationSelected: (index) {
|
|
setState(() => _currentIndex = index);
|
|
},
|
|
backgroundColor: AppTheme.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],
|
|
);
|
|
}),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|