import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_lucide/flutter_lucide.dart'; import 'package:get/get.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:on_field_work/helpers/services/storage/local_storage.dart'; import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart'; import 'package:on_field_work/helpers/widgets/my_spacing.dart'; import 'package:on_field_work/helpers/widgets/my_text.dart'; import 'package:on_field_work/helpers/widgets/avatar.dart'; import 'package:on_field_work/model/employees/employee_info.dart'; import 'package:on_field_work/controller/auth/mpin_controller.dart'; import 'package:on_field_work/view/employees/employee_profile_screen.dart'; import 'package:on_field_work/helpers/services/tenant_service.dart'; import 'package:on_field_work/view/tenant/tenant_selection_screen.dart'; import 'package:on_field_work/controller/tenant/tenant_switch_controller.dart'; import 'package:on_field_work/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; String _appVersion = ''; @override void initState() { super.initState(); _initData(); } Future _initData() async { employeeInfo = LocalStorage.getEmployeeInfo()!; hasMpin = await LocalStorage.getIsMpin(); // Fetch app version PackageInfo packageInfo = await PackageInfo.fromPlatform(); _appVersion = '${packageInfo.version}+${packageInfo.buildNumber}'; 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), if (!_isLoading && !isCondensed) _switchTenantRow(), MySpacing.height(12), Divider( indent: 18, endIndent: 18, thickness: 0.7, color: Colors.grey.withOpacity(0.25), ), MySpacing.height(12), _supportAndSettingsMenu(isCondensed), MySpacing.height(12), // Subtle version text for expanded mode if (!isCondensed && _appVersion.isNotEmpty) _versionText(), 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); }, ), ), // Floating badge for condensed mode if (isCondensed && _appVersion.isNotEmpty) _versionBadge(), ], ), ), ), ), ), ); } // =================== Version Widgets =================== Widget _versionText() { return Padding( padding: const EdgeInsets.only(top: 4, bottom: 12), child: Center( child: ClipRRect( borderRadius: BorderRadius.circular(16), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6), decoration: BoxDecoration( color: Colors.grey.shade100.withOpacity(0.85), borderRadius: BorderRadius.circular(16), border: Border.all( color: Colors.grey.shade200, width: 0.7, ), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.12), blurRadius: 3, offset: const Offset(0, 1), ), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.info_outline, size: 14, color: Colors.grey[700]), const SizedBox(width: 4), Text( 'Version: $_appVersion', style: TextStyle( fontSize: 12, color: Colors.grey[800], fontWeight: FontWeight.w600, ), ), ], ), ), ), ), ), ); } Widget _versionBadge() { return Positioned( bottom: 10, right: 14, child: ClipRRect( borderRadius: BorderRadius.circular(16), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), decoration: BoxDecoration( color: Colors.grey.shade100.withOpacity(0.85), borderRadius: BorderRadius.circular(16), border: Border.all( color: Colors.grey.shade300, width: 1, ), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.17), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Text( _appVersion, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Colors.black87, letterSpacing: 0.4, ), ), ), ), ), ); } // =================== Existing methods =================== Widget _switchTenantRow() { final TenantSwitchController tenantSwitchController = Get.put(TenantSwitchController()); return Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), child: Obx(() { if (tenantSwitchController.isLoading.value) { return _loadingTenantContainer(); } final tenants = tenantSwitchController.tenants; if (tenants.isEmpty) return _noTenantContainer(); // If only one organization, don't show switch option if (tenants.length == 1) { final selectedTenant = tenants.first; return Container( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey.shade300, width: 1), ), child: Row( children: [ ClipRRect( borderRadius: BorderRadius.circular(8), child: Container( width: 20, height: 20, color: Colors.grey.shade200, child: TenantLogo(logoImage: selectedTenant.logoImage), ), ), const SizedBox(width: 10), Expanded( child: Text( selectedTenant.name, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontWeight: FontWeight.bold, color: contentTheme.primary), ), ), const Icon(Icons.check_circle, color: Colors.green, size: 18), ], ), ); } final selectedTenant = TenantService.currentTenant; final sortedTenants = List.of(tenants); if (selectedTenant != null) { sortedTenants.sort((a, b) { if (a.id == selectedTenant.id) return -1; if (b.id == selectedTenant.id) return 1; return 0; }); } return PopupMenuButton( onSelected: (tenantId) => tenantSwitchController.switchTenant(tenantId), itemBuilder: (_) => sortedTenants.map((tenant) { return PopupMenuItem( value: tenant.id, child: Row( children: [ ClipRRect( borderRadius: BorderRadius.circular(8), child: Container( width: 20, height: 20, color: Colors.grey.shade200, child: TenantLogo(logoImage: tenant.logoImage), ), ), const SizedBox(width: 10), Expanded( child: Text( tenant.name, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontWeight: tenant.id == selectedTenant?.id ? FontWeight.bold : FontWeight.w600, color: tenant.id == selectedTenant?.id ? contentTheme.primary : Colors.black87, ), ), ), if (tenant.id == selectedTenant?.id) Icon(Icons.check_circle, color: contentTheme.primary, size: 18), ], ), ); }).toList(), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Icon(Icons.swap_horiz, color: contentTheme.primary), Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 6), child: Text( "Switch Organization", maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: contentTheme.primary, fontWeight: FontWeight.bold), ), ), ), Icon(Icons.arrow_drop_down, color: contentTheme.primary), ], ), ), ); }), ); } Widget _loadingTenantContainer() => Container( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.blue.shade200, width: 1), ), child: const Center(child: CircularProgressIndicator(strokeWidth: 2)), ); Widget _noTenantContainer() => Container( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.blue.shade200, width: 1), ), child: MyText.bodyMedium( "No tenants available", color: Colors.blueAccent, fontWeight: 600, ), ); 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: [ Icon(LucideIcons.log_out, size: 56, color: primaryColor), MySpacing.height(18), MyText.titleLarge( "Logout Confirmation", fontWeight: 700, textAlign: TextAlign.center, ), MySpacing.height(14), 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), 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()), ); } }