fix(windows): improve minimized sidebar layout and replace logo with expand icon

This commit is contained in:
ssbg04 committed 2026-10-13 05:26:22 +08:00
1 parent 9c341bab54
commit 418218fa94
1 file changed
+196 -112
@@ -197,9 +197,14 @@ class _WindowsSidebarLayoutState extends ConsumerState<WindowsSidebarLayout> {
color: isSelected color: isSelected
? AppColors.primaryGreen.withValues(alpha: 0.12) ? AppColors.primaryGreen.withValues(alpha: 0.12)
: Colors.transparent, : Colors.transparent,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(8),
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(8),
hoverColor: Theme.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.05),
onTap: () { onTap: () {
setState(() { setState(() {
_isTabLoading = true; _isTabLoading = true;
@@ -222,12 +227,42 @@ class _WindowsSidebarLayoutState extends ConsumerState<WindowsSidebarLayout> {
.setTab(tab['label'] as String); .setTab(tab['label'] as String);
_reloadTabContent(tab['label'] as String); _reloadTabContent(tab['label'] as String);
}, },
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: const NeverScrollableScrollPhysics(),
child: SizedBox( child: SizedBox(
width: 236, height: 42,
child: Padding( child: _isMinimized
? Stack(
alignment: Alignment.center,
children: [
if (isSelected)
Positioned(
left: 0,
child: Container(
width: 3.5,
height: 20,
decoration: BoxDecoration(
color: AppColors.primaryGreen,
borderRadius: BorderRadius.circular(2),
),
),
),
Center(
child: Icon(
(isSelected
? tab['activeIcon'] ?? tab['icon']
: tab['icon'])
as IconData,
color: isSelected
? AppColors.primaryGreen
: Theme.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.65),
size: 22,
),
),
],
)
: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 14.0, horizontal: 14.0,
vertical: 9.0, vertical: 9.0,
@@ -244,14 +279,11 @@ class _WindowsSidebarLayoutState extends ConsumerState<WindowsSidebarLayout> {
: Theme.of(context) : Theme.of(context)
.colorScheme .colorScheme
.onSurface .onSurface
.withValues(alpha: 0.6), .withValues(alpha: 0.65),
size: 20, size: 20,
), ),
const SizedBox(width: 16), const SizedBox(width: 14),
Expanded( Expanded(
child: AnimatedOpacity(
duration: const Duration(milliseconds: 200),
opacity: _isMinimized ? 0.0 : 1.0,
child: Text( child: Text(
tab['label'] as String, tab['label'] as String,
style: TextStyle( style: TextStyle(
@@ -270,7 +302,6 @@ class _WindowsSidebarLayoutState extends ConsumerState<WindowsSidebarLayout> {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
),
], ],
), ),
), ),
@@ -279,7 +310,6 @@ class _WindowsSidebarLayoutState extends ConsumerState<WindowsSidebarLayout> {
), ),
), ),
), ),
),
], ],
); );
} }
@@ -445,9 +475,9 @@ class _WindowsSidebarLayoutState extends ConsumerState<WindowsSidebarLayout> {
// WINDOWS SIDEBAR (Fixed Width: 260px) // WINDOWS SIDEBAR (Fixed Width: 260px)
// ========================================== // ==========================================
AnimatedContainer( AnimatedContainer(
duration: const Duration(milliseconds: 400), duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut, curve: Curves.easeInOutCubic,
width: _isMinimized ? 80 : 260, width: _isMinimized ? 72 : 260,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
border: Border( border: Border(
@@ -459,26 +489,59 @@ class _WindowsSidebarLayoutState extends ConsumerState<WindowsSidebarLayout> {
), ),
child: Column( child: Column(
children: [ children: [
SingleChildScrollView( // ==========================================
scrollDirection: Axis.horizontal, // SIDEBAR HEADER: EXPAND BUTTON (MINIMIZED) OR LOGO + TITLE + COLLAPSE
physics: const NeverScrollableScrollPhysics(), // ==========================================
child: SizedBox( _isMinimized
width: 260, ? Padding(
child: Padding( padding: const EdgeInsets.symmetric(
vertical: 14.0,
),
child: Center(
child: Tooltip(
message: 'Expand Sidebar',
waitDuration:
const Duration(milliseconds: 300),
child: Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(8),
child: InkWell(
borderRadius: BorderRadius.circular(8),
hoverColor: Theme.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.08),
onTap: () {
setState(() => _isMinimized = false);
},
child: Container(
width: 40,
height: 40,
alignment: Alignment.center,
child: Icon(
Icons.view_sidebar_outlined,
color: Theme.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.8),
size: 22,
),
),
),
),
),
),
)
: Padding(
padding: const EdgeInsets.fromLTRB( padding: const EdgeInsets.fromLTRB(
20, 18,
20,
14, 14,
12, 12,
12,
), ),
child: Row( child: Row(
children: [ children: [
GestureDetector( GestureDetector(
onTap: () {
if (_isMinimized) {
setState(() => _isMinimized = false);
}
},
onLongPressStart: (_) { onLongPressStart: (_) {
_holdTimer = Timer( _holdTimer = Timer(
const Duration(seconds: 3), const Duration(seconds: 3),
@@ -487,12 +550,14 @@ class _WindowsSidebarLayoutState extends ConsumerState<WindowsSidebarLayout> {
}, },
); );
}, },
onLongPressEnd: (_) => _holdTimer?.cancel(), onLongPressEnd: (_) =>
_holdTimer?.cancel(),
onLongPressCancel: () => onLongPressCancel: () =>
_holdTimer?.cancel(), _holdTimer?.cancel(),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8), borderRadius:
BorderRadius.circular(8),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues( color: Colors.black.withValues(
@@ -504,69 +569,76 @@ class _WindowsSidebarLayoutState extends ConsumerState<WindowsSidebarLayout> {
], ],
), ),
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(8), borderRadius:
BorderRadius.circular(8),
child: Image.asset( child: Image.asset(
'assets/images/logo.webp', 'assets/images/logo.webp',
width: 36, width: 32,
height: 36, height: 32,
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
), ),
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 10),
Expanded( Expanded(
child: AnimatedOpacity(
duration: const Duration(
milliseconds: 200,
),
opacity: _isMinimized ? 0.0 : 1.0,
child: Text( child: Text(
'TIS RMS', 'TIS RMS',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 17,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.onSurface, .onSurface,
letterSpacing: 1.2, letterSpacing: 1.1,
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
), Tooltip(
AnimatedOpacity( message: 'Collapse Sidebar',
duration: const Duration(milliseconds: 200), waitDuration:
opacity: _isMinimized ? 0.0 : 1.0, const Duration(milliseconds: 300),
child: IconButton( child: Material(
icon: Icon( color: Colors.transparent,
Icons.menu, borderRadius: BorderRadius.circular(8),
child: InkWell(
borderRadius:
BorderRadius.circular(8),
hoverColor: Theme.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.08),
onTap: () {
setState(() => _isMinimized = true);
},
child: Container(
width: 36,
height: 36,
alignment: Alignment.center,
child: Icon(
Icons.view_sidebar_rounded,
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.onSurface .onSurface
.withValues(alpha: 0.6), .withValues(alpha: 0.65),
size: 21,
),
),
), ),
onPressed: () {
if (!_isMinimized) {
setState(() => _isMinimized = true);
}
},
tooltip: 'Minimize Sidebar',
), ),
), ),
], ],
), ),
), ),
), const SizedBox(height: 6),
),
const SizedBox(height: 8),
// Navigation Items: Single Unified Scrollable List // Navigation Items: Single Unified Scrollable List
Expanded( Expanded(
child: ListView.builder( child: ListView.builder(
padding: const EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: 12, horizontal: _isMinimized ? 12 : 12,
vertical: 4, vertical: 4,
), ),
itemCount: tabs.length, itemCount: tabs.length,
@@ -592,7 +664,7 @@ class _WindowsSidebarLayoutState extends ConsumerState<WindowsSidebarLayout> {
// Persistent Server Connection Status Pill // Persistent Server Connection Status Pill
Padding( Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: _isMinimized ? 12 : 20, horizontal: _isMinimized ? 8 : 20,
vertical: 8, vertical: 8,
), ),
child: Center( child: Center(
@@ -605,38 +677,31 @@ class _WindowsSidebarLayoutState extends ConsumerState<WindowsSidebarLayout> {
height: 1, height: 1,
color: Theme.of(context).dividerColor, color: Theme.of(context).dividerColor,
), ),
SingleChildScrollView( _isMinimized
scrollDirection: Axis.horizontal, ? Padding(
physics: const NeverScrollableScrollPhysics(), padding:
child: SizedBox( const EdgeInsets.symmetric(vertical: 12.0),
width: 260, child: Center(
child: Padding( child: Tooltip(
padding: const EdgeInsets.symmetric( message: ref
horizontal: 18.0, .watch(authProvider)
vertical: 12.0, .value
), ?.fullName ??
child: Row( 'User Profile',
children: [ waitDuration:
GestureDetector( const Duration(milliseconds: 300),
onTap: () {
if (_isMinimized) {
setState(() => _isMinimized = false);
}
},
child: Builder( child: Builder(
builder: (context) { builder: (context) {
final user = ref final user =
.watch(authProvider) ref.watch(authProvider).value;
.value; final initials = user != null &&
final initials =
user != null &&
user.firstName.isNotEmpty && user.firstName.isNotEmpty &&
user.lastName.isNotEmpty user.lastName.isNotEmpty
? '${user.firstName[0]}${user.lastName[0]}' ? '${user.firstName[0]}${user.lastName[0]}'
.toUpperCase() .toUpperCase()
: 'SA'; : 'SA';
return CircleAvatar( return CircleAvatar(
radius: 20, radius: 18,
backgroundColor: backgroundColor:
AppColors.primaryGreen, AppColors.primaryGreen,
child: Text( child: Text(
@@ -644,23 +709,53 @@ class _WindowsSidebarLayoutState extends ConsumerState<WindowsSidebarLayout> {
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 16, fontSize: 13,
), ),
), ),
); );
}, },
), ),
), ),
const SizedBox(width: 12),
Expanded(
child: AnimatedOpacity(
duration: const Duration(
milliseconds: 200,
), ),
opacity: _isMinimized ? 0.0 : 1.0, )
: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 14.0,
vertical: 12.0,
),
child: Row(
children: [
Builder(
builder: (context) {
final user =
ref.watch(authProvider).value;
final initials = user != null &&
user.firstName.isNotEmpty &&
user.lastName.isNotEmpty
? '${user.firstName[0]}${user.lastName[0]}'
.toUpperCase()
: 'SA';
return CircleAvatar(
radius: 18,
backgroundColor:
AppColors.primaryGreen,
child: Text(
initials,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 13,
),
),
);
},
),
const SizedBox(width: 10),
Expanded(
child: Column( child: Column(
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment.start, CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
ref ref
@@ -670,7 +765,7 @@ class _WindowsSidebarLayoutState extends ConsumerState<WindowsSidebarLayout> {
'Unknown User', 'Unknown User',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 14, fontSize: 13,
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.onSurface, .onSurface,
@@ -681,7 +776,7 @@ class _WindowsSidebarLayoutState extends ConsumerState<WindowsSidebarLayout> {
Text( Text(
widget.userRole.toUpperCase(), widget.userRole.toUpperCase(),
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 11,
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.onSurface .onSurface
@@ -691,50 +786,39 @@ class _WindowsSidebarLayoutState extends ConsumerState<WindowsSidebarLayout> {
], ],
), ),
), ),
), IconButton(
AnimatedOpacity(
duration: const Duration(milliseconds: 200),
opacity: _isMinimized ? 0.0 : 1.0,
child: IconButton(
icon: Icon( icon: Icon(
Icons.menu_book_outlined, Icons.menu_book_outlined,
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.onSurface .onSurface
.withValues(alpha: 0.6), .withValues(alpha: 0.6),
size: 19,
), ),
tooltip: 'User Guide & Documentation', tooltip: 'User Guide & Documentation',
visualDensity: VisualDensity.compact,
onPressed: () { onPressed: () {
if (!_isMinimized) {
DocumentationScreen.open(context); DocumentationScreen.open(context);
}
}, },
), ),
), IconButton(
AnimatedOpacity(
duration: const Duration(milliseconds: 200),
opacity: _isMinimized ? 0.0 : 1.0,
child: IconButton(
icon: Icon( icon: Icon(
Icons.logout, Icons.logout,
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.onSurface .onSurface
.withValues(alpha: 0.6), .withValues(alpha: 0.6),
size: 19,
), ),
tooltip: 'Logout', tooltip: 'Logout',
visualDensity: VisualDensity.compact,
onPressed: () { onPressed: () {
if (!_isMinimized) {
showLogoutConfirmationDialog(context); showLogoutConfirmationDialog(context);
}
}, },
), ),
),
], ],
), ),
), ),
),
),
], ],
), ),
), ),