import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../dashboard/views/dashboard_view.dart'; import '../../invoices/views/invoices_list_view.dart'; import '../../notifications/views/notifications_view.dart'; import '../../settings/views/settings_view.dart'; import '../controllers/main_shell_controller.dart'; import '../../../app/routes/app_pages.dart'; import '../../../core/services/upload_progress_service.dart'; class MainShellView extends StatefulWidget { const MainShellView({super.key}); @override State createState() => _MainShellViewState(); } class _MainShellViewState extends State { final MainShellController _shellController = Get.find(); // Must be find(), not put(): main.dart already registers the permanent // instance. Re-putting it here swapped in a second, empty instance that other // controllers were no longer holding, so progress updates went nowhere. final UploadProgressService _progressService = Get.find(); // 5 pages: Home(0), Invoices(1), [Scanner FAB](2), Notifications(3), Settings(4) final List _pages = const [ DashboardView(), // 0 InvoicesListView(), // 1 SizedBox(), // 2 - Scanner (FAB placeholder) NotificationsView(), // 3 SettingsView(), // 4 ]; @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; final navBg = isDark ? const Color(0xFF1A1A2E) : Colors.white; const activeColor = Color(0xFF0F4C81); final inactiveColor = isDark ? Colors.white38 : const Color(0xFF94A3B8); return Scaffold( backgroundColor: isDark ? const Color(0xFF121212) : const Color(0xFFF5F7FA), body: Stack( children: [ Obx( () => IndexedStack( index: _getPageIndex(_shellController.currentIndex.value), children: [ _pages[0], // Dashboard _pages[1], // Invoices _pages[3], // Notifications _pages[4], // Settings ], ), ), // Global Upload Progress Overlay Obx(() => _progressService.isUploading.value ? Positioned( bottom: 80, left: 16, right: 16, child: _buildUploadOverlay(isDark), ) : const SizedBox.shrink()), ], ), floatingActionButton: _buildScannerFAB(), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, bottomNavigationBar: BottomAppBar( shape: const CircularNotchedRectangle(), notchMargin: 8, color: navBg, elevation: 16, child: SizedBox( height: 60, child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ // Left side (2 items) _buildNavItem(0, Icons.home_rounded, 'الرئيسية', activeColor, inactiveColor), _buildNavItem(1, Icons.receipt_long_rounded, 'الفواتير', activeColor, inactiveColor), // Center gap for FAB const SizedBox(width: 48), // Right side (2 items) _buildNavItem(3, Icons.notifications_rounded, 'الإشعارات', activeColor, inactiveColor), _buildNavItem(4, Icons.settings_rounded, 'الإعدادات', activeColor, inactiveColor), ], ), ), ), ); } int _getPageIndex(int navIndex) { // Map nav index to page index (skip scanner placeholder at 2) if (navIndex <= 1) return navIndex; if (navIndex == 3) return 2; // Notifications if (navIndex == 4) return 3; // Settings return 0; } Widget _buildNavItem( int index, IconData icon, String label, Color active, Color inactive) { return Expanded( child: Obx(() { final isSelected = _shellController.currentIndex.value == index; return InkWell( onTap: () => _shellController.selectTab(index), borderRadius: BorderRadius.circular(12), child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ AnimatedContainer( duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), decoration: isSelected ? BoxDecoration( color: active.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(16), ) : null, child: Icon(icon, color: isSelected ? active : inactive, size: 22), ), const SizedBox(height: 2), Text( label, style: TextStyle( fontSize: 10, fontWeight: isSelected ? FontWeight.w700 : FontWeight.w400, color: isSelected ? active : inactive, ), ), ], ), ); }), ); } Widget _buildScannerFAB() { return Container( width: 56, height: 56, decoration: BoxDecoration( shape: BoxShape.circle, gradient: const LinearGradient( colors: [Color(0xFFD4AF37), Color(0xFFF0D060)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), boxShadow: [ BoxShadow( color: const Color(0xFFD4AF37).withValues(alpha: 0.4), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: FloatingActionButton( onPressed: () => Get.toNamed(AppRoutes.SCANNER), backgroundColor: Colors.transparent, elevation: 0, heroTag: 'scanner_fab', child: const Icon(Icons.document_scanner_rounded, color: Colors.white, size: 26), ), ); } Widget _buildUploadOverlay(bool isDark) { final status = _progressService.status.value; final progress = _progressService.progress.value; // Terminal states must look different from in-progress ones, otherwise a // failed or partially failed batch is indistinguishable from a success. final bool isTerminal = status == 'done' || status == 'partial' || status == 'failed'; final Color accentColor = switch (status) { 'done' => const Color(0xFF10B981), 'partial' => const Color(0xFFF59E0B), 'failed' => const Color(0xFFEF4444), _ => const Color(0xFF0F4C81), }; final String statusText = switch (status) { 'uploading' => 'جاري رفع الصور...', 'processing' => 'جاري استخراج البيانات...', 'done' => 'اكتملت المعالجة ✓', 'partial' => 'اكتملت المعالجة مع أخطاء', 'failed' => 'فشلت العملية', _ => 'جاري المعالجة...', }; final IconData? terminalIcon = switch (status) { 'done' => Icons.check_circle, 'partial' => Icons.warning_amber_rounded, 'failed' => Icons.error_outline, _ => null, }; final String detailText = _progressService.errorMessage.value.isNotEmpty ? _progressService.errorMessage.value : '${_progressService.companyName.value} • ${_progressService.currentImageIndex.value}/${_progressService.totalImages.value}'; return Card( elevation: 8, shadowColor: Colors.black26, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), color: isDark ? const Color(0xFF1E1E2E) : Colors.white, child: Padding( padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( children: [ terminalIcon != null ? Icon(terminalIcon, color: accentColor, size: 24) : const SizedBox( width: 24, height: 24, child: CircularProgressIndicator( strokeWidth: 2, color: Color(0xFF0F4C81))), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(statusText, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 13, color: status == 'failed' ? accentColor : null)), Text( detailText, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 11, color: isDark ? Colors.white38 : Colors.grey), ), ], ), ), if (isTerminal) // A terminal card no longer moves on its own, so give the user // a way to clear it instead of waiting out a timer. IconButton( icon: const Icon(Icons.close, size: 18), color: isDark ? Colors.white38 : Colors.grey, onPressed: _progressService.dismiss, tooltip: 'إخفاء', ) else Text( '${(progress * 100).toInt()}%', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 14, color: accentColor), ), ], ), const SizedBox(height: 10), ClipRRect( borderRadius: BorderRadius.circular(4), child: LinearProgressIndicator( value: progress, backgroundColor: isDark ? Colors.white10 : const Color(0xFFE2E8F0), color: accentColor, minHeight: 6, ), ), ], ), ), ); } }