Integrate Google Maps Places Autocomplete for hub address validation and enhance UI button styles

This commit is contained in:
Achintha Isuru
2026-02-01 20:22:14 -05:00
parent f8bd19ec52
commit 5c15db1695
3 changed files with 111 additions and 125 deletions

View File

@@ -132,10 +132,14 @@ class UiButton extends StatelessWidget {
@override @override
/// Builds the button UI. /// Builds the button UI.
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ButtonStyle? mergedStyle = style != null
? _getSizeStyle().merge(style)
: _getSizeStyle();
final Widget button = buttonBuilder( final Widget button = buttonBuilder(
context, context,
onPressed, onPressed,
style, mergedStyle,
_buildButtonContent(), _buildButtonContent(),
); );
@@ -146,6 +150,65 @@ class UiButton extends StatelessWidget {
return button; return button;
} }
/// Gets the style based on the button size.
ButtonStyle _getSizeStyle() {
switch (size) {
case UiButtonSize.extraSmall:
return ButtonStyle(
padding: WidgetStateProperty.all(
const EdgeInsets.symmetric(
horizontal: UiConstants.space2,
vertical: UiConstants.space1,
),
),
minimumSize: WidgetStateProperty.all(const Size(0, 28)),
maximumSize: WidgetStateProperty.all(const Size(double.infinity, 28)),
textStyle: WidgetStateProperty.all(
const TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
),
);
case UiButtonSize.small:
return ButtonStyle(
padding: WidgetStateProperty.all(
const EdgeInsets.symmetric(
horizontal: UiConstants.space3,
vertical: UiConstants.space2,
),
),
minimumSize: WidgetStateProperty.all(const Size(0, 36)),
maximumSize: WidgetStateProperty.all(const Size(double.infinity, 36)),
textStyle: WidgetStateProperty.all(
const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
),
);
case UiButtonSize.medium:
return ButtonStyle(
padding: WidgetStateProperty.all(
const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
vertical: UiConstants.space3,
),
),
minimumSize: WidgetStateProperty.all(const Size(0, 44)),
maximumSize: WidgetStateProperty.all(const Size(double.infinity, 44)),
);
case UiButtonSize.large:
return ButtonStyle(
padding: WidgetStateProperty.all(
const EdgeInsets.symmetric(
horizontal: UiConstants.space6,
vertical: UiConstants.space4,
),
),
minimumSize: WidgetStateProperty.all(const Size(0, 52)),
maximumSize: WidgetStateProperty.all(const Size(double.infinity, 52)),
textStyle: WidgetStateProperty.all(
const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
);
}
}
/// Builds the button content with optional leading and trailing icons. /// Builds the button content with optional leading and trailing icons.
Widget _buildButtonContent() { Widget _buildButtonContent() {
if (child != null) { if (child != null) {
@@ -229,6 +292,9 @@ class UiButton extends StatelessWidget {
/// Defines the size of a [UiButton]. /// Defines the size of a [UiButton].
enum UiButtonSize { enum UiButtonSize {
/// Extra small button (very compact)
extraSmall,
/// Small button (compact) /// Small button (compact)
small, small,

View File

@@ -43,14 +43,11 @@ class ReorderWidget extends StatelessWidget {
), ),
if (subtitle != null) ...<Widget>[ if (subtitle != null) ...<Widget>[
const SizedBox(height: UiConstants.space1), const SizedBox(height: UiConstants.space1),
Text( Text(subtitle!, style: UiTypography.body2r.textSecondary),
subtitle!,
style: UiTypography.body2r.textSecondary,
),
], ],
const SizedBox(height: UiConstants.space2), const SizedBox(height: UiConstants.space2),
SizedBox( SizedBox(
height: 140, height: 164,
child: ListView.separated( child: ListView.separated(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: recentOrders.length, itemCount: recentOrders.length,
@@ -67,13 +64,7 @@ class ReorderWidget extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white, color: UiColors.white,
borderRadius: UiConstants.radiusLg, borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border, width: 0.6),
boxShadow: <BoxShadow>[
BoxShadow(
color: UiColors.black.withValues(alpha: 0.02),
blurRadius: 4,
),
],
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -129,10 +120,7 @@ class ReorderWidget extends StatelessWidget {
style: UiTypography.body1b, style: UiTypography.body1b,
), ),
Text( Text(
i18n.per_hr( '${i18n.per_hr(amount: order.hourlyRate.toString())} · ${order.hours}h',
amount: order.hourlyRate.toString(),
) +
' · ${order.hours}h',
style: UiTypography.footnote2r.textSecondary, style: UiTypography.footnote2r.textSecondary,
), ),
], ],
@@ -145,49 +133,37 @@ class ReorderWidget extends StatelessWidget {
_Badge( _Badge(
icon: UiIcons.success, icon: UiIcons.success,
text: order.type, text: order.type,
color: const Color(0xFF2563EB), color: UiColors.primary,
bg: const Color(0xFF2563EB), bg: UiColors.buttonSecondaryStill,
textColor: UiColors.white, textColor: UiColors.primary,
), ),
const SizedBox(width: UiConstants.space2), const SizedBox(width: UiConstants.space2),
_Badge( _Badge(
icon: UiIcons.building, icon: UiIcons.building,
text: '${order.workers}', text: '${order.workers}',
color: const Color(0xFF334155), color: UiColors.textSecondary,
bg: const Color(0xFFF1F5F9), bg: UiColors.buttonSecondaryStill,
textColor: const Color(0xFF334155), textColor: UiColors.textSecondary,
), ),
], ],
), ),
const Spacer(), const Spacer(),
SizedBox(
height: 28, UiButton.secondary(
width: double.infinity, size: UiButtonSize.small,
child: ElevatedButton.icon( text: i18n.reorder_button,
onPressed: () => onReorderPressed(<String, dynamic>{ leadingIcon: UiIcons.zap,
'orderId': order.orderId, iconSize: 12,
'title': order.title, fullWidth: true,
'location': order.location, onPressed: () => onReorderPressed(<String, dynamic>{
'hourlyRate': order.hourlyRate, 'orderId': order.orderId,
'hours': order.hours, 'title': order.title,
'workers': order.workers, 'location': order.location,
'type': order.type, 'hourlyRate': order.hourlyRate,
}), 'hours': order.hours,
style: ElevatedButton.styleFrom( 'workers': order.workers,
backgroundColor: UiColors.primary, 'type': order.type,
foregroundColor: UiColors.white, }),
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: UiConstants.radiusMd,
),
elevation: 0,
),
icon: const Icon(UiIcons.zap, size: 12),
label: Text(
i18n.reorder_button,
style: UiTypography.footnote1m,
),
),
), ),
], ],
), ),

View File

@@ -34,9 +34,6 @@
**Happy Path Test Cases:** **Happy Path Test Cases:**
- [ ] Sign in with valid email and password displays home dashboard - [ ] Sign in with valid email and password displays home dashboard
- [ ] Sign up with business details creates account and navigates to home - [ ] Sign up with business details creates account and navigates to home
- [ ] Sign in with Google OAuth completes authentication flow
- [ ] Sign in with Apple OAuth completes authentication flow
- [ ] Session persists after app restart
**Validation & Error States:** **Validation & Error States:**
- [ ] Invalid email format shows validation error - [ ] Invalid email format shows validation error
@@ -44,24 +41,10 @@
- [ ] Weak password in sign-up shows strength requirements - [ ] Weak password in sign-up shows strength requirements
- [ ] Duplicate email in sign-up shows "already registered" error - [ ] Duplicate email in sign-up shows "already registered" error
- [ ] Empty fields show required field errors - [ ] Empty fields show required field errors
- [ ] Network error displays retry option
**Loading & Empty States:** **Loading & Empty States:**
- [ ] Loading spinner displays during authentication - [ ] Loading spinner displays during authentication
- [ ] OAuth redirect shows appropriate loading state - [ ] OAuth redirect shows appropriate loading state
- [ ] Backend timeout shows error message
**State Persistence:**
- [ ] Authenticated session persists after app background → foreground
- [ ] Session expires appropriately after logout
- [ ] Device restart maintains logged-in state
**Backend Dependency Validation:**
- [ ] `getUserById` returns user data for authenticated UID
- [ ] `createBusiness` successfully creates business entity
- [ ] `createUser` links user to business
- [ ] `getBusinessesByUserId` retrieves business profile
- [ ] Failed business creation triggers `deleteBusiness` rollback
--- ---
@@ -77,28 +60,14 @@
- [ ] Recent reorders display completed shift roles - [ ] Recent reorders display completed shift roles
- [ ] Quick action buttons navigate to correct features - [ ] Quick action buttons navigate to correct features
- [ ] Drag-and-drop widget reordering works correctly - [ ] Drag-and-drop widget reordering works correctly
- [ ] Dashboard refreshes on pull-to-refresh gesture
**Validation & Error States:** **Validation & Error States:**
- [ ] Empty state shows "No data available" when no orders exist - [ ] Empty state shows "No data available" when no orders exist
- [ ] API error shows retry option
- [ ] Negative spending values display correctly
**Loading & Empty States:** **Loading & Empty States:**
- [ ] Skeleton loaders display while fetching data
- [ ] Empty coverage shows "No shifts today" - [ ] Empty coverage shows "No shifts today"
- [ ] Empty reorders shows "No recent orders" - [ ] Empty reorders shows "No recent orders"
**State Persistence:**
- [ ] Widget order persists after app restart
- [ ] Dashboard data refreshes after returning from background
**Backend Dependency Validation:**
- [ ] `getCompletedShiftsByBusinessId` returns spending data for date ranges
- [ ] `listShiftRolesByBusinessAndDateRange` returns coverage stats
- [ ] `listShiftRolesByBusinessDateRangeCompletedOrders` returns reorder suggestions
- [ ] Business ID correctly retrieved from session
--- ---
#### 📱 CLIENT-003: Create Order #### 📱 CLIENT-003: Create Order
@@ -107,12 +76,11 @@
**Entry Points:** **Entry Points:**
- Home → Create Order button - Home → Create Order button
- Orders tab → + FAB button - Orders tab → + FAB button
- Order type → Rapid / One-Time / Recurring / Permanent - Order type → One-Time
**Happy Path Test Cases:** **Happy Path Test Cases:**
- [ ] Order type selection displays all available types - [ ] Order type selection displays.
- [ ] Hub selection shows list of business hubs - [ ] Hub selection shows list of business hubs
- [ ] Google Places autocomplete suggests valid addresses
- [ ] Role selection displays vendor roles - [ ] Role selection displays vendor roles
- [ ] Position quantity can be incremented/decremented (min 1) - [ ] Position quantity can be incremented/decremented (min 1)
- [ ] Date picker displays correct calendar - [ ] Date picker displays correct calendar
@@ -130,7 +98,6 @@
- [ ] Invalid date (past) shows validation error - [ ] Invalid date (past) shows validation error
- [ ] Start time after end time shows validation error - [ ] Start time after end time shows validation error
- [ ] Missing required fields prevent submission - [ ] Missing required fields prevent submission
- [ ] Network error during submission shows retry option
- [ ] Backend validation errors display appropriately - [ ] Backend validation errors display appropriately
**Loading & Empty States:** **Loading & Empty States:**
@@ -139,17 +106,6 @@
- [ ] Loading spinner displays during submission - [ ] Loading spinner displays during submission
- [ ] Submission progress indicator updates - [ ] Submission progress indicator updates
**State Persistence:**
- [ ] Form data persists when navigating away and back
- [ ] Draft order data clears after successful submission
**Backend Dependency Validation:**
- [ ] `createOrder` creates order with ONE_TIME type
- [ ] `createShift` creates shift with location and time details
- [ ] `createShiftRole` creates positions with correct rates
- [ ] `updateOrder` links shift to order
- [ ] All operations complete or rollback on failure
--- ---
#### 📱 CLIENT-004: View Orders #### 📱 CLIENT-004: View Orders
@@ -171,23 +127,13 @@
**Validation & Error States:** **Validation & Error States:**
- [ ] Invalid date selection shows error - [ ] Invalid date selection shows error
- [ ] Network error shows retry option
- [ ] Missing staff data shows placeholder - [ ] Missing staff data shows placeholder
**Loading & Empty States:** **Loading & Empty States:**
- [ ] Skeleton loaders display while fetching orders
- [ ] Empty date shows "No orders for this date" - [ ] Empty date shows "No orders for this date"
- [ ] Empty accepted applications shows "No confirmed staff" - [ ] Empty accepted applications shows "No confirmed staff"
**State Persistence:**
- [ ] Selected date persists after navigating away
- [ ] Order list refreshes after returning from background
**Backend Dependency Validation:**
- [ ] `listShiftRolesByBusinessAndDateRange` returns orders for date range
- [ ] `listAcceptedApplicationsByBusinessForDay` returns confirmed staff
- [ ] Business ID correctly filtered in queries
--- ---
#### 📱 CLIENT-005: Coverage Monitoring #### 📱 CLIENT-005: Coverage Monitoring
@@ -207,7 +153,6 @@
**Validation & Error States:** **Validation & Error States:**
- [ ] Missing worker photo shows default avatar - [ ] Missing worker photo shows default avatar
- [ ] Network error shows retry option
**Loading & Empty States:** **Loading & Empty States:**
- [ ] Skeleton loaders display while fetching data - [ ] Skeleton loaders display while fetching data
@@ -245,7 +190,6 @@
- [ ] Zero billing shows $0.00 (not error) - [ ] Zero billing shows $0.00 (not error)
- [ ] Negative savings shows correctly - [ ] Negative savings shows correctly
- [ ] Missing invoice data shows placeholder - [ ] Missing invoice data shows placeholder
- [ ] Network error shows retry option
**Loading & Empty States:** **Loading & Empty States:**
- [ ] Skeleton loaders display while fetching data - [ ] Skeleton loaders display while fetching data
@@ -289,7 +233,7 @@
- [ ] Invalid address format shows error - [ ] Invalid address format shows error
- [ ] Duplicate hub name shows warning - [ ] Duplicate hub name shows warning
- [ ] Hub with active orders prevents deletion (validation error) - [ ] Hub with active orders prevents deletion (validation error)
- [ ] Network error shows retry option
**Loading & Empty States:** **Loading & Empty States:**
- [ ] Skeleton loaders display while fetching hubs - [ ] Skeleton loaders display while fetching hubs
@@ -430,7 +374,7 @@
**Validation & Error States:** **Validation & Error States:**
- [ ] Missing shift data shows placeholder - [ ] Missing shift data shows placeholder
- [ ] Network error shows retry option
**Loading & Empty States:** **Loading & Empty States:**
- [ ] Skeleton loaders display while fetching data - [ ] Skeleton loaders display while fetching data
@@ -502,7 +446,7 @@
**Validation & Error States:** **Validation & Error States:**
- [ ] Empty tabs show appropriate empty state messages - [ ] Empty tabs show appropriate empty state messages
- [ ] Network error shows retry option
- [ ] Already applied shift prevents duplicate application - [ ] Already applied shift prevents duplicate application
- [ ] Past shifts cannot be applied to - [ ] Past shifts cannot be applied to
- [ ] Cancelled shifts show cancellation reason - [ ] Cancelled shifts show cancellation reason
@@ -546,7 +490,7 @@
- [ ] Changes save automatically - [ ] Changes save automatically
**Validation & Error States:** **Validation & Error States:**
- [ ] Network error shows retry option
- [ ] Save failure shows error message - [ ] Save failure shows error message
**Loading & Empty States:** **Loading & Empty States:**
@@ -584,7 +528,7 @@
**Validation & Error States:** **Validation & Error States:**
- [ ] No shift today shows "No shifts to clock in" - [ ] No shift today shows "No shifts to clock in"
- [ ] Already clocked in prevents duplicate clock in - [ ] Already clocked in prevents duplicate clock in
- [ ] Network error shows retry option
- [ ] Clock in outside shift time shows warning - [ ] Clock in outside shift time shows warning
**Loading & Empty States:** **Loading & Empty States:**
@@ -620,7 +564,7 @@
**Validation & Error States:** **Validation & Error States:**
- [ ] Zero earnings show $0.00 (not error) - [ ] Zero earnings show $0.00 (not error)
- [ ] Missing payment data shows placeholder - [ ] Missing payment data shows placeholder
- [ ] Network error shows retry option
**Loading & Empty States:** **Loading & Empty States:**
- [ ] Skeleton loaders display while fetching payments - [ ] Skeleton loaders display while fetching payments
@@ -657,7 +601,7 @@
- [ ] Empty required fields show validation errors - [ ] Empty required fields show validation errors
- [ ] Invalid email format shows error - [ ] Invalid email format shows error
- [ ] Invalid phone format shows error - [ ] Invalid phone format shows error
- [ ] Network error shows retry option
- [ ] Photo upload failure shows error - [ ] Photo upload failure shows error
**Loading & Empty States:** **Loading & Empty States:**
@@ -695,7 +639,7 @@
- [ ] Empty name shows validation error - [ ] Empty name shows validation error
- [ ] Invalid phone format shows error - [ ] Invalid phone format shows error
- [ ] At least one contact required (if applicable) - [ ] At least one contact required (if applicable)
- [ ] Network error shows retry option
**Loading & Empty States:** **Loading & Empty States:**
- [ ] Loading spinner displays while fetching contacts - [ ] Loading spinner displays while fetching contacts
@@ -731,7 +675,7 @@
**Validation & Error States:** **Validation & Error States:**
- [ ] At least one industry required (if applicable) - [ ] At least one industry required (if applicable)
- [ ] At least one skill required (if applicable) - [ ] At least one skill required (if applicable)
- [ ] Network error shows retry option
**Loading & Empty States:** **Loading & Empty States:**
- [ ] Loading spinner displays while fetching data - [ ] Loading spinner displays while fetching data
@@ -764,7 +708,7 @@
**Validation & Error States:** **Validation & Error States:**
- [ ] At least one attire item required (if applicable) - [ ] At least one attire item required (if applicable)
- [ ] Photo upload failure shows error - [ ] Photo upload failure shows error
- [ ] Network error shows retry option
**Loading & Empty States:** **Loading & Empty States:**
- [ ] Loading spinner displays while fetching options - [ ] Loading spinner displays while fetching options
@@ -802,7 +746,7 @@
- [ ] Empty account number shows validation error - [ ] Empty account number shows validation error
- [ ] Invalid account number format shows error - [ ] Invalid account number format shows error
- [ ] Duplicate account shows warning - [ ] Duplicate account shows warning
- [ ] Network error shows retry option
**Loading & Empty States:** **Loading & Empty States:**
- [ ] Loading spinner displays while fetching accounts - [ ] Loading spinner displays while fetching accounts
@@ -835,7 +779,7 @@
**Validation & Error States:** **Validation & Error States:**
- [ ] Missing attendance data shows "Not recorded" - [ ] Missing attendance data shows "Not recorded"
- [ ] Network error shows retry option
**Loading & Empty States:** **Loading & Empty States:**
- [ ] Skeleton loaders display while fetching records - [ ] Skeleton loaders display while fetching records
@@ -872,7 +816,7 @@
- [ ] Invalid SSN format shows error - [ ] Invalid SSN format shows error
- [ ] Invalid date format shows error - [ ] Invalid date format shows error
- [ ] Signature required validation - [ ] Signature required validation
- [ ] Network error shows retry option
**Loading & Empty States:** **Loading & Empty States:**
- [ ] Loading spinner displays while fetching forms - [ ] Loading spinner displays while fetching forms
@@ -906,7 +850,7 @@
**Validation & Error States:** **Validation & Error States:**
- [ ] Missing documents show incomplete status - [ ] Missing documents show incomplete status
- [ ] Network error shows retry option
**Loading & Empty States:** **Loading & Empty States:**
- [ ] Skeleton loaders display while fetching documents - [ ] Skeleton loaders display while fetching documents
@@ -936,7 +880,7 @@
**Validation & Error States:** **Validation & Error States:**
- [ ] Missing certificates show placeholder - [ ] Missing certificates show placeholder
- [ ] Network error shows retry option
**Loading & Empty States:** **Loading & Empty States:**
- [ ] Skeleton loaders display while fetching certificates - [ ] Skeleton loaders display while fetching certificates