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,22 +1,21 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart' hide ReadContext;
import 'package:flutter_modular/flutter_modular.dart';
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../blocs/profile_cubit.dart';
import '../blocs/profile_state.dart';
import 'package:krow_core/core.dart';
import '../widgets/language_selector_bottom_sheet.dart';
import '../widgets/logout_button.dart';
import '../widgets/profile_header.dart';
import '../widgets/profile_menu_grid.dart';
import '../widgets/profile_menu_item.dart';
import '../widgets/profile_header.dart';
import '../widgets/reliability_score_bar.dart';
import '../widgets/reliability_stats_card.dart';
import '../widgets/reliability_stats_card.dart';
import '../widgets/section_title.dart';
import '../widgets/language_selector_bottom_sheet.dart';
/// The main Staff Profile page.
///
@@ -68,17 +67,15 @@ class StaffProfilePage extends StatelessWidget {
},
builder: (context, state) {
// Show loading spinner if status is loading
if (state.status == ProfileStatus.loading) {
return const Center(child: CircularProgressIndicator());
}
if (state.status == ProfileStatus.loading) {
return const Center(child: CircularProgressIndicator());
}
if (state.status == ProfileStatus.error) {
if (state.status == ProfileStatus.error) {
return Center(
child: Text(
state.errorMessage ?? 'An error occurred',
style: UiTypography.body1r.copyWith(
color: UiColors.destructive,
),
style: UiTypography.body1r.textError,
),
);
}
@@ -121,7 +118,6 @@ class StaffProfilePage extends StatelessWidget {
SectionTitle(i18n.sections.onboarding),
ProfileMenuGrid(
crossAxisCount: 3,
children: [
ProfileMenuItem(
icon: UiIcons.user,
@@ -181,18 +177,23 @@ class StaffProfilePage extends StatelessWidget {
),
const SizedBox(height: UiConstants.space6),
SectionTitle(
i18n.header.title.contains("Perfil") ? "Ajustes" : "Settings",
i18n.header.title.contains("Perfil")
? "Ajustes"
: "Settings",
),
ProfileMenuGrid(
crossAxisCount: 3,
children: [
ProfileMenuItem(
icon: UiIcons.globe,
label: i18n.header.title.contains("Perfil") ? "Idioma" : "Language",
label: i18n.header.title.contains("Perfil")
? "Idioma"
: "Language",
onTap: () {
showModalBottomSheet(
context: context,
builder: (context) => const LanguageSelectorBottomSheet(),
builder: (context) =>
const LanguageSelectorBottomSheet(),
);
},
),

View File

@@ -1,7 +1,6 @@
import 'package:core_localization/core_localization.dart';
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';
/// A bottom sheet that allows the user to select their preferred language.
@@ -15,10 +14,12 @@ class LanguageSelectorBottomSheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(UiConstants.space6),
decoration: BoxDecoration(
padding: const EdgeInsets.all(UiConstants.space6),
decoration: const BoxDecoration(
color: UiColors.background,
borderRadius: BorderRadius.vertical(top: Radius.circular(UiConstants.radiusBase)),
borderRadius: BorderRadius.vertical(
top: Radius.circular(UiConstants.radiusBase),
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
@@ -29,19 +30,19 @@ class LanguageSelectorBottomSheet extends StatelessWidget {
style: UiTypography.headline4m,
textAlign: TextAlign.center,
),
SizedBox(height: UiConstants.space6),
const SizedBox(height: UiConstants.space6),
_buildLanguageOption(
context,
label: 'English',
locale: AppLocale.en,
),
SizedBox(height: UiConstants.space4),
const SizedBox(height: UiConstants.space4),
_buildLanguageOption(
context,
label: 'Español',
locale: AppLocale.es,
),
SizedBox(height: UiConstants.space6),
const SizedBox(height: UiConstants.space6),
],
),
);
@@ -62,21 +63,23 @@ class LanguageSelectorBottomSheet extends StatelessWidget {
onTap: () {
// Dispatch the ChangeLocale event to the LocaleBloc
Modular.get<LocaleBloc>().add(ChangeLocale(locale.flutterLocale));
// Close the bottom sheet
Navigator.pop(context);
// Force a rebuild of the entire app to reflect locale change instantly if not handled by root widget
// (Usually handled by BlocBuilder at the root, but this ensures settings are updated)
},
borderRadius: BorderRadius.circular(UiConstants.radiusMdValue),
child: Container(
padding: EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
horizontal: UiConstants.space4,
),
decoration: BoxDecoration(
color: isSelected ? UiColors.primary.withValues(alpha: 0.1) : UiColors.background,
color: isSelected
? UiColors.primary.withValues(alpha: 0.1)
: UiColors.background,
borderRadius: BorderRadius.circular(UiConstants.radiusMdValue),
border: Border.all(
color: isSelected ? UiColors.primary : UiColors.border,
@@ -88,12 +91,10 @@ class LanguageSelectorBottomSheet extends StatelessWidget {
children: [
Text(
label,
style: isSelected
? UiTypography.body1b.copyWith(color: UiColors.primary)
: UiTypography.body1r,
style: isSelected ? UiTypography.body1b.primary : UiTypography.body1r,
),
if (isSelected)
Icon(
const Icon(
UiIcons.check,
color: UiColors.primary,
size: 24.0,

View File

@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// The sign-out button widget.
///
@@ -14,31 +13,33 @@ class LogoutButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final i18n = t.staff.profile.header;
return Container(
width: double.infinity,
decoration: BoxDecoration(
color: UiColors.bgPopup,
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border),
),
child: Material(
color: const Color(0x00000000),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
borderRadius: UiConstants.radiusLg,
child: Padding(
padding: EdgeInsets.symmetric(vertical: UiConstants.space4),
padding: const EdgeInsets.symmetric(vertical: UiConstants.space4),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(LucideIcons.logOut, color: UiColors.destructive, size: 20),
SizedBox(width: UiConstants.space2),
const Icon(
UiIcons.logOut,
color: UiColors.destructive,
size: 20,
),
const SizedBox(width: UiConstants.space2),
Text(
i18n.sign_out,
style: UiTypography.body1m.copyWith(
color: UiColors.destructive,
),
style: UiTypography.body1m.textError,
),
],
),

View File

@@ -34,13 +34,13 @@ class ProfileHeader extends StatelessWidget {
return Container(
width: double.infinity,
padding: EdgeInsets.fromLTRB(
padding: const EdgeInsets.fromLTRB(
UiConstants.space5,
UiConstants.space5,
UiConstants.space5,
UiConstants.space16,
),
decoration: BoxDecoration(
decoration: const BoxDecoration(
color: UiColors.primary,
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(UiConstants.space6),
@@ -56,22 +56,20 @@ class ProfileHeader extends StatelessWidget {
children: [
Text(
i18n.title,
style: UiTypography.headline4m.copyWith(
color: UiColors.primaryForeground,
),
style: UiTypography.headline4m.textSecondary,
),
GestureDetector(
onTap: onSignOutTap,
child: Text(
i18n.sign_out,
style: UiTypography.body2m.copyWith(
color: UiColors.primaryForeground.withOpacity(0.8),
color: UiColors.primaryForeground.withValues(alpha: 0.8),
),
),
),
],
),
SizedBox(height: UiConstants.space8),
const SizedBox(height: UiConstants.space8),
// Avatar Section
Stack(
alignment: Alignment.bottomRight,
@@ -79,7 +77,7 @@ class ProfileHeader extends StatelessWidget {
Container(
width: 112,
height: 112,
padding: EdgeInsets.all(UiConstants.space1),
padding: const EdgeInsets.all(UiConstants.space1),
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
@@ -87,13 +85,13 @@ class ProfileHeader extends StatelessWidget {
end: Alignment.bottomRight,
colors: [
UiColors.accent,
UiColors.accent.withOpacity(0.5),
UiColors.accent.withValues(alpha: 0.5),
UiColors.primaryForeground,
],
),
boxShadow: [
BoxShadow(
color: UiColors.foreground.withOpacity(0.2),
color: UiColors.foreground.withValues(alpha: 0.2),
blurRadius: 10,
offset: const Offset(0, 4),
),
@@ -103,7 +101,7 @@ class ProfileHeader extends StatelessWidget {
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: UiColors.primaryForeground.withOpacity(0.2),
color: UiColors.primaryForeground.withValues(alpha: 0.2),
width: 4,
),
),
@@ -123,16 +121,16 @@ class ProfileHeader extends StatelessWidget {
end: Alignment.bottomRight,
colors: [
UiColors.accent,
UiColors.accent.withOpacity(0.7),
UiColors.accent.withValues(alpha: 0.7),
],
),
),
alignment: Alignment.center,
child: Text(
fullName.isNotEmpty ? fullName[0].toUpperCase() : 'K',
style: UiTypography.displayM.copyWith(
color: UiColors.primary,
),
fullName.isNotEmpty
? fullName[0].toUpperCase()
: 'K',
style: UiTypography.displayM.primary,
),
)
: null,
@@ -148,7 +146,7 @@ class ProfileHeader extends StatelessWidget {
border: Border.all(color: UiColors.primary, width: 2),
boxShadow: [
BoxShadow(
color: UiColors.foreground.withOpacity(0.1),
color: UiColors.foreground.withValues(alpha: 0.1),
blurRadius: 4,
),
],
@@ -161,28 +159,24 @@ class ProfileHeader extends StatelessWidget {
),
],
),
SizedBox(height: UiConstants.space4),
const SizedBox(height: UiConstants.space4),
Text(
fullName,
style: UiTypography.headline3m.copyWith(
color: UiColors.primaryForeground,
),
style: UiTypography.headline3m.textPlaceholder,
),
SizedBox(height: UiConstants.space1),
const SizedBox(height: UiConstants.space1),
Container(
padding: EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space3,
vertical: UiConstants.space1,
),
decoration: BoxDecoration(
color: UiColors.accent.withOpacity(0.2),
color: UiColors.accent.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(UiConstants.space5),
),
child: Text(
level,
style: UiTypography.footnote1b.copyWith(
color: UiColors.accent,
),
style: UiTypography.footnote1b.accent,
),
),
],

View File

@@ -25,10 +25,10 @@ class ProfileMenuItem extends StatelessWidget {
child: Container(
decoration: BoxDecoration(
color: UiColors.bgPopup,
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border),
),
padding: EdgeInsets.all(UiConstants.space2),
padding: const EdgeInsets.all(UiConstants.space2),
child: AspectRatio(
aspectRatio: 1.0,
child: Stack(
@@ -42,24 +42,23 @@ class ProfileMenuItem extends StatelessWidget {
width: 36,
height: 36,
decoration: BoxDecoration(
color: UiColors.primary.withOpacity(0.08),
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
color: UiColors.primary.withValues(alpha: 0.08),
borderRadius: UiConstants.radiusLg,
),
alignment: Alignment.center,
child: Icon(icon, color: UiColors.primary, size: 20),
),
SizedBox(height: UiConstants.space1),
const SizedBox(height: UiConstants.space1),
Padding(
padding: EdgeInsets.symmetric(horizontal: UiConstants.space1),
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space1,
),
child: Text(
label,
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: UiTypography.footnote1m.copyWith(
color: UiColors.foreground,
height: 1.2,
),
style: UiTypography.footnote1m.textSecondary,
),
),
],
@@ -76,16 +75,18 @@ class ProfileMenuItem extends StatelessWidget {
shape: BoxShape.circle,
color: completed!
? UiColors.primary
: UiColors.primary.withOpacity(0.1),
: UiColors.primary.withValues(alpha: 0.1),
),
alignment: Alignment.center,
child: completed!
? const Icon(Icons.check, size: 10, color: UiColors.primaryForeground)
? const Icon(
UiIcons.check,
size: 10,
color: UiColors.primaryForeground,
)
: Text(
"!",
style: UiTypography.footnote2b.copyWith(
color: UiColors.primary,
),
style: UiTypography.footnote2b.primary,
),
),
),

View File

@@ -19,10 +19,10 @@ class ReliabilityScoreBar extends StatelessWidget {
final score = (reliabilityScore ?? 0) / 100;
return Container(
padding: EdgeInsets.all(UiConstants.space4),
padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration(
color: UiColors.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
color: UiColors.primary.withValues(alpha: 0.1),
borderRadius: UiConstants.radiusLg,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -32,19 +32,15 @@ class ReliabilityScoreBar extends StatelessWidget {
children: [
Text(
i18n.title,
style: UiTypography.body2m.copyWith(
color: UiColors.primary,
),
style: UiTypography.body2m.primary,
),
Text(
"${reliabilityScore ?? 0}%",
style: UiTypography.headline4m.copyWith(
color: UiColors.primary,
),
style: UiTypography.headline4m.primary,
),
],
),
SizedBox(height: UiConstants.space2),
const SizedBox(height: UiConstants.space2),
ClipRRect(
borderRadius: BorderRadius.circular(UiConstants.space1),
child: LinearProgressIndicator(
@@ -55,12 +51,10 @@ class ReliabilityScoreBar extends StatelessWidget {
),
),
Padding(
padding: EdgeInsets.only(top: UiConstants.space2),
padding: const EdgeInsets.only(top: UiConstants.space2),
child: Text(
i18n.description,
style: UiTypography.footnote2r.copyWith(
color: UiColors.mutedForeground,
),
style: UiTypography.footnote2r.textSecondary,
),
),
],

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// Displays the staff member's reliability statistics (Shifts, Rating, On Time, etc.).
///
@@ -24,14 +23,14 @@ class ReliabilityStatsCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(UiConstants.space4),
padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration(
color: UiColors.bgPopup,
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border),
boxShadow: [
BoxShadow(
color: UiColors.foreground.withOpacity(0.05),
color: UiColors.foreground.withValues(alpha: 0.05),
blurRadius: 4,
offset: const Offset(0, 1),
),
@@ -42,31 +41,31 @@ class ReliabilityStatsCard extends StatelessWidget {
children: [
_buildStatItem(
context,
LucideIcons.briefcase,
UiIcons.briefcase,
"${totalShifts ?? 0}",
"Shifts",
),
_buildStatItem(
context,
LucideIcons.star,
UiIcons.star,
(averageRating ?? 0.0).toStringAsFixed(1),
"Rating",
),
_buildStatItem(
context,
LucideIcons.clock,
UiIcons.clock,
"${onTimeRate ?? 0}%",
"On Time",
),
_buildStatItem(
context,
LucideIcons.xCircle,
UiIcons.xCircle,
"${noShowCount ?? 0}",
"No Shows",
),
_buildStatItem(
context,
LucideIcons.ban,
UiIcons.ban,
"${cancellationCount ?? 0}",
"Cancel.",
),
@@ -88,26 +87,22 @@ class ReliabilityStatsCard extends StatelessWidget {
width: 40,
height: 40,
decoration: BoxDecoration(
color: UiColors.primary.withOpacity(0.1),
color: UiColors.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(UiConstants.radiusMdValue),
),
alignment: Alignment.center,
child: Icon(icon, size: 20, color: UiColors.primary),
),
SizedBox(height: UiConstants.space1),
const SizedBox(height: UiConstants.space1),
Text(
value,
style: UiTypography.body1b.copyWith(
color: UiColors.foreground,
),
style: UiTypography.body1b.textSecondary,
),
FittedBox(
fit: BoxFit.scaleDown,
child: Text(
label,
style: UiTypography.footnote2r.copyWith(
color: UiColors.mutedForeground,
),
style: UiTypography.footnote2r.textSecondary,
),
),
],

View File

@@ -13,14 +13,11 @@ class SectionTitle extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: EdgeInsets.only(left: UiConstants.space1),
margin: EdgeInsets.only(bottom: UiConstants.space3),
padding: const EdgeInsets.only(left: UiConstants.space1),
margin: const EdgeInsets.only(bottom: UiConstants.space3),
child: Text(
title.toUpperCase(),
style: UiTypography.footnote1b.copyWith(
color: UiColors.mutedForeground,
letterSpacing: 0.5,
),
style: UiTypography.footnote1b.textSecondary,
),
);
}