Standardize UI to design system tokens

Refactor multiple UI components to use design system tokens and primitives. Added new UiIcons (coffee, wifi, xCircle, ban) and typography color getters (primary, accent). Replaced hardcoded paddings, spacings, radii, borderRadius, and icon imports (lucide_icons -> UiIcons) with UiConstants, UiColors, UiTypography and UiIcons, and switched to UiColors.withValues for opacity. Changes apply across authentication, availability, clock_in (and its widgets), commute tracker, lunch break modal, location map placeholder, attendance card, date selector, and related presentation files to improve visual consistency.
This commit is contained in:
Achintha Isuru
2026-02-10 17:17:56 -05:00
parent bcd973cf64
commit 4c38013c10
58 changed files with 1821 additions and 1832 deletions

View File

@@ -4,7 +4,6 @@ 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';
@@ -84,7 +83,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>[
@@ -103,12 +104,13 @@ 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
Text(
@@ -117,7 +119,7 @@ class _ClockInPageState extends State<ClockInPage> {
style: UiTypography.headline4m,
),
const SizedBox(height: 16),
const SizedBox(height: UiConstants.space4),
// Selected Shift Info Card
if (todayShifts.isNotEmpty)
@@ -128,14 +130,16 @@ 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: UiColors.white,
borderRadius: BorderRadius.circular(
12,
),
borderRadius:
UiConstants.radiusLg,
border: Border.all(
color: shift.id ==
selectedShift?.id
@@ -211,39 +215,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: UiColors.bgSecondary,
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: <Widget>[
const Icon(
LucideIcons.clock,
size: 48,
color: UiColors.iconThird,
),
const SizedBox(height: 16),
Text(
"You're early!",
style: UiTypography.body1m.textSecondary,
),
const SizedBox(height: 4),
Text(
"Check-in available at ${_getCheckInAvailabilityTime(selectedShift)}",
style: UiTypography.body2r.textSecondary,
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:
@@ -264,14 +270,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(),
);
},
),
);
},
),
@@ -279,12 +288,14 @@ 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: UiColors.tagSuccess,
borderRadius: BorderRadius.circular(16),
borderRadius: UiConstants.radiusLg,
border: Border.all(
color: UiColors.success.withValues(alpha: 0.3),
color: UiColors.success.withValues(
alpha: 0.3,
),
),
),
child: Column(
@@ -297,17 +308,17 @@ class _ClockInPageState extends State<ClockInPage> {
shape: BoxShape.circle,
),
child: const Icon(
LucideIcons.check,
UiIcons.check,
color: UiColors.textSuccess,
size: 24,
),
),
const SizedBox(height: 12),
const SizedBox(height: UiConstants.space3),
Text(
"Shift Completed!",
style: UiTypography.body1b.textSuccess,
),
const SizedBox(height: 4),
const SizedBox(height: UiConstants.space1),
Text(
"Great work today",
style: UiTypography.body2r.textSuccess,
@@ -319,10 +330,10 @@ 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: UiColors.bgSecondary,
borderRadius: BorderRadius.circular(16),
borderRadius: UiConstants.radiusLg,
),
child: Column(
children: <Widget>[
@@ -331,7 +342,7 @@ class _ClockInPageState extends State<ClockInPage> {
style: UiTypography.body1m.textSecondary,
textAlign: TextAlign.center,
),
const SizedBox(height: 4),
const SizedBox(height: UiConstants.space1),
Text(
"Accept a shift to clock in",
style: UiTypography.body2r.textSecondary,
@@ -344,14 +355,16 @@ 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: UiColors.tagSuccess,
borderRadius: BorderRadius.circular(12),
borderRadius: UiConstants.radiusLg,
border: Border.all(
color: UiColors.success.withValues(alpha: 0.3),
color: UiColors.success.withValues(
alpha: 0.3,
),
),
),
child: Row(
@@ -382,7 +395,7 @@ class _ClockInPageState extends State<ClockInPage> {
shape: BoxShape.circle,
),
child: const Icon(
LucideIcons.check,
UiIcons.check,
color: UiColors.textSuccess,
),
),
@@ -419,10 +432,10 @@ 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 ? UiColors.white : UiColors.transparent,
borderRadius: BorderRadius.circular(8),
borderRadius: UiConstants.radiusMd,
boxShadow: isSelected
? <BoxShadow>[
BoxShadow(
@@ -476,21 +489,23 @@ class _ClockInPageState extends State<ClockInPage> {
width: 96,
height: 96,
decoration: BoxDecoration(
color: scanned ? UiColors.tagSuccess : UiColors.tagInProgress,
color: scanned
? UiColors.tagSuccess
: UiColors.tagInProgress,
shape: BoxShape.circle,
),
child: Icon(
scanned ? LucideIcons.check : LucideIcons.nfc,
scanned ? UiIcons.check : UiIcons.nfc,
size: 48,
color: scanned ? UiColors.textSuccess : UiColors.primary,
),
),
const SizedBox(height: 24),
const SizedBox(height: UiConstants.space6),
Text(
scanned ? 'Processing check-in...' : 'Ready to scan',
style: UiTypography.headline4m,
),
const SizedBox(height: 8),
const SizedBox(height: UiConstants.space2),
Text(
scanned
? 'Please wait...'
@@ -499,7 +514,7 @@ class _ClockInPageState extends State<ClockInPage> {
style: UiTypography.body2r.textSecondary,
),
if (!scanned) ...<Widget>[
const SizedBox(height: 24),
const SizedBox(height: UiConstants.space6),
SizedBox(
width: double.infinity,
height: 56,
@@ -520,7 +535,7 @@ 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),
icon: const Icon(UiIcons.nfc, size: 24),
label: Text(
'Tap to Scan',
style: UiTypography.headline4m.white,
@@ -529,7 +544,7 @@ class _ClockInPageState extends State<ClockInPage> {
backgroundColor: UiColors.primary,
foregroundColor: UiColors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: UiConstants.radiusLg,
),
),
),

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';
enum AttendanceType { checkin, checkout, breaks, days }
@@ -24,10 +23,10 @@ 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: UiColors.white,
borderRadius: BorderRadius.circular(16),
borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.bgSecondary),
boxShadow: <BoxShadow>[
BoxShadow(
@@ -46,11 +45,11 @@ 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: UiTypography.titleUppercase4m.textSecondary,
@@ -86,25 +85,25 @@ class AttendanceCard extends StatelessWidget {
switch (type) {
case AttendanceType.checkin:
return _AttendanceStyle(
icon: LucideIcons.logIn,
icon: UiIcons.logIn,
bgColor: UiColors.primary.withValues(alpha: 0.1),
iconColor: UiColors.primary,
);
case AttendanceType.checkout:
return _AttendanceStyle(
icon: LucideIcons.logOut,
icon: UiIcons.logOut,
bgColor: UiColors.foreground.withValues(alpha: 0.1),
iconColor: UiColors.foreground,
);
case AttendanceType.breaks:
return _AttendanceStyle(
icon: LucideIcons.coffee,
icon: UiIcons.coffee,
bgColor: UiColors.accent.withValues(alpha: 0.2),
iconColor: UiColors.accentForeground,
);
case AttendanceType.days:
return _AttendanceStyle(
icon: LucideIcons.calendar,
icon: UiIcons.calendar,
bgColor: UiColors.success.withValues(alpha: 0.1),
iconColor: UiColors.textSuccess,
);

View File

@@ -1,7 +1,6 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:lucide_icons/lucide_icons.dart';
enum CommuteMode {
lockedNoShift,
@@ -158,8 +157,8 @@ 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: LinearGradient(
begin: Alignment.topLeft,
@@ -169,7 +168,7 @@ class _CommuteTrackerState extends State<CommuteTracker> {
UiColors.primary.withValues(alpha: 0.1),
],
),
borderRadius: BorderRadius.circular(12),
borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[
BoxShadow(
color: UiColors.black.withValues(alpha: 0.05),
@@ -192,12 +191,12 @@ class _CommuteTrackerState extends State<CommuteTracker> {
shape: BoxShape.circle,
),
child: const Icon(
LucideIcons.mapPin,
UiIcons.mapPin,
size: 16,
color: UiColors.white,
),
),
const SizedBox(width: 12),
const SizedBox(width: UiConstants.space3),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -206,7 +205,7 @@ class _CommuteTrackerState extends State<CommuteTracker> {
'Enable Commute Tracking?',
style: UiTypography.body2m.textPrimary,
),
const 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: UiTypography.body4r.textSecondary,
@@ -216,7 +215,7 @@ class _CommuteTrackerState extends State<CommuteTracker> {
),
],
),
const SizedBox(height: 12),
const SizedBox(height: UiConstants.space3),
Row(
children: <Widget>[
Expanded(
@@ -225,13 +224,15 @@ class _CommuteTrackerState extends State<CommuteTracker> {
setState(() => _localHasConsent = false);
},
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 8),
padding: const EdgeInsets.symmetric(
vertical: UiConstants.space2,
),
side: const BorderSide(color: UiColors.border),
),
child: Text('Not Now', style: UiTypography.footnote1m),
),
),
const SizedBox(width: 8),
const SizedBox(width: UiConstants.space2),
Expanded(
child: ElevatedButton(
onPressed: () {
@@ -239,7 +240,9 @@ class _CommuteTrackerState extends State<CommuteTracker> {
},
style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary,
padding: const EdgeInsets.symmetric(vertical: 8),
padding: const EdgeInsets.symmetric(
vertical: UiConstants.space2,
),
),
child: Text(
'Enable',
@@ -256,11 +259,11 @@ 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: UiColors.white,
borderRadius: BorderRadius.circular(12),
borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[
BoxShadow(
color: UiColors.black.withValues(alpha: 0.05),
@@ -279,12 +282,12 @@ class _CommuteTrackerState extends State<CommuteTracker> {
shape: BoxShape.circle,
),
child: const Icon(
LucideIcons.navigation,
UiIcons.navigation,
size: 16,
color: UiColors.textSecondary,
),
),
const SizedBox(width: 8),
const SizedBox(width: UiConstants.space2),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -295,11 +298,11 @@ class _CommuteTrackerState extends State<CommuteTracker> {
'On My Way',
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: UiColors.textInactive,
),
@@ -351,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>[
@@ -370,7 +373,7 @@ class _CommuteTrackerState extends State<CommuteTracker> {
shape: BoxShape.circle,
),
child: const Icon(
LucideIcons.navigation,
UiIcons.navigation,
size: 48,
color: UiColors.white,
),
@@ -382,12 +385,12 @@ class _CommuteTrackerState extends State<CommuteTracker> {
setState(() {});
},
),
const SizedBox(height: 24),
const SizedBox(height: UiConstants.space6),
Text(
'On My Way',
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: UiTypography.body2r.copyWith(
@@ -395,15 +398,15 @@ class _CommuteTrackerState extends State<CommuteTracker> {
),
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: UiColors.white.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(16),
borderRadius: UiConstants.radiusLg,
border: Border.all(
color: UiColors.white.withValues(alpha: 0.2),
),
@@ -416,7 +419,7 @@ class _CommuteTrackerState extends State<CommuteTracker> {
color: UiColors.primaryForeground.withValues(alpha: 0.8),
),
),
const SizedBox(height: 4),
const SizedBox(height: UiConstants.space1),
Text(
_formatDistance(widget.distanceMeters!),
style: UiTypography.displayM.white,
@@ -425,14 +428,14 @@ class _CommuteTrackerState extends State<CommuteTracker> {
),
),
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: UiColors.white.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(16),
borderRadius: UiConstants.radiusLg,
border: Border.all(
color: UiColors.white.withValues(alpha: 0.2),
),
@@ -445,7 +448,7 @@ class _CommuteTrackerState extends State<CommuteTracker> {
color: UiColors.primaryForeground.withValues(alpha: 0.8),
),
),
const SizedBox(height: 4),
const SizedBox(height: UiConstants.space1),
Text(
'${widget.etaMinutes} min',
style: UiTypography.headline1m.white,
@@ -455,7 +458,7 @@ class _CommuteTrackerState extends State<CommuteTracker> {
),
],
],
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: UiTypography.footnote1r.copyWith(
@@ -469,7 +472,7 @@ class _CommuteTrackerState extends State<CommuteTracker> {
),
),
Padding(
padding: const EdgeInsets.all(20),
padding: const EdgeInsets.all(UiConstants.space5),
child: OutlinedButton(
onPressed: () {
setState(() => _localIsCommuteOn = false);
@@ -477,7 +480,9 @@ class _CommuteTrackerState extends State<CommuteTracker> {
style: OutlinedButton.styleFrom(
foregroundColor: UiColors.white,
side: BorderSide(color: UiColors.white.withValues(alpha: 0.3)),
padding: const EdgeInsets.symmetric(vertical: 16),
padding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
),
minimumSize: const Size(double.infinity, 48),
),
child: Text('Turn Off Commute Mode', style: UiTypography.buttonL),
@@ -491,8 +496,8 @@ 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: LinearGradient(
begin: Alignment.topLeft,
@@ -502,7 +507,7 @@ class _CommuteTrackerState extends State<CommuteTracker> {
UiColors.tagActive,
],
),
borderRadius: BorderRadius.circular(12),
borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[
BoxShadow(
color: UiColors.black.withValues(alpha: 0.1),
@@ -521,17 +526,17 @@ class _CommuteTrackerState extends State<CommuteTracker> {
shape: BoxShape.circle,
),
child: const Icon(
LucideIcons.checkCircle,
UiIcons.check,
size: 32,
color: UiColors.white,
),
),
const SizedBox(height: 16),
const SizedBox(height: UiConstants.space4),
Text(
'You\'ve Arrived! 🎉',
style: UiTypography.headline3m.textPrimary,
),
const SizedBox(height: 8),
const SizedBox(height: UiConstants.space2),
Text(
'You\'re at the shift location. Ready to clock in?',
style: UiTypography.body2r.textSecondary,

View File

@@ -34,10 +34,12 @@ 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 ? UiColors.primary : UiColors.white,
borderRadius: BorderRadius.circular(16),
borderRadius: UiConstants.radiusLg,
boxShadow: isSelected
? <BoxShadow>[
BoxShadow(
@@ -55,7 +57,8 @@ class DateSelector extends StatelessWidget {
DateFormat('d').format(date),
style: UiTypography.title1m.copyWith(
fontWeight: FontWeight.bold,
color: isSelected ? UiColors.white : UiColors.foreground,
color:
isSelected ? UiColors.white : UiColors.foreground,
),
),
const SizedBox(height: 2),
@@ -67,7 +70,7 @@ class DateSelector extends StatelessWidget {
: UiColors.textInactive,
),
),
const SizedBox(height: 4),
const SizedBox(height: UiConstants.space1),
if (hasShift)
Container(
width: 6,

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';
class LocationMapPlaceholder extends StatelessWidget {
@@ -19,7 +18,7 @@ class LocationMapPlaceholder extends StatelessWidget {
width: double.infinity,
decoration: BoxDecoration(
color: UiColors.border,
borderRadius: BorderRadius.circular(16),
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',
@@ -37,9 +36,12 @@ class LocationMapPlaceholder extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(LucideIcons.mapPin,
size: 48, color: UiColors.iconSecondary),
const SizedBox(height: 8),
const Icon(
UiIcons.mapPin,
size: 48,
color: UiColors.iconSecondary,
),
const SizedBox(height: UiConstants.space2),
Text('Map View (GPS)', style: UiTypography.body2r.textSecondary),
],
),
@@ -47,14 +49,17 @@ class LocationMapPlaceholder extends StatelessWidget {
// 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: UiColors.white,
borderRadius: BorderRadius.circular(12),
borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[
BoxShadow(
color: UiColors.black.withValues(alpha: 0.1),
@@ -66,15 +71,13 @@ class LocationMapPlaceholder extends StatelessWidget {
child: Row(
children: <Widget>[
Icon(
isVerified
? LucideIcons.checkCircle
: LucideIcons.alertCircle,
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,

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';
class LunchBreakDialog extends StatefulWidget {
const LunchBreakDialog({super.key, required this.onComplete});
@@ -48,7 +47,9 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
Widget build(BuildContext context) {
return Dialog(
backgroundColor: UiColors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(UiConstants.space6),
),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: _buildCurrentStep(),
@@ -75,7 +76,7 @@ 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>[
@@ -87,18 +88,18 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
shape: BoxShape.circle,
),
child: const Icon(
LucideIcons.coffee,
UiIcons.coffee,
size: 40,
color: UiColors.iconSecondary,
),
),
const SizedBox(height: 24),
const SizedBox(height: UiConstants.space6),
Text(
"Did You Take\na Lunch?",
textAlign: TextAlign.center,
style: UiTypography.headline1m.textPrimary,
),
const SizedBox(height: 24),
const SizedBox(height: UiConstants.space6),
Row(
children: <Widget>[
Expanded(
@@ -110,10 +111,12 @@ 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: UiColors.border),
borderRadius: BorderRadius.circular(12),
borderRadius: UiConstants.radiusLg,
color: UiColors.transparent,
),
alignment: Alignment.center,
@@ -124,7 +127,7 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
),
),
),
const SizedBox(width: 16),
const SizedBox(width: UiConstants.space4),
Expanded(
child: ElevatedButton(
onPressed: () {
@@ -135,9 +138,11 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
},
style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary,
padding: const EdgeInsets.symmetric(vertical: 16),
padding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: UiConstants.radiusLg,
),
),
child: Text(
@@ -156,169 +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>[
Text(
"When did you take lunch?",
style: UiTypography.headline4m,
),
const SizedBox(height: 24),
// 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),
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,
),
),
),
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: 24),
ElevatedButton(
onPressed: () {
setState(() => _step = 3);
},
style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary,
minimumSize: const Size(double.infinity, 48),
),
child: Text("Next", style: UiTypography.body1m.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() {
// No lunch reason
return Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
"Why didn't you take lunch?",
style: UiTypography.headline4m,
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, style: UiTypography.body2r),
value: reason,
groupValue: _noLunchReason,
onChanged: (String? val) =>
setState(() => _noLunchReason = val),
activeColor: UiColors.primary,
)),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {
setState(() => _step = 3);
},
style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary,
minimumSize: const Size(double.infinity, 48),
),
child: Text("Next", style: UiTypography.body1m.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>[
Text(
"Additional Notes",
style: UiTypography.headline4m,
),
const SizedBox(height: 16),
TextField(
onChanged: (String v) => _additionalNotes = v,
style: UiTypography.body2r,
decoration: const InputDecoration(
hintText: 'Add any details...',
border: OutlineInputBorder(),
),
maxLines: 3,
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(),
),
maxLines: 3,
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {
setState(() => _step = 4);
},
style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary,
minimumSize: const Size(double.infinity, 48),
),
child: Text("Submit", style: UiTypography.body1m.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: UiColors.success),
const SizedBox(height: 24),
Text(
"Break Logged!",
style: UiTypography.headline1m,
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: UiColors.primary,
minimumSize: const Size(double.infinity, 48),
),
child: Text("Close", style: UiTypography.body1m.white),
),
],
));
child: Text("Close", style: UiTypography.body1m.white),
),
],
),
);
}
}

View File

@@ -1,9 +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 +71,9 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
@override
Widget build(BuildContext context) {
final Color baseColor =
widget.isCheckedIn ? UiColors.success : UiColors.primary;
final Color baseColor = widget.isCheckedIn
? UiColors.success
: UiColors.primary;
if (widget.mode == 'nfc') {
return GestureDetector(
@@ -93,7 +92,7 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
height: 56,
decoration: BoxDecoration(
color: baseColor,
borderRadius: BorderRadius.circular(16),
borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[
BoxShadow(
color: baseColor.withValues(alpha: 0.4),
@@ -106,8 +105,8 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(LucideIcons.wifi, color: UiColors.white),
const SizedBox(width: 12),
const Icon(UiIcons.wifi, color: UiColors.white),
const SizedBox(width: UiConstants.space3),
Text(
widget.isLoading
? (widget.isCheckedIn
@@ -129,10 +128,12 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
// Calculate background color based on drag
final double progress = _dragValue / maxDrag;
final Color startColor =
widget.isCheckedIn ? UiColors.success : UiColors.primary;
final Color endColor =
widget.isCheckedIn ? UiColors.primary : UiColors.success;
final Color startColor = widget.isCheckedIn
? UiColors.success
: UiColors.primary;
final Color endColor = widget.isCheckedIn
? UiColors.primary
: UiColors.success;
final Color currentColor =
Color.lerp(startColor, endColor, progress) ?? startColor;
@@ -140,7 +141,7 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
height: 56,
decoration: BoxDecoration(
color: currentColor,
borderRadius: BorderRadius.circular(16),
borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[
BoxShadow(
color: UiColors.black.withValues(alpha: 0.1),
@@ -173,14 +174,16 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
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: UiColors.white,
borderRadius: BorderRadius.circular(12),
borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[
BoxShadow(
color: UiColors.black.withValues(alpha: 0.1),
@@ -191,9 +194,7 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
),
child: Center(
child: Icon(
_isComplete
? LucideIcons.check
: LucideIcons.arrowRight,
_isComplete ? UiIcons.check : UiIcons.arrowRight,
color: startColor,
),
),