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

@@ -1,7 +1,7 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:intl/intl.dart';
import 'package:krow_domain/krow_domain.dart';
import '../blocs/payments/payments_bloc.dart';
@@ -33,12 +33,22 @@ class _PaymentsPageState extends State<PaymentsPage> {
return BlocProvider<PaymentsBloc>.value(
value: _bloc,
child: Scaffold(
backgroundColor: UiColors.background,
body: BlocBuilder<PaymentsBloc, PaymentsState>(
builder: (BuildContext context, PaymentsState state) {
if (state is PaymentsLoading) {
return const Center(child: CircularProgressIndicator());
return Center(
child: CircularProgressIndicator(
color: UiColors.primary,
),
);
} else if (state is PaymentsError) {
return Center(child: Text('Error: ${state.message}'));
return Center(
child: Text(
'Error: ${state.message}',
style: UiTypography.body2r.textError,
),
);
} else if (state is PaymentsLoaded) {
return _buildContent(context, state);
}
@@ -55,63 +65,57 @@ class _PaymentsPageState extends State<PaymentsPage> {
children: <Widget>[
// Header Section with Gradient
Container(
decoration: const BoxDecoration(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: <Color>[Color(0xFF0032A0), Color(0xFF333F48)],
colors: <Color>[
UiColors.primary,
UiColors.primary.withValues(alpha: 0.8),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
padding: EdgeInsets.fromLTRB(
20,
MediaQuery.of(context).padding.top + 24,
20,
32,
UiConstants.space5,
MediaQuery.of(context).padding.top + UiConstants.space6,
UiConstants.space5,
UiConstants.space8,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text(
Text(
"Earnings",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
style: UiTypography.displayMb.white,
),
const SizedBox(height: 24),
const SizedBox(height: UiConstants.space6),
// Main Balance
Center(
child: Column(
children: <Widget>[
const Text(
Text(
"Total Earnings",
style: TextStyle(
color: Color(0xFFF8E08E),
fontSize: 14,
style: UiTypography.body2r.copyWith(
color: UiColors.accent,
),
),
const SizedBox(height: 4),
const SizedBox(height: UiConstants.space1),
Text(
"\$${state.summary.totalEarnings.toStringAsFixed(0).replaceAllMapped(RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (Match m) => '${m[1]},')}",
style: const TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Colors.white,
),
style: UiTypography.displayL.white,
),
],
),
),
const SizedBox(height: 16),
const SizedBox(height: UiConstants.space4),
// Period Tabs
Container(
padding: const EdgeInsets.all(4),
padding: const EdgeInsets.all(UiConstants.space1),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
color: UiColors.white.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
),
child: Row(
children: <Widget>[
@@ -127,9 +131,9 @@ class _PaymentsPageState extends State<PaymentsPage> {
// Main Content - Offset upwards
Transform.translate(
offset: const Offset(0, -16),
offset: const Offset(0, -UiConstants.space4),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
@@ -138,76 +142,75 @@ class _PaymentsPageState extends State<PaymentsPage> {
payments: state.history,
period: state.activePeriod,
),
const SizedBox(height: 24),
const SizedBox(height: UiConstants.space6),
// Quick Stats
Row(
children: <Widget>[
Expanded(
child: PaymentStatsCard(
icon: LucideIcons.trendingUp,
iconColor: const Color(0xFF059669),
icon: UiIcons.chart,
iconColor: UiColors.success,
label: "This Week",
amount: "\$${state.summary.weeklyEarnings}",
),
),
const SizedBox(width: 12),
const SizedBox(width: UiConstants.space3),
Expanded(
child: PaymentStatsCard(
icon: LucideIcons.calendar,
iconColor: const Color(0xFF2563EB),
icon: UiIcons.calendar,
iconColor: UiColors.primary,
label: "This Month",
amount: "\$${state.summary.monthlyEarnings.toStringAsFixed(0)}",
),
),
],
),
const SizedBox(height: 16),
const SizedBox(height: UiConstants.space4),
// Pending Pay
if(state.summary.pendingEarnings > 0) PendingPayCard(
amount: state.summary.pendingEarnings,
onCashOut: () {
Modular.to.pushNamed('/early-pay');
},
),
const SizedBox(height: 24),
if (state.summary.pendingEarnings > 0)
PendingPayCard(
amount: state.summary.pendingEarnings,
onCashOut: () {
Modular.to.pushNamed('/early-pay');
},
),
const SizedBox(height: UiConstants.space6),
// Recent Payments
if (state.history.isNotEmpty) Column(
children: <Widget>[
const Text(
"Recent Payments",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF0F172A),
if (state.history.isNotEmpty)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"Recent Payments",
style: UiTypography.body2m.textPrimary,
),
),
const SizedBox(height: 12),
Column(
children: state.history.map((StaffPayment payment) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: PaymentHistoryItem(
amount: payment.amount,
title: "Shift Payment",
location: "Varies",
address: "Payment ID: ${payment.id}",
date: payment.paidAt != null
? DateFormat('E, MMM d').format(payment.paidAt!)
: 'Pending',
workedTime: "Completed",
hours: 0,
rate: 0.0,
status: payment.status.name.toUpperCase(),
),
);
}).toList(),
),
],
),
const SizedBox(height: UiConstants.space3),
Column(
children: state.history.map((StaffPayment payment) {
return Padding(
padding: const EdgeInsets.only(
bottom: UiConstants.space2),
child: PaymentHistoryItem(
amount: payment.amount,
title: "Shift Payment",
location: "Varies",
address: "Payment ID: ${payment.id}",
date: payment.paidAt != null
? DateFormat('E, MMM d')
.format(payment.paidAt!)
: 'Pending',
workedTime: "Completed",
hours: 0,
rate: 0.0,
status: payment.status.name.toUpperCase(),
),
);
}).toList(),
),
],
),
const SizedBox(height: 100),
],
@@ -225,19 +228,17 @@ class _PaymentsPageState extends State<PaymentsPage> {
child: GestureDetector(
onTap: () => _bloc.add(ChangePeriodEvent(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 : Colors.transparent,
borderRadius: BorderRadius.circular(UiConstants.radiusMdValue),
),
child: Center(
child: Text(
label,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: isSelected ? const Color(0xFF0032A0) : Colors.white,
),
style: isSelected
? UiTypography.body2m.copyWith(color: UiColors.primary)
: UiTypography.body2m.white,
),
),
),

View File

@@ -1,3 +1,4 @@
import 'package:design_system/design_system.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
@@ -25,26 +26,30 @@ class EarningsGraph extends StatelessWidget {
return Container(
height: 200,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
color: UiColors.white,
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
),
child: Center(
child: Text(
"No sufficient data for graph",
style: UiTypography.body2r.textSecondary,
),
),
child: const Center(child: Text("No sufficient data for graph")),
);
}
final List<FlSpot> spots = _generateSpots(validPayments);
final double maxX = spots.isNotEmpty ? spots.last.x : 0.0;
final double maxY = spots.isNotEmpty ? spots.map((FlSpot s) => s.y).reduce((double a, double b) => a > b ? a : b) : 0.0;
return Container(
height: 220,
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
color: UiColors.white,
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: UiColors.black.withValues(alpha: 0.05),
offset: const Offset(0, 4),
blurRadius: 12,
),
@@ -53,15 +58,11 @@ class EarningsGraph extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text(
Text(
"Earnings Trend",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF0F172A),
),
style: UiTypography.body2b.textPrimary,
),
const SizedBox(height: 16),
const SizedBox(height: UiConstants.space4),
Expanded(
child: LineChart(
LineChartData(
@@ -79,7 +80,7 @@ class EarningsGraph extends StatelessWidget {
padding: const EdgeInsets.only(top: 8.0),
child: Text(
DateFormat('d').format(validPayments[index].paidAt!),
style: const TextStyle(fontSize: 10, color: Colors.grey),
style: UiTypography.footnote1r.textSecondary,
),
);
}
@@ -96,13 +97,13 @@ class EarningsGraph extends StatelessWidget {
LineChartBarData(
spots: spots,
isCurved: true,
color: const Color(0xFF0032A0),
color: UiColors.primary,
barWidth: 3,
isStrokeCapRound: true,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(
show: true,
color: const Color(0xFF0032A0).withOpacity(0.1),
color: UiColors.primary.withValues(alpha: 0.1),
),
),
],
@@ -121,7 +122,7 @@ class EarningsGraph extends StatelessWidget {
List<FlSpot> _generateSpots(List<StaffPayment> data) {
// Generate spots based on index in the list for simplicity in this demo
// Real implementation would map to actual dates on X-axis
return List.generate(data.length, (int index) {
return List<FlSpot>.generate(data.length, (int index) {
return FlSpot(index.toDouble(), data[index].amount);
});
}

View File

@@ -1,5 +1,5 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
class PaymentHistoryItem extends StatelessWidget {
@@ -28,13 +28,13 @@ class PaymentHistoryItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
color: UiColors.white,
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 2,
offset: const Offset(0, 1),
),
@@ -50,23 +50,20 @@ class PaymentHistoryItem extends StatelessWidget {
width: 6,
height: 6,
decoration: const BoxDecoration(
color: Color(0xFF3B82F6), // blue-500
color: UiColors.primary,
shape: BoxShape.circle,
),
),
const SizedBox(width: 6),
const Text(
Text(
"PAID",
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w700,
color: Color(0xFF2563EB), // blue-600
letterSpacing: 0.5,
style: UiTypography.titleUppercase4b.copyWith(
color: UiColors.primary,
),
),
],
),
const SizedBox(height: 12),
const SizedBox(height: UiConstants.space3),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -76,16 +73,16 @@ class PaymentHistoryItem extends StatelessWidget {
width: 44,
height: 44,
decoration: BoxDecoration(
color: const Color(0xFFF1F5F9), // slate-100
borderRadius: BorderRadius.circular(12),
color: UiColors.secondary,
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
),
child: const Icon(
LucideIcons.dollarSign,
color: Color(0xFF334155), // slate-700
UiIcons.chart,
color: UiColors.mutedForeground,
size: 24,
),
),
const SizedBox(width: 12),
const SizedBox(width: UiConstants.space3),
// Content
Expanded(
@@ -101,18 +98,11 @@ class PaymentHistoryItem extends StatelessWidget {
children: <Widget>[
Text(
title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF0F172A), // slate-900
),
style: UiTypography.body2b.textPrimary,
),
Text(
location,
style: const TextStyle(
fontSize: 12,
color: Color(0xFF475569), // slate-600
),
style: UiTypography.body3r.textSecondary,
),
],
),
@@ -122,75 +112,59 @@ class PaymentHistoryItem extends StatelessWidget {
children: <Widget>[
Text(
"\$${amount.toStringAsFixed(0)}",
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF0F172A), // slate-900
),
style: UiTypography.headline4m.textPrimary,
),
Text(
"\$${rate.toStringAsFixed(0)}/hr · ${hours}h",
style: const TextStyle(
fontSize: 10,
color: Color(0xFF64748B), // slate-500
),
style: UiTypography.footnote1r.textSecondary,
),
],
),
],
),
const SizedBox(height: 8),
const SizedBox(height: UiConstants.space2),
// Date and Time
Row(
children: <Widget>[
const Icon(
LucideIcons.calendar,
UiIcons.calendar,
size: 12,
color: Color(0xFF64748B),
color: UiColors.mutedForeground,
),
const SizedBox(width: 8),
const SizedBox(width: UiConstants.space2),
Text(
date,
style: const TextStyle(
fontSize: 12,
color: Color(0xFF64748B),
),
style: UiTypography.body3r.textSecondary,
),
const SizedBox(width: 8),
const SizedBox(width: UiConstants.space2),
const Icon(
LucideIcons.clock,
UiIcons.clock,
size: 12,
color: Color(0xFF64748B),
color: UiColors.mutedForeground,
),
const SizedBox(width: 8),
const SizedBox(width: UiConstants.space2),
Text(
workedTime,
style: const TextStyle(
fontSize: 12,
color: Color(0xFF64748B),
),
style: UiTypography.body3r.textSecondary,
),
],
),
const SizedBox(height: 4),
const SizedBox(height: 1),
// Address
Row(
children: <Widget>[
const Icon(
LucideIcons.mapPin,
UiIcons.mapPin,
size: 12,
color: Color(0xFF64748B),
color: UiColors.mutedForeground,
),
const SizedBox(width: 8),
const SizedBox(width: UiConstants.space2),
Expanded(
child: Text(
address,
style: const TextStyle(
fontSize: 12,
color: Color(0xFF64748B),
),
style: UiTypography.body3r.textSecondary,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),

View File

@@ -1,7 +1,7 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
class PaymentStatsCard extends StatelessWidget {
const PaymentStatsCard({
super.key,
required this.icon,
@@ -9,6 +9,7 @@ class PaymentStatsCard extends StatelessWidget {
required this.label,
required this.amount,
});
final IconData icon;
final Color iconColor;
final String label;
@@ -17,13 +18,13 @@ class PaymentStatsCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
color: UiColors.white,
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 2,
offset: const Offset(0, 1),
),
@@ -35,24 +36,17 @@ class PaymentStatsCard extends StatelessWidget {
Row(
children: <Widget>[
Icon(icon, size: 16, color: iconColor),
const SizedBox(width: 8),
const SizedBox(width: UiConstants.space2),
Text(
label,
style: const TextStyle(
fontSize: 12,
color: Color(0xFF64748B), // slate-500
),
style: UiTypography.body3r.textSecondary,
),
],
),
const SizedBox(height: 8),
const SizedBox(height: UiConstants.space2),
Text(
amount,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Color(0xFF0F172A), // slate-900
),
style: UiTypography.headline1m.textPrimary,
),
],
),

View File

@@ -1,5 +1,5 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
class PendingPayCard extends StatelessWidget {
@@ -14,17 +14,13 @@ class PendingPayCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(14),
padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: <Color>[Color(0xFFEFF6FF), Color(0xFFEFF6FF)], // blue-50 to blue-50
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
color: UiColors.tagInProgress,
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 2,
offset: const Offset(0, 1),
),
@@ -36,63 +32,34 @@ class PendingPayCard extends StatelessWidget {
Row(
children: <Widget>[
Container(
width: 40,
height: 40,
width: UiConstants.space10,
height: UiConstants.space10,
decoration: BoxDecoration(
color: const Color(0xFFE8F0FF),
borderRadius: BorderRadius.circular(8),
color: UiColors.white.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(UiConstants.radiusMdValue),
),
child: const Icon(
LucideIcons.dollarSign,
color: Color(0xFF0047FF),
UiIcons.chart,
color: UiColors.primary,
size: 20,
),
),
const SizedBox(width: 10),
const SizedBox(width: UiConstants.space3),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text(
Text(
"Pending",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Color(0xFF0F172A), // slate-900
fontSize: 14,
),
style: UiTypography.body2b.textPrimary,
),
Text(
"\$${amount.toStringAsFixed(0)} available",
style: const TextStyle(
fontSize: 12,
color: Color(0xFF475569), // slate-600
fontWeight: FontWeight.w500,
),
style: UiTypography.body3m.textSecondary,
),
],
),
],
),
/*
ElevatedButton.icon(
onPressed: onCashOut,
icon: const Icon(LucideIcons.zap, size: 14),
label: const Text("Early Pay"),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0047FF),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
elevation: 4,
shadowColor: Colors.black.withOpacity(0.2),
textStyle: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
*/
],
),
);