Merge branch 'dev' into feature/centralized-data-error-handling and resolve conflicts

This commit is contained in:
2026-02-11 12:34:29 +05:30
158 changed files with 10945 additions and 5478 deletions

View File

@@ -5,12 +5,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:intl/intl.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../bloc/clock_in_bloc.dart';
import '../bloc/clock_in_event.dart';
import '../bloc/clock_in_state.dart';
import '../theme/app_colors.dart';
import '../widgets/commute_tracker.dart';
import '../widgets/date_selector.dart';
import '../widgets/lunch_break_modal.dart';
@@ -40,10 +38,10 @@ class _ClockInPageState extends State<ClockInPage> {
listener: (BuildContext context, ClockInState state) {
if (state.status == ClockInStatus.failure &&
state.errorMessage != null) {
ScaffoldMessenger.of(
UiSnackbar.show(
context,
).showSnackBar(
SnackBar(content: Text(translateErrorKey(state.errorMessage!))),
message: translateErrorKey(state.errorMessage!),
type: UiSnackbarType.error,
);
}
},
@@ -67,14 +65,6 @@ class _ClockInPageState extends State<ClockInPage> {
final bool isCheckedIn =
state.attendance.isCheckedIn && isActiveSelected;
// Format times for display
final String checkInStr = checkInTime != null
? DateFormat('h:mm a').format(checkInTime)
: '--:-- --';
final String checkOutStr = checkOutTime != null
? DateFormat('h:mm a').format(checkOutTime)
: '--:-- --';
return Scaffold(
appBar: UiAppBar(
titleWidget: Text(
@@ -94,7 +84,9 @@ class _ClockInPageState extends State<ClockInPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space5,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
@@ -113,26 +105,22 @@ class _ClockInPageState extends State<ClockInPage> {
// Date Selector
DateSelector(
selectedDate: state.selectedDate,
onSelect: (DateTime date) => _bloc.add(DateSelected(date)),
onSelect: (DateTime date) =>
_bloc.add(DateSelected(date)),
shiftDates: <String>[
DateFormat('yyyy-MM-dd').format(DateTime.now()),
],
),
const SizedBox(height: 20),
const SizedBox(height: UiConstants.space5),
// Your Activity Header
const Text(
Text(
"Your Activity",
textAlign: TextAlign.start,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColors.krowCharcoal,
),
style: UiTypography.headline4m,
),
const SizedBox(height: 16),
const SizedBox(height: UiConstants.space4),
// Selected Shift Info Card
if (todayShifts.isNotEmpty)
@@ -143,19 +131,21 @@ class _ClockInPageState extends State<ClockInPage> {
onTap: () =>
_bloc.add(ShiftSelected(shift)),
child: Container(
padding: const EdgeInsets.all(12),
margin:
const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(
UiConstants.space3,
),
margin: const EdgeInsets.only(
bottom: UiConstants.space3,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(
12,
),
color: UiColors.white,
borderRadius:
UiConstants.radiusLg,
border: Border.all(
color: shift.id ==
selectedShift?.id
? AppColors.krowBlue
: const Color(0xFFE2E8F0),
? UiColors.primary
: UiColors.border,
width:
shift.id == selectedShift?.id
? 2
@@ -176,34 +166,25 @@ class _ClockInPageState extends State<ClockInPage> {
selectedShift?.id
? "SELECTED SHIFT"
: "TODAY'S SHIFT",
style: TextStyle(
fontSize: 10,
fontWeight:
FontWeight.w600,
style: UiTypography
.titleUppercase4b
.copyWith(
color: shift.id ==
selectedShift?.id
? AppColors.krowBlue
: AppColors
.krowCharcoal,
letterSpacing: 0.5,
? UiColors.primary
: UiColors
.textSecondary,
),
),
const SizedBox(height: 2),
Text(
shift.title,
style: const TextStyle(
fontSize: 14,
fontWeight:
FontWeight.w600,
color: Color(0xFF1E293B),
),
style: UiTypography.body2b,
),
Text(
"${shift.clientName}${shift.location}",
style: const TextStyle(
fontSize: 12,
color: Color(0xFF64748B),
),
style: UiTypography.body3r
.textSecondary,
),
],
),
@@ -214,18 +195,14 @@ class _ClockInPageState extends State<ClockInPage> {
children: <Widget>[
Text(
"${_formatTime(shift.startTime)} - ${_formatTime(shift.endTime)}",
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF475569),
),
style: UiTypography.body3m
.textSecondary,
),
Text(
"\$${shift.hourlyRate}/hr",
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: AppColors.krowBlue,
style: UiTypography.body3m
.copyWith(
color: UiColors.primary,
),
),
],
@@ -239,46 +216,41 @@ class _ClockInPageState extends State<ClockInPage> {
),
// Swipe To Check In / Checked Out State / No Shift State
if (selectedShift != null && checkOutTime == null) ...<Widget>[
if (!isCheckedIn &&
!_isCheckInAllowed(selectedShift))
Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: const Color(0xFFF1F5F9), // slate-100
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: <Widget>[
const Icon(
LucideIcons.clock,
size: 48,
color: Color(0xFF94A3B8), // slate-400
),
const SizedBox(height: 16),
const Text(
"You're early!",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF475569), // slate-600
),
),
const SizedBox(height: 4),
Text(
"Check-in available at ${_getCheckInAvailabilityTime(selectedShift)}",
style: const TextStyle(
fontSize: 14,
color: Color(0xFF64748B), // slate-500
),
textAlign: TextAlign.center,
),
],
),
)
else
SwipeToCheckIn(
if (selectedShift != null &&
checkOutTime == null) ...<Widget>[
if (!isCheckedIn &&
!_isCheckInAllowed(selectedShift))
Container(
width: double.infinity,
padding:
const EdgeInsets.all(UiConstants.space6),
decoration: BoxDecoration(
color: UiColors.bgSecondary,
borderRadius: UiConstants.radiusLg,
),
child: Column(
children: <Widget>[
const Icon(
UiIcons.clock,
size: 48,
color: UiColors.iconThird,
),
const SizedBox(height: UiConstants.space4),
Text(
"You're early!",
style: UiTypography.body1m.textSecondary,
),
const SizedBox(height: UiConstants.space1),
Text(
"Check-in available at ${_getCheckInAvailabilityTime(selectedShift)}",
style: UiTypography.body2r.textSecondary,
textAlign: TextAlign.center,
),
],
),
)
else
SwipeToCheckIn(
isCheckedIn: isCheckedIn,
mode: state.checkInMode,
isLoading:
@@ -299,14 +271,17 @@ class _ClockInPageState extends State<ClockInPage> {
onCheckOut: () {
showDialog(
context: context,
builder: (BuildContext context) => LunchBreakDialog(
onComplete: () {
Navigator.of(
context,
).pop(); // Close dialog first
_bloc.add(const CheckOutRequested());
},
),
builder: (BuildContext context) =>
LunchBreakDialog(
onComplete: () {
Navigator.of(
context,
).pop(); // Close dialog first
_bloc.add(
const CheckOutRequested(),
);
},
),
);
},
),
@@ -314,13 +289,15 @@ class _ClockInPageState extends State<ClockInPage> {
checkOutTime != null) ...<Widget>[
// Shift Completed State
Container(
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(UiConstants.space6),
decoration: BoxDecoration(
color: const Color(0xFFECFDF5), // emerald-50
borderRadius: BorderRadius.circular(16),
color: UiColors.tagSuccess,
borderRadius: UiConstants.radiusLg,
border: Border.all(
color: const Color(0xFFA7F3D0),
), // emerald-200
color: UiColors.success.withValues(
alpha: 0.3,
),
),
),
child: Column(
children: <Widget>[
@@ -328,31 +305,24 @@ class _ClockInPageState extends State<ClockInPage> {
width: 48,
height: 48,
decoration: const BoxDecoration(
color: Color(0xFFD1FAE5), // emerald-100
color: UiColors.tagActive,
shape: BoxShape.circle,
),
child: const Icon(
LucideIcons.check,
color: Color(0xFF059669), // emerald-600
UiIcons.check,
color: UiColors.textSuccess,
size: 24,
),
),
const SizedBox(height: 12),
const Text(
const SizedBox(height: UiConstants.space3),
Text(
"Shift Completed!",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF065F46), // emerald-800
),
style: UiTypography.body1b.textSuccess,
),
const SizedBox(height: 4),
const Text(
const SizedBox(height: UiConstants.space1),
Text(
"Great work today",
style: TextStyle(
fontSize: 14,
color: Color(0xFF059669), // emerald-600
),
style: UiTypography.body2r.textSuccess,
),
],
),
@@ -361,29 +331,22 @@ class _ClockInPageState extends State<ClockInPage> {
// No Shift State
Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(UiConstants.space6),
decoration: BoxDecoration(
color: const Color(0xFFF1F5F9), // slate-100
borderRadius: BorderRadius.circular(16),
color: UiColors.bgSecondary,
borderRadius: UiConstants.radiusLg,
),
child: const Column(
child: Column(
children: <Widget>[
Text(
"No confirmed shifts for today",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Color(0xFF475569), // slate-600
),
style: UiTypography.body1m.textSecondary,
textAlign: TextAlign.center,
),
SizedBox(height: 4),
const SizedBox(height: UiConstants.space1),
Text(
"Accept a shift to clock in",
style: TextStyle(
fontSize: 14,
color: Color(0xFF64748B), // slate-500
),
style: UiTypography.body2r.textSecondary,
textAlign: TextAlign.center,
),
],
@@ -393,15 +356,17 @@ class _ClockInPageState extends State<ClockInPage> {
// Checked In Banner
if (isCheckedIn && checkInTime != null) ...<Widget>[
const SizedBox(height: 12),
const SizedBox(height: UiConstants.space3),
Container(
padding: const EdgeInsets.all(12),
padding: const EdgeInsets.all(UiConstants.space3),
decoration: BoxDecoration(
color: const Color(0xFFECFDF5), // emerald-50
borderRadius: BorderRadius.circular(12),
color: UiColors.tagSuccess,
borderRadius: UiConstants.radiusLg,
border: Border.all(
color: const Color(0xFFA7F3D0),
), // emerald-200
color: UiColors.success.withValues(
alpha: 0.3,
),
),
),
child: Row(
mainAxisAlignment:
@@ -411,23 +376,15 @@ class _ClockInPageState extends State<ClockInPage> {
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
const Text(
Text(
"Checked in at",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF059669),
),
style: UiTypography.body3m.textSuccess,
),
Text(
DateFormat(
'h:mm a',
).format(checkInTime),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFF065F46),
),
style: UiTypography.body1b.textSuccess,
),
],
),
@@ -435,12 +392,12 @@ class _ClockInPageState extends State<ClockInPage> {
width: 40,
height: 40,
decoration: const BoxDecoration(
color: Color(0xFFD1FAE5),
color: UiColors.tagActive,
shape: BoxShape.circle,
),
child: const Icon(
LucideIcons.check,
color: Color(0xFF059669),
UiIcons.check,
color: UiColors.textSuccess,
),
),
],
@@ -476,14 +433,14 @@ class _ClockInPageState extends State<ClockInPage> {
child: GestureDetector(
onTap: () => _bloc.add(CheckInModeChanged(value)),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8),
padding: const EdgeInsets.symmetric(vertical: UiConstants.space2),
decoration: BoxDecoration(
color: isSelected ? Colors.white : Colors.transparent,
borderRadius: BorderRadius.circular(8),
color: isSelected ? UiColors.white : UiColors.transparent,
borderRadius: UiConstants.radiusMd,
boxShadow: isSelected
? <BoxShadow>[
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 2,
offset: const Offset(0, 1),
),
@@ -496,15 +453,15 @@ class _ClockInPageState extends State<ClockInPage> {
Icon(
icon,
size: 16,
color: isSelected ? Colors.black : Colors.grey,
color: isSelected ? UiColors.foreground : UiColors.iconThird,
),
const SizedBox(width: 6),
Text(
label,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: isSelected ? Colors.black : Colors.grey,
style: UiTypography.body2m.copyWith(
color: isSelected
? UiColors.foreground
: UiColors.textSecondary,
),
),
],
@@ -534,36 +491,31 @@ class _ClockInPageState extends State<ClockInPage> {
height: 96,
decoration: BoxDecoration(
color: scanned
? Colors.green.shade50
: Colors.blue.shade50,
? UiColors.tagSuccess
: UiColors.tagInProgress,
shape: BoxShape.circle,
),
child: Icon(
scanned ? LucideIcons.check : LucideIcons.nfc,
scanned ? UiIcons.check : UiIcons.nfc,
size: 48,
color: scanned
? Colors.green.shade600
: Colors.blue.shade600,
color: scanned ? UiColors.textSuccess : UiColors.primary,
),
),
const SizedBox(height: 24),
const SizedBox(height: UiConstants.space6),
Text(
scanned ? 'Processing check-in...' : 'Ready to scan',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
style: UiTypography.headline4m,
),
const SizedBox(height: 8),
const SizedBox(height: UiConstants.space2),
Text(
scanned
? 'Please wait...'
: 'Hold your phone near the NFC tag at the clock-in station',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 14, color: Colors.grey.shade600),
style: UiTypography.body2r.textSecondary,
),
if (!scanned) ...<Widget>[
const SizedBox(height: 24),
const SizedBox(height: UiConstants.space6),
SizedBox(
width: double.infinity,
height: 56,
@@ -584,19 +536,16 @@ class _ClockInPageState extends State<ClockInPage> {
// But this dialog is just a function call.
// It's safer to just return a result
},
icon: const Icon(LucideIcons.nfc, size: 24),
label: const Text(
icon: const Icon(UiIcons.nfc, size: 24),
label: Text(
'Tap to Scan',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
style: UiTypography.headline4m.white,
),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0047FF),
foregroundColor: Colors.white,
backgroundColor: UiColors.primary,
foregroundColor: UiColors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: UiConstants.radiusLg,
),
),
),

View File

@@ -1,13 +0,0 @@
import 'package:flutter/material.dart';
class AppColors {
static const Color krowBlue = Color(0xFF0A39DF);
static const Color krowYellow = Color(0xFFFFED4A);
static const Color krowCharcoal = Color(0xFF121826);
static const Color krowMuted = Color(0xFF6A7382);
static const Color krowBorder = Color(0xFFE3E6E9);
static const Color krowBackground = Color(0xFFFAFBFC);
static const Color white = Colors.white;
static const Color black = Colors.black;
}

View File

@@ -1,10 +1,9 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
enum AttendanceType { checkin, checkout, breaks, days }
class AttendanceCard extends StatelessWidget {
const AttendanceCard({
super.key,
required this.type,
@@ -24,14 +23,14 @@ class AttendanceCard extends StatelessWidget {
final _AttendanceStyle styles = _getStyles(type);
return Container(
padding: const EdgeInsets.all(12),
padding: const EdgeInsets.all(UiConstants.space3),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade100),
color: UiColors.white,
borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.bgSecondary),
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 2,
offset: const Offset(0, 1),
),
@@ -46,17 +45,14 @@ class AttendanceCard extends StatelessWidget {
height: 32,
decoration: BoxDecoration(
color: styles.bgColor,
borderRadius: BorderRadius.circular(8),
borderRadius: UiConstants.radiusMd,
),
child: Icon(styles.icon, size: 16, color: styles.iconColor),
),
const SizedBox(height: 8),
const SizedBox(height: UiConstants.space2),
Text(
title,
style: const TextStyle(
fontSize: 11,
color: Color(0xFF64748B), // slate-500
),
style: UiTypography.titleUppercase4m.textSecondary,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@@ -65,27 +61,20 @@ class AttendanceCard extends StatelessWidget {
fit: BoxFit.scaleDown,
child: Text(
value,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF0F172A), // slate-900
),
style: UiTypography.headline4m,
),
),
if (scheduledTime != null) ...<Widget>[
const SizedBox(height: 2),
Text(
"Scheduled: $scheduledTime",
style: const TextStyle(
fontSize: 10,
color: Color(0xFF94A3B8), // slate-400
),
style: UiTypography.footnote2r.textInactive,
),
],
const SizedBox(height: 2),
Text(
subtitle,
style: const TextStyle(fontSize: 12, color: Color(0xFF0032A0)),
style: UiTypography.footnote1r.copyWith(color: UiColors.primary),
),
],
),
@@ -96,27 +85,27 @@ class AttendanceCard extends StatelessWidget {
switch (type) {
case AttendanceType.checkin:
return _AttendanceStyle(
icon: LucideIcons.logIn,
bgColor: const Color(0xFF0032A0).withOpacity(0.1),
iconColor: const Color(0xFF0032A0),
icon: UiIcons.logIn,
bgColor: UiColors.primary.withValues(alpha: 0.1),
iconColor: UiColors.primary,
);
case AttendanceType.checkout:
return _AttendanceStyle(
icon: LucideIcons.logOut,
bgColor: const Color(0xFF333F48).withOpacity(0.1),
iconColor: const Color(0xFF333F48),
icon: UiIcons.logOut,
bgColor: UiColors.foreground.withValues(alpha: 0.1),
iconColor: UiColors.foreground,
);
case AttendanceType.breaks:
return _AttendanceStyle(
icon: LucideIcons.coffee,
bgColor: const Color(0xFFF9E547).withOpacity(0.2),
iconColor: const Color(0xFF4C460D),
icon: UiIcons.coffee,
bgColor: UiColors.accent.withValues(alpha: 0.2),
iconColor: UiColors.accentForeground,
);
case AttendanceType.days:
return _AttendanceStyle(
icon: LucideIcons.calendar,
bgColor: Colors.green.withOpacity(0.1),
iconColor: Colors.green,
icon: UiIcons.calendar,
bgColor: UiColors.success.withValues(alpha: 0.1),
iconColor: UiColors.textSuccess,
);
}
}

View File

@@ -1,6 +1,5 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../theme/app_colors.dart';
import 'package:krow_domain/krow_domain.dart';
enum CommuteMode {
@@ -158,21 +157,21 @@ class _CommuteTrackerState extends State<CommuteTracker> {
Widget _buildConsentCard() {
return Container(
margin: const EdgeInsets.only(bottom: 20),
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: UiConstants.space5),
padding: const EdgeInsets.all(UiConstants.space3),
decoration: BoxDecoration(
gradient: const LinearGradient(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: <Color>[
Color(0xFFEFF6FF), // blue-50
Color(0xFFECFEFF), // cyan-50
UiColors.primary.withValues(alpha: 0.05),
UiColors.primary.withValues(alpha: 0.1),
],
),
borderRadius: BorderRadius.circular(12),
borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 2,
offset: const Offset(0, 1),
),
@@ -188,42 +187,35 @@ class _CommuteTrackerState extends State<CommuteTracker> {
width: 32,
height: 32,
decoration: const BoxDecoration(
color: Color(0xFF2563EB), // blue-600
color: UiColors.primary,
shape: BoxShape.circle,
),
child: const Icon(
LucideIcons.mapPin,
UiIcons.mapPin,
size: 16,
color: Colors.white,
color: UiColors.white,
),
),
const SizedBox(width: 12),
const Expanded(
const SizedBox(width: UiConstants.space3),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'Enable Commute Tracking?',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF0F172A), // slate-900
),
style: UiTypography.body2m.textPrimary,
),
SizedBox(height: 4),
const SizedBox(height: UiConstants.space1),
Text(
'Share location 1hr before shift so your manager can see you\'re on the way.',
style: TextStyle(
fontSize: 12,
color: Color(0xFF475569), // slate-600
),
style: UiTypography.body4r.textSecondary,
),
],
),
),
],
),
const SizedBox(height: 12),
const SizedBox(height: UiConstants.space3),
Row(
children: <Widget>[
Expanded(
@@ -232,25 +224,29 @@ class _CommuteTrackerState extends State<CommuteTracker> {
setState(() => _localHasConsent = false);
},
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 8),
side: const BorderSide(color: Color(0xFFE2E8F0)),
padding: const EdgeInsets.symmetric(
vertical: UiConstants.space2,
),
side: const BorderSide(color: UiColors.border),
),
child: const Text('Not Now', style: TextStyle(fontSize: 12)),
child: Text('Not Now', style: UiTypography.footnote1m),
),
),
const SizedBox(width: 8),
const SizedBox(width: UiConstants.space2),
Expanded(
child: ElevatedButton(
onPressed: () {
setState(() => _localHasConsent = true);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2563EB), // blue-600
padding: const EdgeInsets.symmetric(vertical: 8),
backgroundColor: UiColors.primary,
padding: const EdgeInsets.symmetric(
vertical: UiConstants.space2,
),
),
child: const Text(
child: Text(
'Enable',
style: TextStyle(fontSize: 12, color: Colors.white),
style: UiTypography.footnote1m.white,
),
),
),
@@ -263,14 +259,14 @@ class _CommuteTrackerState extends State<CommuteTracker> {
Widget _buildPreShiftCard() {
return Container(
margin: const EdgeInsets.only(bottom: 20),
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: UiConstants.space5),
padding: const EdgeInsets.all(UiConstants.space3),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
color: UiColors.white,
borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 2,
offset: const Offset(0, 1),
),
@@ -282,56 +278,46 @@ class _CommuteTrackerState extends State<CommuteTracker> {
width: 32,
height: 32,
decoration: const BoxDecoration(
color: Color(0xFFF1F5F9), // slate-100
color: UiColors.bgSecondary,
shape: BoxShape.circle,
),
child: const Icon(
LucideIcons.navigation,
UiIcons.navigation,
size: 16,
color: Color(0xFF475569), // slate-600
color: UiColors.textSecondary,
),
),
const SizedBox(width: 8),
const SizedBox(width: UiConstants.space2),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
const Text(
Text(
'On My Way',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF0F172A), // slate-900
),
style: UiTypography.body2m.textPrimary,
),
const SizedBox(width: 8),
const SizedBox(width: UiConstants.space2),
Row(
children: <Widget>[
const Icon(
LucideIcons.clock,
UiIcons.clock,
size: 12,
color: Color(0xFF64748B), // slate-500
color: UiColors.textInactive,
),
const SizedBox(width: 2),
Text(
'Shift starts in ${_getMinutesUntilShift()} min',
style: const TextStyle(
fontSize: 11,
color: Color(0xFF64748B), // slate-500
),
style: UiTypography.titleUppercase4m.textSecondary,
),
],
),
],
),
const Text(
Text(
'Track arrival',
style: TextStyle(
fontSize: 10,
color: Color(0xFF64748B), // slate-500
),
style: UiTypography.titleUppercase4m.textSecondary,
),
],
),
@@ -342,7 +328,7 @@ class _CommuteTrackerState extends State<CommuteTracker> {
setState(() => _localIsCommuteOn = value);
widget.onCommuteToggled?.call(value);
},
activeThumbColor: AppColors.krowBlue,
activeThumbColor: UiColors.primary,
),
],
),
@@ -357,8 +343,8 @@ class _CommuteTrackerState extends State<CommuteTracker> {
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: <Color>[
Color(0xFF2563EB), // blue-600
Color(0xFF0891B2), // cyan-600
UiColors.primary,
UiColors.iconActive,
],
),
),
@@ -368,7 +354,7 @@ class _CommuteTrackerState extends State<CommuteTracker> {
Expanded(
child: Center(
child: Padding(
padding: const EdgeInsets.all(20),
padding: const EdgeInsets.all(UiConstants.space5),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
@@ -383,13 +369,13 @@ class _CommuteTrackerState extends State<CommuteTracker> {
width: 96,
height: 96,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
color: UiColors.white.withValues(alpha: 0.2),
shape: BoxShape.circle,
),
child: const Icon(
LucideIcons.navigation,
UiIcons.navigation,
size: 48,
color: Colors.white,
color: UiColors.white,
),
),
);
@@ -399,100 +385,84 @@ class _CommuteTrackerState extends State<CommuteTracker> {
setState(() {});
},
),
const SizedBox(height: 24),
const Text(
const SizedBox(height: UiConstants.space6),
Text(
'On My Way',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
),
style: UiTypography.displayMb.white,
),
const SizedBox(height: 8),
const SizedBox(height: UiConstants.space2),
Text(
'Your manager can see you\'re heading to the site',
style: TextStyle(
fontSize: 14,
color: Colors.blue.shade100,
style: UiTypography.body2r.copyWith(
color: UiColors.primaryForeground.withValues(alpha: 0.8),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
const SizedBox(height: UiConstants.space8),
if (widget.distanceMeters != null) ...<Widget>[
Container(
width: double.infinity,
constraints: const BoxConstraints(maxWidth: 300),
padding: const EdgeInsets.all(20),
padding: const EdgeInsets.all(UiConstants.space5),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
color: UiColors.white.withValues(alpha: 0.1),
borderRadius: UiConstants.radiusLg,
border: Border.all(
color: Colors.white.withOpacity(0.2),
color: UiColors.white.withValues(alpha: 0.2),
),
),
child: Column(
children: <Widget>[
Text(
'Distance to Site',
style: TextStyle(
fontSize: 14,
color: Colors.blue.shade100,
style: UiTypography.body2r.copyWith(
color: UiColors.primaryForeground.withValues(alpha: 0.8),
),
),
const SizedBox(height: 4),
const SizedBox(height: UiConstants.space1),
Text(
_formatDistance(widget.distanceMeters!),
style: const TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Colors.white,
),
style: UiTypography.displayM.white,
),
],
),
),
if (widget.etaMinutes != null) ...<Widget>[
const SizedBox(height: 12),
const SizedBox(height: UiConstants.space3),
Container(
width: double.infinity,
constraints: const BoxConstraints(maxWidth: 300),
padding: const EdgeInsets.all(20),
padding: const EdgeInsets.all(UiConstants.space5),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
color: UiColors.white.withValues(alpha: 0.1),
borderRadius: UiConstants.radiusLg,
border: Border.all(
color: Colors.white.withOpacity(0.2),
color: UiColors.white.withValues(alpha: 0.2),
),
),
child: Column(
children: <Widget>[
Text(
'Estimated Arrival',
style: TextStyle(
fontSize: 14,
color: Colors.blue.shade100,
style: UiTypography.body2r.copyWith(
color: UiColors.primaryForeground.withValues(alpha: 0.8),
),
),
const SizedBox(height: 4),
const SizedBox(height: UiConstants.space1),
Text(
'${widget.etaMinutes} min',
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
style: UiTypography.headline1m.white,
),
],
),
),
],
],
const SizedBox(height: 32),
const SizedBox(height: UiConstants.space8),
Text(
'Most app features are locked while commute mode is on. You\'ll be able to clock in once you arrive.',
style: TextStyle(
fontSize: 12,
color: Colors.blue.shade100,
style: UiTypography.footnote1r.copyWith(
color: UiColors.primaryForeground.withValues(alpha: 0.8),
),
textAlign: TextAlign.center,
),
@@ -502,18 +472,20 @@ class _CommuteTrackerState extends State<CommuteTracker> {
),
),
Padding(
padding: const EdgeInsets.all(20),
padding: const EdgeInsets.all(UiConstants.space5),
child: OutlinedButton(
onPressed: () {
setState(() => _localIsCommuteOn = false);
},
style: OutlinedButton.styleFrom(
foregroundColor: Colors.white,
side: BorderSide(color: Colors.white.withOpacity(0.3)),
padding: const EdgeInsets.symmetric(vertical: 16),
foregroundColor: UiColors.white,
side: BorderSide(color: UiColors.white.withValues(alpha: 0.3)),
padding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
),
minimumSize: const Size(double.infinity, 48),
),
child: const Text('Turn Off Commute Mode'),
child: Text('Turn Off Commute Mode', style: UiTypography.buttonL),
),
),
],
@@ -524,21 +496,21 @@ class _CommuteTrackerState extends State<CommuteTracker> {
Widget _buildArrivedCard() {
return Container(
margin: const EdgeInsets.only(bottom: 20),
padding: const EdgeInsets.all(20),
margin: const EdgeInsets.only(bottom: UiConstants.space5),
padding: const EdgeInsets.all(UiConstants.space5),
decoration: BoxDecoration(
gradient: const LinearGradient(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: <Color>[
Color(0xFFECFDF5), // emerald-50
Color(0xFFD1FAE5), // green-50
UiColors.tagSuccess,
UiColors.tagActive,
],
),
borderRadius: BorderRadius.circular(12),
borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.withOpacity(0.1),
color: UiColors.black.withValues(alpha: 0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
@@ -550,31 +522,24 @@ class _CommuteTrackerState extends State<CommuteTracker> {
width: 64,
height: 64,
decoration: const BoxDecoration(
color: Color(0xFF10B981), // emerald-500
color: UiColors.success,
shape: BoxShape.circle,
),
child: const Icon(
LucideIcons.checkCircle,
UiIcons.check,
size: 32,
color: Colors.white,
color: UiColors.white,
),
),
const SizedBox(height: 16),
const Text(
const SizedBox(height: UiConstants.space4),
Text(
'You\'ve Arrived! 🎉',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Color(0xFF0F172A), // slate-900
),
style: UiTypography.headline3m.textPrimary,
),
const SizedBox(height: 8),
const Text(
const SizedBox(height: UiConstants.space2),
Text(
'You\'re at the shift location. Ready to clock in?',
style: TextStyle(
fontSize: 14,
color: Color(0xFF475569), // slate-600
),
style: UiTypography.body2r.textSecondary,
textAlign: TextAlign.center,
),
],

View File

@@ -1,8 +1,8 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class DateSelector extends StatelessWidget {
const DateSelector({
super.key,
required this.selectedDate,
@@ -34,14 +34,16 @@ class DateSelector extends StatelessWidget {
onTap: () => onSelect(date),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
margin: const EdgeInsets.symmetric(horizontal: 4),
margin: const EdgeInsets.symmetric(
horizontal: UiConstants.space1,
),
decoration: BoxDecoration(
color: isSelected ? const Color(0xFF0032A0) : Colors.white,
borderRadius: BorderRadius.circular(16),
color: isSelected ? UiColors.primary : UiColors.white,
borderRadius: UiConstants.radiusLg,
boxShadow: isSelected
? <BoxShadow>[
BoxShadow(
color: const Color(0xFF0032A0).withOpacity(0.3),
color: UiColors.primary.withValues(alpha: 0.3),
blurRadius: 10,
offset: const Offset(0, 4),
),
@@ -53,33 +55,28 @@ class DateSelector extends StatelessWidget {
children: <Widget>[
Text(
DateFormat('d').format(date),
style: TextStyle(
fontSize: 18,
style: UiTypography.title1m.copyWith(
fontWeight: FontWeight.bold,
color: isSelected
? Colors.white
: const Color(0xFF0F172A),
color:
isSelected ? UiColors.white : UiColors.foreground,
),
),
const SizedBox(height: 2),
Text(
DateFormat('E').format(date),
style: TextStyle(
fontSize: 12,
style: UiTypography.footnote2r.copyWith(
color: isSelected
? Colors.white.withOpacity(0.8)
: const Color(0xFF94A3B8),
? UiColors.white.withValues(alpha: 0.8)
: UiColors.textInactive,
),
),
const SizedBox(height: 4),
const SizedBox(height: UiConstants.space1),
if (hasShift)
Container(
width: 6,
height: 6,
decoration: BoxDecoration(
color: isSelected
? Colors.white
: const Color(0xFF0032A0),
color: isSelected ? UiColors.white : UiColors.primary,
shape: BoxShape.circle,
),
)
@@ -87,8 +84,8 @@ class DateSelector extends StatelessWidget {
Container(
width: 6,
height: 6,
decoration: BoxDecoration(
color: Colors.grey.shade300,
decoration: const BoxDecoration(
color: UiColors.border,
shape: BoxShape.circle,
),
)

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:design_system/design_system.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:flutter/material.dart';
class LocationMapPlaceholder extends StatelessWidget {
@@ -18,8 +17,8 @@ class LocationMapPlaceholder extends StatelessWidget {
height: 200,
width: double.infinity,
decoration: BoxDecoration(
color: const Color(0xFFE2E8F0),
borderRadius: BorderRadius.circular(16),
color: UiColors.border,
borderRadius: UiConstants.radiusLg,
image: DecorationImage(
image: const NetworkImage(
'https://maps.googleapis.com/maps/api/staticmap?center=40.7128,-74.0060&zoom=15&size=600x300&maptype=roadmap&markers=color:red%7C40.7128,-74.0060&key=YOUR_API_KEY',
@@ -33,30 +32,37 @@ class LocationMapPlaceholder extends StatelessWidget {
child: Stack(
children: <Widget>[
// Fallback UI if image fails (which it will without key)
const Center(
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(LucideIcons.mapPin, size: 48, color: UiColors.iconSecondary),
SizedBox(height: 8),
Text('Map View (GPS)', style: TextStyle(color: UiColors.textSecondary)),
const Icon(
UiIcons.mapPin,
size: 48,
color: UiColors.iconSecondary,
),
const SizedBox(height: UiConstants.space2),
Text('Map View (GPS)', style: UiTypography.body2r.textSecondary),
],
),
),
// Status Overlay
Positioned(
bottom: 16,
left: 16,
right: 16,
bottom: UiConstants.space4,
left: UiConstants.space4,
right: UiConstants.space4,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
vertical: UiConstants.space3,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
color: UiColors.white,
borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.withOpacity(0.1),
color: UiColors.black.withValues(alpha: 0.1),
blurRadius: 8,
offset: const Offset(0, 4),
),
@@ -65,23 +71,25 @@ class LocationMapPlaceholder extends StatelessWidget {
child: Row(
children: <Widget>[
Icon(
isVerified ? LucideIcons.checkCircle : LucideIcons.alertCircle,
color: isVerified ? UiColors.textSuccess : UiColors.destructive,
isVerified ? UiIcons.checkCircle : UiIcons.warning,
color: isVerified
? UiColors.textSuccess
: UiColors.destructive,
size: 20,
),
const SizedBox(width: 12),
const SizedBox(width: UiConstants.space3),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
isVerified ? 'Location Verified' : 'Location Check',
style: UiTypography.body1b.copyWith(color: UiColors.textPrimary),
style: UiTypography.body1b.textPrimary,
),
if (distance != null)
Text(
'${distance!.toStringAsFixed(0)}m from venue',
style: UiTypography.body2r.copyWith(color: UiColors.textSecondary),
style: UiTypography.body2r.textSecondary,
),
],
),

View File

@@ -1,8 +1,7 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
class LunchBreakDialog extends StatefulWidget {
const LunchBreakDialog({super.key, required this.onComplete});
final VoidCallback onComplete;
@@ -47,8 +46,10 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
backgroundColor: UiColors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(UiConstants.space6),
),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: _buildCurrentStep(),
@@ -75,34 +76,30 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
Widget _buildStep1() {
return Padding(
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(UiConstants.space6),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: Colors.grey.shade100,
decoration: const BoxDecoration(
color: UiColors.bgSecondary,
shape: BoxShape.circle,
),
child: const Icon(
LucideIcons.coffee,
UiIcons.coffee,
size: 40,
color: Color(0xFF6A7382),
color: UiColors.iconSecondary,
),
),
const SizedBox(height: 24),
const Text(
const SizedBox(height: UiConstants.space6),
Text(
"Did You Take\na Lunch?",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Color(0xFF121826),
),
style: UiTypography.headline1m.textPrimary,
),
const SizedBox(height: 24),
const SizedBox(height: UiConstants.space6),
Row(
children: <Widget>[
Expanded(
@@ -114,25 +111,23 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
});
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16),
padding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(12),
color: Colors.transparent,
border: Border.all(color: UiColors.border),
borderRadius: UiConstants.radiusLg,
color: UiColors.transparent,
),
alignment: Alignment.center,
child: const Text(
child: Text(
"No",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF121826),
),
style: UiTypography.body1m.textPrimary,
),
),
),
),
const SizedBox(width: 16),
const SizedBox(width: UiConstants.space4),
Expanded(
child: ElevatedButton(
onPressed: () {
@@ -142,19 +137,17 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
});
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0032A0),
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: UiColors.primary,
padding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: UiConstants.radiusLg,
),
),
child: const Text(
child: Text(
"Yes",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
style: UiTypography.body1m.white,
),
),
),
@@ -168,155 +161,183 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
Widget _buildStep2() {
// Time input
return Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text(
"When did you take lunch?",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 24),
// Mock Inputs
Row(
children: <Widget>[
Expanded(
child: DropdownButtonFormField<String>(
isExpanded: true,
initialValue: _breakStart,
items: _timeOptions.map((String t) => DropdownMenuItem(value: t, child: Text(t, style: const TextStyle(fontSize: 13)))).toList(),
onChanged: (String? v) => setState(() => _breakStart = v),
decoration: const InputDecoration(
labelText: 'Start',
contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8),
),
),
),
const SizedBox(width: 10),
Expanded(
child: DropdownButtonFormField<String>(
isExpanded: true,
initialValue: _breakEnd,
items: _timeOptions.map((String t) => DropdownMenuItem(value: t, child: Text(t, style: const TextStyle(fontSize: 13)))).toList(),
onChanged: (String? v) => setState(() => _breakEnd = v),
decoration: const InputDecoration(
labelText: 'End',
contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8),
),
),
),
],
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {
setState(() => _step = 3);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0032A0),
minimumSize: const Size(double.infinity, 48),
padding: const EdgeInsets.all(UiConstants.space6),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"When did you take lunch?",
style: UiTypography.headline4m,
),
const SizedBox(height: UiConstants.space6),
// Mock Inputs
Row(
children: <Widget>[
Expanded(
child: DropdownButtonFormField<String>(
isExpanded: true,
value: _breakStart,
items: _timeOptions
.map(
(String t) => DropdownMenuItem(
value: t,
child: Text(t, style: UiTypography.body3r),
),
)
.toList(),
onChanged: (String? v) => setState(() => _breakStart = v),
decoration: const InputDecoration(
labelText: 'Start',
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 8,
),
),
),
child: const Text("Next", style: TextStyle(color: Colors.white)),
),
const SizedBox(width: 10),
Expanded(
child: DropdownButtonFormField<String>(
isExpanded: true,
value: _breakEnd,
items: _timeOptions
.map(
(String t) => DropdownMenuItem(
value: t,
child: Text(t, style: UiTypography.body3r),
),
)
.toList(),
onChanged: (String? v) => setState(() => _breakEnd = v),
decoration: const InputDecoration(
labelText: 'End',
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 8,
),
),
),
),
],
),
const SizedBox(height: UiConstants.space6),
ElevatedButton(
onPressed: () {
setState(() => _step = 3);
},
style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary,
minimumSize: const Size(double.infinity, 48),
),
],
));
child: Text("Next", style: UiTypography.body1m.white),
),
],
),
);
}
Widget _buildStep2b() {
Widget _buildStep2b() {
// No lunch reason
return Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
const Text(
"Why didn't you take lunch?",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
padding: const EdgeInsets.all(UiConstants.space6),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
"Why didn't you take lunch?",
style: UiTypography.headline4m,
),
const SizedBox(height: UiConstants.space4),
..._noLunchReasons.map(
(String reason) => RadioListTile<String>(
title: Text(reason, style: UiTypography.body2r),
value: reason,
groupValue: _noLunchReason,
onChanged: (String? val) => setState(() => _noLunchReason = val),
activeColor: UiColors.primary,
),
const SizedBox(height: 16),
..._noLunchReasons.map((String reason) => RadioListTile<String>(
title: Text(reason),
value: reason,
groupValue: _noLunchReason,
onChanged: (String? val) => setState(() => _noLunchReason = val),
)),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {
setState(() => _step = 3);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0032A0),
minimumSize: const Size(double.infinity, 48),
),
child: const Text("Next", style: TextStyle(color: Colors.white)),
const SizedBox(height: UiConstants.space6),
ElevatedButton(
onPressed: () {
setState(() => _step = 3);
},
style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary,
minimumSize: const Size(double.infinity, 48),
),
],
));
child: Text("Next", style: UiTypography.body1m.white),
),
],
),
);
}
Widget _buildStep3() {
// Additional Notes
return Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text(
"Additional Notes",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
return Padding(
padding: const EdgeInsets.all(UiConstants.space6),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"Additional Notes",
style: UiTypography.headline4m,
),
const SizedBox(height: UiConstants.space4),
TextField(
onChanged: (String v) => _additionalNotes = v,
style: UiTypography.body2r,
decoration: const InputDecoration(
hintText: 'Add any details...',
border: OutlineInputBorder(),
),
const SizedBox(height: 16),
TextField(
onChanged: (String v) => _additionalNotes = v,
decoration: const InputDecoration(
hintText: 'Add any details...',
border: OutlineInputBorder(),
),
maxLines: 3,
),
maxLines: 3,
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {
setState(() => _step = 4);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0032A0),
minimumSize: const Size(double.infinity, 48),
),
child: const Text("Submit", style: TextStyle(color: Colors.white)),
const SizedBox(height: UiConstants.space6),
ElevatedButton(
onPressed: () {
setState(() => _step = 4);
},
style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary,
minimumSize: const Size(double.infinity, 48),
),
],
));
child: Text("Submit", style: UiTypography.body1m.white),
),
],
),
);
}
Widget _buildStep4() {
// Success
return Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Icon(LucideIcons.checkCircle, size: 64, color: Colors.green),
const SizedBox(height: 24),
const Text(
"Break Logged!",
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
return Padding(
padding: const EdgeInsets.all(UiConstants.space6),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Icon(UiIcons.checkCircle, size: 64, color: UiColors.success),
const SizedBox(height: UiConstants.space6),
Text(
"Break Logged!",
style: UiTypography.headline1m,
),
const SizedBox(height: UiConstants.space6),
ElevatedButton(
onPressed: widget.onComplete,
style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary,
minimumSize: const Size(double.infinity, 48),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: widget.onComplete,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0032A0),
minimumSize: const Size(double.infinity, 48),
),
child: const Text("Close", style: TextStyle(color: Colors.white)),
),
],
));
child: Text("Close", style: UiTypography.body1m.white),
),
],
),
);
}
}

View File

@@ -1,8 +1,7 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
class SwipeToCheckIn extends StatefulWidget {
const SwipeToCheckIn({
super.key,
this.onCheckIn,
@@ -73,8 +72,8 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
@override
Widget build(BuildContext context) {
final Color baseColor = widget.isCheckedIn
? const Color(0xFF10B981)
: const Color(0xFF0032A0);
? UiColors.success
: UiColors.primary;
if (widget.mode == 'nfc') {
return GestureDetector(
@@ -93,10 +92,10 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
height: 56,
decoration: BoxDecoration(
color: baseColor,
borderRadius: BorderRadius.circular(16),
borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[
BoxShadow(
color: baseColor.withOpacity(0.4),
color: baseColor.withValues(alpha: 0.4),
blurRadius: 25,
offset: const Offset(0, 10),
spreadRadius: -5,
@@ -106,19 +105,15 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(LucideIcons.wifi, color: Colors.white),
const SizedBox(width: 12),
const Icon(UiIcons.wifi, color: UiColors.white),
const SizedBox(width: UiConstants.space3),
Text(
widget.isLoading
? (widget.isCheckedIn
? "Checking out..."
: "Checking in...")
: (widget.isCheckedIn ? "NFC Check Out" : "NFC Check In"),
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
style: UiTypography.body1b.white,
),
],
),
@@ -134,11 +129,11 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
// Calculate background color based on drag
final double progress = _dragValue / maxDrag;
final Color startColor = widget.isCheckedIn
? const Color(0xFF10B981)
: const Color(0xFF0032A0);
? UiColors.success
: UiColors.primary;
final Color endColor = widget.isCheckedIn
? const Color(0xFF0032A0)
: const Color(0xFF10B981);
? UiColors.primary
: UiColors.success;
final Color currentColor =
Color.lerp(startColor, endColor, progress) ?? startColor;
@@ -146,10 +141,10 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
height: 56,
decoration: BoxDecoration(
color: currentColor,
borderRadius: BorderRadius.circular(16),
borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.withOpacity(0.1),
color: UiColors.black.withValues(alpha: 0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
@@ -164,11 +159,7 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
widget.isCheckedIn
? "Swipe to Check Out"
: "Swipe to Check In",
style: TextStyle(
color: Colors.white.withOpacity(0.8),
fontWeight: FontWeight.w600,
fontSize: 18,
),
style: UiTypography.body1b,
),
),
),
@@ -176,28 +167,26 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
Center(
child: Text(
widget.isCheckedIn ? "Check Out!" : "Check In!",
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: 18,
),
style: UiTypography.body1b,
),
),
Positioned(
left: 4 + _dragValue,
top: 4,
child: GestureDetector(
onHorizontalDragUpdate: (DragUpdateDetails d) => _onDragUpdate(d, maxWidth),
onHorizontalDragEnd: (DragEndDetails d) => _onDragEnd(d, maxWidth),
onHorizontalDragUpdate: (DragUpdateDetails d) =>
_onDragUpdate(d, maxWidth),
onHorizontalDragEnd: (DragEndDetails d) =>
_onDragEnd(d, maxWidth),
child: Container(
width: _handleSize,
height: _handleSize,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
color: UiColors.white,
borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.withOpacity(0.1),
color: UiColors.black.withValues(alpha: 0.1),
blurRadius: 2,
offset: const Offset(0, 1),
),
@@ -205,9 +194,7 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
),
child: Center(
child: Icon(
_isComplete
? LucideIcons.check
: LucideIcons.arrowRight,
_isComplete ? UiIcons.check : UiIcons.arrowRight,
color: startColor,
),
),