feat: Enhance navigation robustness by redirecting to the appropriate home page on navigation errors or when popping the root route.

This commit is contained in:
Achintha Isuru
2026-02-28 17:02:44 -05:00
parent 76424b1b1f
commit c26128f1f2
4 changed files with 80 additions and 226 deletions

View File

@@ -1,6 +1,7 @@
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_domain/krow_domain.dart';
import '../navigation_extensions.dart';
import 'route_paths.dart';
/// Typed navigation extension for the Client application.
@@ -33,14 +34,14 @@ extension ClientNavigator on IModularNavigator {
/// This effectively logs out the user by navigating to root.
/// Used when signing out or session expires.
void toClientRoot() {
navigate(ClientPaths.root);
safeNavigate(ClientPaths.root);
}
/// Navigates to the get started page.
///
/// This is the landing page for unauthenticated users, offering login/signup options.
void toClientGetStartedPage() {
navigate(ClientPaths.getStarted);
safeNavigate(ClientPaths.getStarted);
}
/// Navigates to the client sign-in page.
@@ -48,7 +49,7 @@ extension ClientNavigator on IModularNavigator {
/// This page allows existing clients to log in using email/password
/// or social authentication providers.
void toClientSignIn() {
pushNamed(ClientPaths.signIn);
safePush(ClientPaths.signIn);
}
/// Navigates to the client sign-up page.
@@ -56,7 +57,7 @@ extension ClientNavigator on IModularNavigator {
/// This page allows new clients to create an account and provides
/// the initial registration form.
void toClientSignUp() {
pushNamed(ClientPaths.signUp);
safePush(ClientPaths.signUp);
}
/// Navigates to the client home dashboard.
@@ -66,7 +67,7 @@ extension ClientNavigator on IModularNavigator {
///
/// Uses pushNamed to avoid trailing slash issues with navigate().
void toClientHome() {
navigate(ClientPaths.home);
safeNavigate(ClientPaths.home);
}
/// Navigates to the client main shell.
@@ -74,7 +75,7 @@ extension ClientNavigator on IModularNavigator {
/// This is the container with bottom navigation. Usually you'd navigate
/// to a specific tab instead (like [toClientHome]).
void toClientMain() {
navigate(ClientPaths.main);
safeNavigate(ClientPaths.main);
}
// ==========================================================================
@@ -85,43 +86,43 @@ extension ClientNavigator on IModularNavigator {
///
/// Displays workforce coverage analytics and metrics.
void toClientCoverage() {
navigate(ClientPaths.coverage);
safeNavigate(ClientPaths.coverage);
}
/// Navigates to the Billing tab.
///
/// Access billing history, invoices, and payment methods.
void toClientBilling() {
navigate(ClientPaths.billing);
safeNavigate(ClientPaths.billing);
}
/// Navigates to the Completion Review page.
void toCompletionReview({Object? arguments}) {
pushNamed(ClientPaths.completionReview, arguments: arguments);
safePush(ClientPaths.completionReview, arguments: arguments);
}
/// Navigates to the full list of invoices awaiting approval.
void toAwaitingApproval({Object? arguments}) {
navigate(ClientPaths.awaitingApproval, arguments: arguments);
safeNavigate(ClientPaths.awaitingApproval, arguments: arguments);
}
/// Navigates to the Invoice Ready page.
void toInvoiceReady() {
pushNamed(ClientPaths.invoiceReady);
safePush(ClientPaths.invoiceReady);
}
/// Navigates to the Orders tab.
///
/// View and manage all shift orders with filtering and sorting.
void toClientOrders() {
navigate(ClientPaths.orders);
safeNavigate(ClientPaths.orders);
}
/// Navigates to the Reports tab.
///
/// Generate and view workforce reports and analytics.
void toClientReports() {
navigate(ClientPaths.reports);
safeNavigate(ClientPaths.reports);
}
// ==========================================================================
@@ -132,12 +133,12 @@ extension ClientNavigator on IModularNavigator {
///
/// Manage account settings, notifications, and app preferences.
void toClientSettings() {
pushNamed(ClientPaths.settings);
safePush(ClientPaths.settings);
}
/// Pushes the edit profile page.
void toClientEditProfile() {
pushNamed('${ClientPaths.settings}/edit-profile');
safePush('${ClientPaths.settings}/edit-profile');
}
// ==========================================================================
@@ -148,12 +149,12 @@ extension ClientNavigator on IModularNavigator {
///
/// View and manage physical locations/hubs where staff are deployed.
Future<void> toClientHubs() async {
await pushNamed(ClientPaths.hubs);
await safePush(ClientPaths.hubs);
}
/// Navigates to the details of a specific hub.
Future<bool?> toHubDetails(Hub hub) {
return pushNamed<bool?>(
return safePush<bool?>(
ClientPaths.hubDetails,
arguments: <String, dynamic>{'hub': hub},
);
@@ -161,7 +162,7 @@ extension ClientNavigator on IModularNavigator {
/// Navigates to the page to add a new hub or edit an existing one.
Future<bool?> toEditHub({Hub? hub}) async {
return pushNamed<bool?>(
return safePush<bool?>(
ClientPaths.editHub,
arguments: <String, dynamic>{'hub': hub},
// Some versions of Modular allow passing opaque here, but if not
@@ -178,35 +179,35 @@ extension ClientNavigator on IModularNavigator {
///
/// This is the starting point for all order creation flows.
void toCreateOrder({Object? arguments}) {
navigate(ClientPaths.createOrder, arguments: arguments);
safeNavigate(ClientPaths.createOrder, arguments: arguments);
}
/// Pushes the rapid order creation flow.
///
/// Quick shift creation with simplified inputs for urgent needs.
void toCreateOrderRapid({Object? arguments}) {
pushNamed(ClientPaths.createOrderRapid, arguments: arguments);
safePush(ClientPaths.createOrderRapid, arguments: arguments);
}
/// Pushes the one-time order creation flow.
///
/// Create a shift that occurs once at a specific date and time.
void toCreateOrderOneTime({Object? arguments}) {
pushNamed(ClientPaths.createOrderOneTime, arguments: arguments);
safePush(ClientPaths.createOrderOneTime, arguments: arguments);
}
/// Pushes the recurring order creation flow.
///
/// Create shifts that repeat on a defined schedule (daily, weekly, etc.).
void toCreateOrderRecurring({Object? arguments}) {
pushNamed(ClientPaths.createOrderRecurring, arguments: arguments);
safePush(ClientPaths.createOrderRecurring, arguments: arguments);
}
/// Pushes the permanent order creation flow.
///
/// Create a long-term or permanent staffing position.
void toCreateOrderPermanent({Object? arguments}) {
pushNamed(ClientPaths.createOrderPermanent, arguments: arguments);
safePush(ClientPaths.createOrderPermanent, arguments: arguments);
}
// ==========================================================================
@@ -215,7 +216,7 @@ extension ClientNavigator on IModularNavigator {
/// Navigates to the order details page to a specific date.
void toOrdersSpecificDate(DateTime date) {
navigate(
safeNavigate(
ClientPaths.orders,
arguments: <String, DateTime>{'initialDate': date},
);

View File

@@ -1,4 +1,6 @@
import 'package:flutter_modular/flutter_modular.dart';
import 'client/route_paths.dart';
import 'staff/route_paths.dart';
/// Base navigation utilities extension for [IModularNavigator].
///
@@ -21,10 +23,7 @@ extension NavigationExtensions on IModularNavigator {
/// * [arguments] - Optional arguments to pass to the route
///
/// Returns `true` if navigation was successful, `false` otherwise.
Future<bool> safeNavigate(
String path, {
Object? arguments,
}) async {
Future<bool> safeNavigate(String path, {Object? arguments}) async {
try {
navigate(path, arguments: arguments);
return true;
@@ -32,6 +31,7 @@ extension NavigationExtensions on IModularNavigator {
// In production, you might want to log this to a monitoring service
// ignore: avoid_print
print('Navigation error to $path: $e');
navigateToHome();
return false;
}
}
@@ -56,6 +56,7 @@ extension NavigationExtensions on IModularNavigator {
// In production, you might want to log this to a monitoring service
// ignore: avoid_print
print('Push navigation error to $routeName: $e');
navigateToHome();
return null;
}
}
@@ -68,14 +69,31 @@ extension NavigationExtensions on IModularNavigator {
navigate('/');
}
/// Pops the current route if possible.
/// Pops the current route if possible, otherwise navigates to home.
///
/// Returns `true` if a route was popped, `false` if already at root.
/// Returns `true` if a route was popped, `false` if it navigated to home.
bool popSafe() {
if (canPop()) {
pop();
return true;
}
navigateToHome();
return false;
}
/// Navigates to the designated home page based on the current context.
///
/// Checks the current path to determine if the user is in the Client
/// or Staff portion of the application and routes to their respective home.
void navigateToHome() {
final String currentPath = Modular.to.path;
if (currentPath.contains('/client')) {
navigate(ClientPaths.home);
} else if (currentPath.contains('/worker') ||
currentPath.contains('/staff')) {
navigate(StaffPaths.home);
} else {
navigate('/');
}
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_domain/krow_domain.dart';
import '../navigation_extensions.dart';
import 'route_paths.dart';
/// Typed navigation extension for the Staff application.
@@ -33,76 +34,36 @@ extension StaffNavigator on IModularNavigator {
/// This effectively logs out the user by navigating to root.
/// Used when signing out or session expires.
void toInitialPage() {
navigate(StaffPaths.root);
safeNavigate(StaffPaths.root);
}
/// Navigates to the get started page.
///
/// This is the landing page for unauthenticated users, offering login/signup options.
void toGetStartedPage() {
navigate(StaffPaths.getStarted);
safeNavigate(StaffPaths.getStarted);
}
/// Navigates to the phone verification page.
///
/// Used for both login and signup flows to verify phone numbers via OTP.
///
/// Parameters:
/// * [mode] - The authentication mode: 'login' or 'signup'
///
/// The mode is passed as an argument and used by the verification page
/// to determine the appropriate flow.
void toPhoneVerification(String mode) {
pushNamed(
safePush(
StaffPaths.phoneVerification,
arguments: <String, String>{'mode': mode},
);
}
/// Navigates to the profile setup page, replacing the current route.
///
/// This is typically called after successful phone verification for new
/// staff members. Uses pushReplacement to prevent going back to verification.
void toProfileSetup() {
pushNamed(StaffPaths.profileSetup);
safePush(StaffPaths.profileSetup);
}
// ==========================================================================
// MAIN NAVIGATION
// ==========================================================================
/// Navigates to the staff home dashboard.
///
/// This is the main landing page for authenticated staff members.
/// Displays shift cards, quick actions, and notifications.
void toStaffHome() {
pushNamedAndRemoveUntil(StaffPaths.home, (_) => false);
}
/// Navigates to the benefits overview page.
void toBenefits() {
pushNamed(StaffPaths.benefits);
safePush(StaffPaths.benefits);
}
/// Navigates to the staff main shell.
///
/// This is the container with bottom navigation. Navigates to home tab
/// by default. Usually you'd navigate to a specific tab instead.
void toStaffMain() {
pushNamedAndRemoveUntil('${StaffPaths.main}/home/', (_) => false);
}
// ==========================================================================
// MAIN NAVIGATION TABS
// ==========================================================================
/// Navigates to the Shifts tab.
///
/// Browse available shifts, accepted shifts, and shift history.
///
/// Parameters:
/// * [selectedDate] - Optional date to pre-select in the shifts view
/// * [initialTab] - Optional initial tab (via query parameter)
void toShifts({
DateTime? selectedDate,
String? initialTab,
@@ -118,94 +79,47 @@ extension StaffNavigator on IModularNavigator {
if (refreshAvailable == true) {
args['refreshAvailable'] = true;
}
navigate(StaffPaths.shifts, arguments: args.isEmpty ? null : args);
safeNavigate(StaffPaths.shifts, arguments: args.isEmpty ? null : args);
}
/// Navigates to the Payments tab.
///
/// View payment history, earnings breakdown, and tax information.
void toPayments() {
pushNamedAndRemoveUntil(StaffPaths.payments, (_) => false);
}
/// Navigates to the Clock In tab.
///
/// Access time tracking interface for active shifts.
void toClockIn() {
pushNamedAndRemoveUntil(StaffPaths.clockIn, (_) => false);
}
/// Navigates to the Profile tab.
///
/// Manage personal information, documents, and preferences.
void toProfile() {
navigate(StaffPaths.profile);
safeNavigate(StaffPaths.profile);
}
// ==========================================================================
// SHIFT MANAGEMENT
// ==========================================================================
/// Navigates to the shift details page for a specific shift.
///
/// Displays comprehensive information about a shift including location,
/// time, pay rate, and action buttons for accepting/declining/applying.
///
/// Parameters:
/// * [shift] - The shift entity to display details for
///
/// The shift object is passed as an argument and can be retrieved
/// in the details page.
void toShiftDetails(Shift shift) {
navigate(StaffPaths.shiftDetails(shift.id), arguments: shift);
safeNavigate(StaffPaths.shiftDetails(shift.id), arguments: shift);
}
// ==========================================================================
// ONBOARDING & PROFILE SECTIONS
// ==========================================================================
/// Pushes the personal information page.
///
/// Collect or edit basic personal information.
void toPersonalInfo() {
pushNamed(StaffPaths.onboardingPersonalInfo);
safePush(StaffPaths.onboardingPersonalInfo);
}
/// Pushes the preferred locations editing page.
///
/// Allows staff to search and manage their preferred US work locations.
void toPreferredLocations() {
pushNamed(StaffPaths.preferredLocations);
safePush(StaffPaths.preferredLocations);
}
/// Pushes the emergency contact page.
///
/// Manage emergency contact details for safety purposes.
void toEmergencyContact() {
pushNamed(StaffPaths.emergencyContact);
safePush(StaffPaths.emergencyContact);
}
/// Pushes the work experience page.
///
/// Record previous work experience and qualifications.
void toExperience() {
navigate(StaffPaths.experience);
safeNavigate(StaffPaths.experience);
}
/// Pushes the attire preferences page.
///
/// Record sizing and appearance information for uniform allocation.
void toAttire() {
navigate(StaffPaths.attire);
safeNavigate(StaffPaths.attire);
}
/// Pushes the attire capture page.
///
/// Parameters:
/// * [item] - The attire item to capture
/// * [initialPhotoUrl] - Optional initial photo URL
void toAttireCapture({required AttireItem item, String? initialPhotoUrl}) {
navigate(
safeNavigate(
StaffPaths.attireCapture,
arguments: <String, dynamic>{
'item': item,
@@ -214,24 +128,12 @@ extension StaffNavigator on IModularNavigator {
);
}
// ==========================================================================
// COMPLIANCE & DOCUMENTS
// ==========================================================================
/// Pushes the documents management page.
///
/// Upload and manage required documents like ID and work permits.
void toDocuments() {
navigate(StaffPaths.documents);
safeNavigate(StaffPaths.documents);
}
/// Pushes the document upload page.
///
/// Parameters:
/// * [document] - The document metadata to upload
/// * [initialUrl] - Optional initial document URL
void toDocumentUpload({required StaffDocument document, String? initialUrl}) {
navigate(
safeNavigate(
StaffPaths.documentUpload,
arguments: <String, dynamic>{
'document': document,
@@ -240,124 +142,59 @@ extension StaffNavigator on IModularNavigator {
);
}
/// Pushes the certificates management page.
///
/// Manage professional certificates (e.g., food handling, CPR).
void toCertificates() {
pushNamed(StaffPaths.certificates);
safePush(StaffPaths.certificates);
}
// ==========================================================================
// FINANCIAL INFORMATION
// ==========================================================================
/// Pushes the bank account information page.
///
/// Manage banking details for direct deposit payments.
void toBankAccount() {
pushNamed(StaffPaths.bankAccount);
safePush(StaffPaths.bankAccount);
}
/// Pushes the tax forms page.
///
/// Manage W-4, tax withholding, and related tax documents.
void toTaxForms() {
pushNamed(StaffPaths.taxForms);
safePush(StaffPaths.taxForms);
}
/// Pushes the time card page.
///
/// View detailed time entries and timesheets.
void toTimeCard() {
pushNamed(StaffPaths.timeCard);
safePush(StaffPaths.timeCard);
}
// ==========================================================================
// SCHEDULING & AVAILABILITY
// ==========================================================================
/// Pushes the availability management page.
///
/// Define when the staff member is available to work.
void toAvailability() {
pushNamed(StaffPaths.availability);
safePush(StaffPaths.availability);
}
// ==========================================================================
// ADDITIONAL FEATURES
// ==========================================================================
/// Pushes the KROW University page (placeholder).
///
/// Access training materials and educational courses.
void toKrowUniversity() {
pushNamed(StaffPaths.krowUniversity);
safePush(StaffPaths.krowUniversity);
}
/// Pushes the trainings page (placeholder).
///
/// View and complete required training modules.
void toTrainings() {
pushNamed(StaffPaths.trainings);
safePush(StaffPaths.trainings);
}
/// Pushes the leaderboard page (placeholder).
///
/// View performance rankings and achievements.
void toLeaderboard() {
pushNamed(StaffPaths.leaderboard);
safePush(StaffPaths.leaderboard);
}
/// Pushes the FAQs page.
///
/// Access frequently asked questions and help resources.
void toFaqs() {
pushNamed(StaffPaths.faqs);
safePush(StaffPaths.faqs);
}
// ==========================================================================
// PRIVACY & SECURITY
// ==========================================================================
/// Navigates to the privacy and security settings page.
///
/// Manage privacy preferences including:
/// * Location sharing settings
/// * View terms of service
/// * View privacy policy
void toPrivacySecurity() {
pushNamed(StaffPaths.privacySecurity);
safePush(StaffPaths.privacySecurity);
}
/// Navigates to the Terms of Service page.
///
/// Display the full terms of service document in a dedicated page view.
void toTermsOfService() {
pushNamed(StaffPaths.termsOfService);
safePush(StaffPaths.termsOfService);
}
/// Navigates to the Privacy Policy page.
///
/// Display the full privacy policy document in a dedicated page view.
void toPrivacyPolicy() {
pushNamed(StaffPaths.privacyPolicy);
safePush(StaffPaths.privacyPolicy);
}
// ==========================================================================
// MESSAGING & COMMUNICATION
// ==========================================================================
/// Pushes the messages page (placeholder).
///
/// Access internal messaging system.
void toMessages() {
pushNamed(StaffPaths.messages);
safePush(StaffPaths.messages);
}
/// Pushes the settings page (placeholder).
///
/// General app settings and preferences.
void toSettings() {
pushNamed(StaffPaths.settings);
safePush(StaffPaths.settings);
}
}