Improved attendance screen navigations on mobile
This commit is contained in:
@@ -105,19 +105,7 @@ class _AttendanceScreenState extends ConsumerState<AttendanceScreen>
|
|||||||
title: 'Attendance',
|
title: 'Attendance',
|
||||||
subtitle: 'Check in, logbook, pass slip and leave',
|
subtitle: 'Check in, logbook, pass slip and leave',
|
||||||
),
|
),
|
||||||
TabBar(
|
_buildAttendanceNav(context),
|
||||||
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'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TabBarView(
|
child: TabBarView(
|
||||||
controller: _tabController,
|
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(
|
Widget _buildFabMenu(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
ThemeData theme,
|
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
|
// Tab 1 – Check In / Check Out
|
||||||
// ────────────────────────────────────────────────
|
// ────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user