Skip to content

Instantly share code, notes, and snippets.

@Slozzyondul
Last active July 9, 2025 16:30
Show Gist options
  • Select an option

  • Save Slozzyondul/e67e6a05261387f9e28a6edcbe13940a to your computer and use it in GitHub Desktop.

Select an option

Save Slozzyondul/e67e6a05261387f9e28a6edcbe13940a to your computer and use it in GitHub Desktop.
I am trying to reduce the spacing between the menu bar item when isCollapsed is true but still unable to figure out
import 'dart:io';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:prototype1/src/frontend/routing/app_router.dart';
import 'package:image_picker/image_picker.dart';
class DesktopLayout extends ConsumerStatefulWidget {
final Widget child;
const DesktopLayout({super.key, required this.child});
@override
ConsumerState<DesktopLayout> createState() => _DesktopLayoutState();
}
class _DesktopLayoutState extends ConsumerState<DesktopLayout> {
File? _selectedImage;
bool _isCollapsed = false;
bool _isInitialBuild = true;
final ImagePicker _picker = ImagePicker();
bool _isHealthExpanded = false;
bool _isSecurityExpanded = false;
// Dynamic spacing based on collapsed state
// Reduced spacing for collapsed state
double get _menuItemSpacing =>
_isCollapsed ? 0.0 : 2.0; // Changed from 2.0 to 0.0 or a very small value
Future<void> _pickImage() async {
final XFile? pickedFile =
await _picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
setState(() {
_selectedImage = File(pickedFile.path);
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Profile image selected: ${pickedFile.name}')),
);
}
}
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (_isInitialBuild && mounted) {
final screenWidth = MediaQuery.of(context).size.width;
if (screenWidth < 900) {
setState(() {
_isCollapsed = true;
});
}
_isInitialBuild = false;
}
}
@override
Widget build(BuildContext context) {
final currentLocation = GoRouterState.of(context).uri.path;
return Material(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Sidebar Navigation
Material(
elevation: 2,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: _isCollapsed ? 80 : 250,
color: Theme.of(context).colorScheme.surfaceContainerHighest,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height,
),
child: SingleChildScrollView(
primary: false,
dragStartBehavior: DragStartBehavior.down,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: _isCollapsed
? CrossAxisAlignment.center
: CrossAxisAlignment.stretch,
children: [
Padding(
padding: EdgeInsets.all(_isCollapsed ? 2.0 : 4.0),
child: IconButton(
icon:
Icon(_isCollapsed ? Icons.menu_open : Icons.menu),
tooltip: _isCollapsed
? 'Expand Sidebar'
: 'Collapse Sidebar',
onPressed: () {
setState(() {
_isCollapsed = !_isCollapsed;
});
},
),
),
AnimatedSwitcher(
duration: Duration(milliseconds: 200),
transitionBuilder:
(Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: ScaleTransition(
scale: animation,
child: child,
),
);
},
child: _isCollapsed
? SizedBox.shrink(key: ValueKey('collapsed_avatar'))
: Column(
key: ValueKey('expanded_avatar'),
children: [
GestureDetector(
onTap: _pickImage,
child: CircleAvatar(
radius: 40,
backgroundColor:
Theme.of(context).colorScheme.primary,
backgroundImage: _selectedImage != null
? FileImage(_selectedImage!)
: null,
child: _selectedImage == null
? Icon(Icons.person_add_alt_1,
size: 40,
color: Theme.of(context)
.colorScheme
.onPrimary)
: null,
),
),
SizedBox(height: _menuItemSpacing * 0),
],
),
),
// Menu items with dynamic spacing
...[
buildNavigationItems(
AppRoute.home,
context,
'Home',
Icons.home,
isCollapsed: _isCollapsed,
isSelected: currentLocation == '/',
),
buildNavigationItems(
AppRoute.attendance,
context,
'Attendance',
Icons.check_circle,
isCollapsed: _isCollapsed,
isSelected: currentLocation == '/attendance',
),
buildNavigationItems(
AppRoute.onlineAssessments,
context,
'Online Assessment',
Icons.assessment,
isCollapsed: _isCollapsed,
isSelected: currentLocation == '/onlineAssessments',
),
buildNavigationItems(
AppRoute.recordGrades,
context,
'Record Grades',
Icons.input,
isCollapsed: _isCollapsed,
isSelected: currentLocation == '/recordGrades',
),
buildNavigationItems(
AppRoute.communicationNotifications,
context,
'Communication & Notifications',
Icons.notifications,
isCollapsed: _isCollapsed,
isSelected: currentLocation ==
'/communicationNotifications'),
buildNavigationItems(
AppRoute.transport,
context,
'Transport',
Icons.motorcycle,
isCollapsed: _isCollapsed,
isSelected: currentLocation == '/transport',
),
// Health section
_isCollapsed
? PopupMenuButton<String>(
icon: Icon(Icons.health_and_safety,
color: Theme.of(context).iconTheme.color),
tooltip: 'Health',
onSelected: (value) {
if (value == 'medicalInventory') {
context.go('/medicalInventory');
}
},
itemBuilder: (context) => [
PopupMenuItem<String>(
value: 'medicalInventory',
child: Row(
children: [
Icon(Icons.medical_information,
size: 20),
SizedBox(width: 8),
Text('Medical Inventory'),
],
),
),
],
)
: Theme(
data: Theme.of(context)
.copyWith(dividerColor: Colors.transparent),
child: ExpansionTile(
initiallyExpanded: _isHealthExpanded,
onExpansionChanged: (expanded) {
setState(() {
_isHealthExpanded = expanded;
});
},
leading: Icon(Icons.health_and_safety,
color: Theme.of(context).iconTheme.color),
title: Text('Health',
style: Theme.of(context)
.textTheme
.bodyLarge),
trailing: Icon(_isHealthExpanded
? Icons.expand_less
: Icons.expand_more),
childrenPadding: EdgeInsets.only(left: 32.0),
children: [
buildNavigationItems(
AppRoute.medicalInventory,
context,
'Medical Inventory',
Icons.medical_information,
isCollapsed: _isCollapsed,
isSelected: currentLocation ==
'/medicalInventory',
),
],
),
),
buildNavigationItems(
AppRoute.finance,
context,
'Finance',
Icons.attach_money,
isCollapsed: _isCollapsed,
isSelected: currentLocation == '/finance',
),
buildNavigationItems(
AppRoute.viewStudent,
context,
'View Student',
Icons.people_alt_outlined,
isCollapsed: _isCollapsed,
isSelected: currentLocation == '/viewStudent',
),
buildNavigationItems(
AppRoute.teachers,
context,
'Teachers',
Icons.people,
isCollapsed: _isCollapsed,
isSelected: currentLocation == '/teachers',
),
buildNavigationItems(
AppRoute.settings,
context,
'Settings',
Icons.settings,
isCollapsed: _isCollapsed,
isSelected: currentLocation == '/settings',
),
// Security section
_isCollapsed
? PopupMenuButton<String>(
icon: Icon(Icons.security,
color: Theme.of(context).iconTheme.color),
tooltip: 'Security',
onSelected: (value) {
if (value == 'userManagement') {
context.go('/userManagement');
} else if (value == 'userPermmisions') {
context.go('/userPermmisions');
}
},
itemBuilder: (context) => [
PopupMenuItem<String>(
value: 'userManagement',
child: Row(
children: [
Icon(Icons.supervised_user_circle,
size: 20),
SizedBox(width: 8),
Text('User Management'),
],
),
),
PopupMenuItem<String>(
value: 'userPermmisions',
child: Row(
children: [
Icon(Icons.perm_identity, size: 20),
SizedBox(width: 8),
Text('User Permmisions'),
],
),
),
],
)
: Theme(
data: Theme.of(context)
.copyWith(dividerColor: Colors.transparent),
child: ExpansionTile(
initiallyExpanded: _isSecurityExpanded,
onExpansionChanged: (expanded) {
setState(() {
_isSecurityExpanded = expanded;
});
},
leading: Icon(Icons.security,
color: Theme.of(context).iconTheme.color),
title: Text('Security',
style: Theme.of(context)
.textTheme
.bodyLarge),
trailing: Icon(_isSecurityExpanded
? Icons.expand_less
: Icons.expand_more),
childrenPadding: EdgeInsets.only(left: 32.0),
children: [
buildNavigationItems(
AppRoute.userManagement,
context,
'User Management',
Icons.supervised_user_circle,
isCollapsed: _isCollapsed,
isSelected:
currentLocation == '/userManagement',
),
buildNavigationItems(
AppRoute.userPermmisions,
context,
'User Permmisions',
Icons.perm_identity,
isCollapsed: _isCollapsed,
isSelected:
currentLocation == '/userPermmisions',
),
],
),
),
buildNavigationItems(
AppRoute.schoolConfiguration,
context,
'School Configuration',
Icons.confirmation_num,
isCollapsed: _isCollapsed,
isSelected:
currentLocation == '/schoolConfiguration'),
]
.expand((widget) =>
[widget, SizedBox(height: _menuItemSpacing)])
.toList()
..removeLast(),
SizedBox(height: _menuItemSpacing * 2),
buildNavigationItems(
AppRoute.login,
context,
'Login',
Icons.logout,
isCollapsed: _isCollapsed,
isSelected: currentLocation == '/login',
),
],
),
),
),
),
),
// Main Content
Expanded(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
widget.child,
],
),
),
),
),
],
),
);
}
}
Widget buildNavigationItems(
AppRoute route,
BuildContext context,
String label,
IconData icon, {
bool isSelected = false,
bool isCollapsed = false,
}) {
if (isCollapsed) {
Widget iconWidget = Icon(
icon,
color: isSelected
? Theme.of(context)
.colorScheme
.onPrimary // Use onPrimary for selected icon color on primary background
: Theme.of(context)
.colorScheme
.onSurfaceVariant, // Use a suitable color for unselected icons
);
Widget collapsedItem = Tooltip(
message: label,
waitDuration: const Duration(milliseconds: 300),
child: InkWell(
onTap: () => _navigateToRoute(context, route),
borderRadius: BorderRadius.circular(8.0),
// Hover, splash, and highlight colors should be set for the InkWell to show clearly
hoverColor: Theme.of(context)
.colorScheme
.primary
.withOpacity(0.1), // Lighter hover
splashColor: Theme.of(context)
.colorScheme
.primary
.withOpacity(0.2), // Lighter splash
highlightColor: Theme.of(context)
.colorScheme
.primary
.withOpacity(0.15), // Lighter highlight
child: Container(
height: 48.0, // Standard dense ListTile height for consistency
alignment: Alignment.center,
decoration: BoxDecoration(
color: isSelected
? Theme.of(context)
.colorScheme
.primary // Use primary color for selected background
: Colors.transparent, // Transparent when not selected
borderRadius: BorderRadius.circular(8.0),
),
child: iconWidget,
),
),
);
return Padding(
// *** MODIFICATION HERE ***
// Reduced vertical padding for collapsed state
padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 2.0), // Reduced from 4.0 to 2.0
child: Material(
// Material for InkWell to draw on, and for shape/clipping
// The background color for the Material widget should typically be transparent
// or match the sidebar's background if you want a solid color behind it.
// It's currently `Theme.of(context).colorScheme.tertiary`, which might
// give an unexpected background for unselected items. Consider making it transparent.
color: Colors.transparent, // Changed to transparent
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
clipBehavior: Clip.antiAlias,
child: collapsedItem,
),
);
} else {
// Expanded state using ListTile
return Padding(
// Keep existing padding for expanded state
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: ListTile(
leading: Icon(icon),
title: Text(label),
selected: isSelected,
onTap: () => _navigateToRoute(context, route),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
),
);
}
}
void _navigateToRoute(BuildContext context, AppRoute route) {
if (context.mounted) {
debugPrint('Navigating to: ${route.name}');
context.goNamed(route.name);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment