import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_lucide/flutter_lucide.dart'; import 'package:get/get.dart'; import 'package:marco/helpers/services/storage/local_storage.dart'; import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/avatar.dart'; import 'package:marco/model/employees/employee_info.dart'; import 'package:marco/controller/auth/mpin_controller.dart'; import 'package:marco/view/employees/employee_profile_screen.dart'; import 'package:marco/helpers/theme/theme_editor_widget.dart'; class UserProfileBar extends StatefulWidget { final bool isCondensed; const UserProfileBar({Key? key, this.isCondensed = false}) : super(key: key); @override State createState() => _UserProfileBarState(); } class _UserProfileBarState extends State with SingleTickerProviderStateMixin, UIMixin { late EmployeeInfo employeeInfo; bool _isLoading = true; bool hasMpin = true; bool _isThemeEditorVisible = false; @override void initState() { super.initState(); _initData(); } Future _initData() async { employeeInfo = LocalStorage.getEmployeeInfo()!; hasMpin = await LocalStorage.getIsMpin(); setState(() => _isLoading = false); } @override Widget build(BuildContext context) { final bool isCondensed = widget.isCondensed; return Padding( padding: const EdgeInsets.only(left: 14), child: ClipRRect( borderRadius: BorderRadius.circular(22), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 18, sigmaY: 18), child: AnimatedContainer( duration: const Duration(milliseconds: 350), curve: Curves.easeInOut, width: isCondensed ? 84 : 260, decoration: BoxDecoration( gradient: LinearGradient( colors: [ Colors.white.withOpacity(0.95), Colors.white.withOpacity(0.85), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(22), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.06), blurRadius: 18, offset: const Offset(0, 8), ) ], border: Border.all( color: Colors.grey.withOpacity(0.25), width: 1, ), ), child: SafeArea( bottom: true, child: Stack( children: [ Offstage( offstage: _isThemeEditorVisible, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _isLoading ? const _LoadingSection() : _userProfileSection(isCondensed), MySpacing.height(12), Divider( indent: 18, endIndent: 18, thickness: 0.7, color: Colors.grey.withOpacity(0.25), ), MySpacing.height(12), _supportAndSettingsMenu(isCondensed), const Spacer(), Divider( indent: 18, endIndent: 18, thickness: 0.35, color: Colors.grey.withOpacity(0.18), ), _logoutButton(isCondensed), ], ), ), Offstage( offstage: !_isThemeEditorVisible, child: ThemeEditorWidget( onClose: () { setState(() => _isThemeEditorVisible = false); }, ), ), ], )), ), ), ), ); } Widget _userProfileSection(bool condensed) { final padding = MySpacing.fromLTRB( condensed ? 16 : 26, condensed ? 20 : 28, condensed ? 14 : 28, condensed ? 10 : 18, ); final avatarSize = condensed ? 48.0 : 64.0; return Padding( padding: padding, child: Row( children: [ Container( decoration: BoxDecoration( shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Theme.of(context).primaryColor.withOpacity(0.15), blurRadius: 10, spreadRadius: 1, ), ], border: Border.all( color: Theme.of(context).primaryColor, width: 2, ), ), child: Avatar( firstName: employeeInfo.firstName, lastName: employeeInfo.lastName, size: avatarSize, ), ), if (!condensed) ...[ MySpacing.width(16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.bodyLarge( '${employeeInfo.firstName} ${employeeInfo.lastName}', fontWeight: 700, color: Colors.indigo, ), MySpacing.height(4), MyText.bodySmall( "You're on track this month!", color: Colors.black54, ), ], ), ), ], ], ), ); } Widget _supportAndSettingsMenu(bool condensed) { final spacingHeight = condensed ? 8.0 : 14.0; return Padding( padding: const EdgeInsets.symmetric(horizontal: 14), child: Column( children: [ _menuItemRow( icon: LucideIcons.user, label: 'My Profile', onTap: _onProfileTap, ), SizedBox(height: spacingHeight), _menuItemRow( icon: LucideIcons.settings, label: 'Settings', onTap: () { setState(() { _isThemeEditorVisible = true; }); }, ), SizedBox(height: spacingHeight), _menuItemRow( icon: LucideIcons.badge_alert, label: 'Support', ), SizedBox(height: spacingHeight), _menuItemRow( icon: LucideIcons.lock, label: hasMpin ? 'Change MPIN' : 'Set MPIN', onTap: _onMpinTap, ), ], ), ); } Widget _menuItemRow({ required IconData icon, required String label, VoidCallback? onTap, Color? iconColor, Color? textColor, }) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( color: Colors.white.withOpacity(0.9), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey.withOpacity(0.2), width: 1), ), child: Row( children: [ Icon(icon, size: 22, color: iconColor ?? Colors.black87), const SizedBox(width: 16), Expanded( child: Text( label, style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: textColor ?? Colors.black87, ), ), ), const Icon(Icons.chevron_right, size: 20, color: Colors.black54), ], ), ), ); } void _onProfileTap() { Get.to(() => EmployeeProfilePage( employeeId: employeeInfo.id, )); } void _onMpinTap() { final controller = Get.put(MPINController()); if (hasMpin) controller.setChangeMpinMode(); Navigator.pushNamed(context, "/auth/mpin-auth"); } Widget _logoutButton(bool condensed) { return Padding( padding: MySpacing.all(14), child: SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: _showLogoutConfirmation, icon: const Icon(LucideIcons.log_out, size: 22, color: Colors.white), label: condensed ? const SizedBox.shrink() : MyText.bodyMedium( "Logout", color: Colors.white, fontWeight: 700, ), style: ElevatedButton.styleFrom( backgroundColor: contentTheme.primary, foregroundColor: Colors.white, shadowColor: contentTheme.primary, padding: EdgeInsets.symmetric( vertical: condensed ? 14 : 18, horizontal: condensed ? 14 : 22, ), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ), ), ); } Future _showLogoutConfirmation() async { final bool? confirm = await showDialog( context: context, builder: _buildLogoutDialog, ); if (confirm == true) await LocalStorage.logout(); } Widget _buildLogoutDialog(BuildContext context) { final theme = Theme.of(context); final primaryColor = contentTheme.primary; return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), elevation: 10, backgroundColor: theme.cardColor, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 34), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Top icon Icon(LucideIcons.log_out, size: 56, color: primaryColor), MySpacing.height(18), // Title MyText.titleLarge( "Logout Confirmation", fontWeight: 700, textAlign: TextAlign.center, ), MySpacing.height(14), // Subtitle MyText.bodyMedium( "Are you sure you want to logout?\nYou will need to login again to continue.", color: Colors.grey[700], textAlign: TextAlign.center, ), MySpacing.height(30), // Buttons Row( children: [ Expanded( child: ElevatedButton( onPressed: () => Navigator.pop(context, false), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14)), ), child: MyText.bodyMedium( "Cancel", color: Colors.white, fontWeight: 600, ), ), ), MySpacing.width(18), Expanded( child: ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom( backgroundColor: primaryColor, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14)), ), child: MyText.bodyMedium( "Logout", color: Colors.white, fontWeight: 600, ), ), ), ], ), ], ), ), ); } } class _LoadingSection extends StatelessWidget { const _LoadingSection(); @override Widget build(BuildContext context) { return const Padding( padding: EdgeInsets.symmetric(vertical: 44, horizontal: 8), child: Center(child: CircularProgressIndicator()), ); } }