Improved attendance screen navigations on mobile
This commit is contained in:
@@ -105,19 +105,7 @@ class _AttendanceScreenState extends ConsumerState<AttendanceScreen>
|
||||
title: 'Attendance',
|
||||
subtitle: 'Check in, logbook, pass slip and leave',
|
||||
),
|
||||
TabBar(
|
||||
controller: _tabController,
|
||||
isScrollable: true,
|
||||
tabAlignment: TabAlignment.start,
|
||||
tabs: const [
|
||||
Tab(text: 'Check In'),
|
||||
Tab(text: 'Logbook'),
|
||||
Tab(text: 'Work Log'),
|
||||
Tab(text: 'My Schedule'),
|
||||
Tab(text: 'Pass Slip'),
|
||||
Tab(text: 'Leave'),
|
||||
],
|
||||
),
|
||||
_buildAttendanceNav(context),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
@@ -137,6 +125,35 @@ class _AttendanceScreenState extends ConsumerState<AttendanceScreen>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAttendanceNav(BuildContext context) {
|
||||
final isMobile = AppBreakpoints.isMobile(context);
|
||||
final isDesktop = AppBreakpoints.isDesktop(context);
|
||||
if (isMobile) {
|
||||
return _AttendanceSectionPicker(
|
||||
controller: _tabController,
|
||||
currentIndex: _tabController.index,
|
||||
);
|
||||
}
|
||||
return TabBar(
|
||||
controller: _tabController,
|
||||
isScrollable: isDesktop,
|
||||
tabAlignment: isDesktop ? TabAlignment.start : TabAlignment.fill,
|
||||
tabs: _attendanceTabs(shortLabels: !isDesktop),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _attendanceTabs({required bool shortLabels}) => [
|
||||
Tab(icon: const Icon(Icons.login_rounded, size: 18), text: 'Check In'),
|
||||
Tab(icon: const Icon(Icons.menu_book_outlined, size: 18), text: 'Logbook'),
|
||||
Tab(icon: const Icon(Icons.timer_outlined, size: 18), text: 'Work Log'),
|
||||
Tab(
|
||||
icon: const Icon(Icons.calendar_month_outlined, size: 18),
|
||||
text: shortLabels ? 'Schedule' : 'My Schedule',
|
||||
),
|
||||
Tab(icon: const Icon(Icons.directions_walk_outlined, size: 18), text: 'Pass Slip'),
|
||||
Tab(icon: const Icon(Icons.event_busy_outlined, size: 18), text: 'Leave'),
|
||||
];
|
||||
|
||||
Widget _buildFabMenu(
|
||||
BuildContext context,
|
||||
ThemeData theme,
|
||||
@@ -282,6 +299,122 @@ class _AttendanceScreenState extends ConsumerState<AttendanceScreen>
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
// Adaptive section picker — mobile only (<600dp)
|
||||
// ────────────────────────────────────────────────
|
||||
|
||||
class _AttendanceSectionPicker extends StatelessWidget {
|
||||
const _AttendanceSectionPicker({
|
||||
required this.controller,
|
||||
required this.currentIndex,
|
||||
});
|
||||
|
||||
final TabController controller;
|
||||
final int currentIndex;
|
||||
|
||||
static final _sections = <({IconData icon, String label})>[
|
||||
(icon: Icons.login_rounded, label: 'Check In'),
|
||||
(icon: Icons.menu_book_outlined, label: 'Logbook'),
|
||||
(icon: Icons.timer_outlined, label: 'Work Log'),
|
||||
(icon: Icons.calendar_month_outlined, label: 'My Schedule'),
|
||||
(icon: Icons.directions_walk_outlined, label: 'Pass Slip'),
|
||||
(icon: Icons.event_busy_outlined, label: 'Leave'),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final tt = Theme.of(context).textTheme;
|
||||
final current = _sections[currentIndex];
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.chevron_left),
|
||||
onPressed: currentIndex > 0
|
||||
? () => controller.animateTo(currentIndex - 1)
|
||||
: null,
|
||||
),
|
||||
Expanded(
|
||||
child: ActionChip(
|
||||
avatar: Icon(current.icon, size: 16, color: cs.primary),
|
||||
label: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
current.label,
|
||||
style: tt.labelLarge?.copyWith(color: cs.onSurface),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Icon(Icons.expand_more, size: 16, color: cs.onSurfaceVariant),
|
||||
],
|
||||
),
|
||||
backgroundColor: cs.surfaceContainerHighest,
|
||||
side: BorderSide.none,
|
||||
onPressed: () => _showSheet(context),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
onPressed: currentIndex < _sections.length - 1
|
||||
? () => controller.animateTo(currentIndex + 1)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showSheet(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final tt = Theme.of(context).textTheme;
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
showDragHandle: true,
|
||||
builder: (sheetContext) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('Attendance', style: tt.titleMedium),
|
||||
),
|
||||
),
|
||||
for (int i = 0; i < _sections.length; i++)
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
_sections[i].icon,
|
||||
color: i == currentIndex ? cs.primary : cs.onSurfaceVariant,
|
||||
),
|
||||
title: Text(
|
||||
_sections[i].label,
|
||||
style: i == currentIndex
|
||||
? tt.bodyMedium?.copyWith(
|
||||
color: cs.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
)
|
||||
: tt.bodyMedium,
|
||||
),
|
||||
selected: i == currentIndex,
|
||||
selectedTileColor: cs.primaryContainer.withValues(alpha: 0.3),
|
||||
onTap: () {
|
||||
controller.animateTo(i);
|
||||
Navigator.pop(sheetContext);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
// Tab 1 – Check In / Check Out
|
||||
// ────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user