diff --git a/lib/app_state.dart b/lib/app_state.dart index c3fd170..17df856 100644 --- a/lib/app_state.dart +++ b/lib/app_state.dart @@ -73,7 +73,7 @@ class AppState extends ChangeNotifier { bool get isLoggedIn => _authState.isLoggedIn; String get username => _authState.username; - // Navigation state + // Navigation state - simplified AppNavigationMode get navigationMode => _navigationState.mode; LatLng? get provisionalPinLocation => _navigationState.provisionalPinLocation; String? get provisionalPinAddress => _navigationState.provisionalPinAddress; @@ -81,6 +81,8 @@ class AppState extends ChangeNotifier { bool get isInSearchMode => _navigationState.isInSearchMode; bool get isInRouteMode => _navigationState.isInRouteMode; bool get hasActiveRoute => _navigationState.hasActiveRoute; + bool get showSearchButton => _navigationState.showSearchButton; + bool get showRouteButton => _navigationState.showRouteButton; List? get routePath => _navigationState.routePath; // Route state @@ -90,6 +92,9 @@ class AppState extends ChangeNotifier { String? get routeEndAddress => _navigationState.routeEndAddress; double? get routeDistance => _navigationState.routeDistance; bool get settingRouteStart => _navigationState.settingRouteStart; + bool get isSettingSecondPoint => _navigationState.isSettingSecondPoint; + bool get isCalculating => _navigationState.isCalculating; + bool get showingOverview => _navigationState.showingOverview; // Navigation search state bool get isNavigationSearchLoading => _navigationState.isSearchLoading; @@ -277,13 +282,13 @@ class AppState extends ChangeNotifier { _searchState.clearResults(); } - // ---------- Navigation Methods ---------- + // ---------- Navigation Methods - Simplified ---------- void enterSearchMode(LatLng mapCenter) { _navigationState.enterSearchMode(mapCenter); } - void cancelSearchMode() { - _navigationState.cancelSearchMode(); + void cancelNavigation() { + _navigationState.cancel(); } void updateProvisionalPinLocation(LatLng newLocation) { @@ -294,30 +299,30 @@ class AppState extends ChangeNotifier { _navigationState.selectSearchResult(result); } - void startRouteSetup({required bool settingStart}) { - _navigationState.startRouteSetup(settingStart: settingStart); + void startRoutePlanning({required bool thisLocationIsStart}) { + _navigationState.startRoutePlanning(thisLocationIsStart: thisLocationIsStart); } - void selectRouteLocation() { - _navigationState.selectRouteLocation(); + void selectSecondRoutePoint() { + _navigationState.selectSecondRoutePoint(); } void startRoute() { _navigationState.startRoute(); } + void showRouteOverview() { + _navigationState.showRouteOverview(); + } + + void hideRouteOverview() { + _navigationState.hideRouteOverview(); + } + void cancelRoute() { _navigationState.cancelRoute(); } - void viewRouteOverview() { - _navigationState.viewRouteOverview(); - } - - void returnToActiveRoute() { - _navigationState.returnToActiveRoute(); - } - // Navigation search methods Future searchNavigation(String query) async { await _navigationState.search(query); diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index df2a8fe..1fefa29 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -189,12 +189,12 @@ class _HomeScreenState extends State with TickerProviderStateMixin { void _onNavigationButtonPressed() { final appState = context.read(); - debugPrint('[HomeScreen] Navigation button pressed - hasActiveRoute: ${appState.hasActiveRoute}, navigationMode: ${appState.navigationMode}'); + debugPrint('[HomeScreen] Navigation button pressed - showRouteButton: ${appState.showRouteButton}, navigationMode: ${appState.navigationMode}'); - if (appState.hasActiveRoute) { - // Route button - view route overview - debugPrint('[HomeScreen] Viewing route overview'); - appState.viewRouteOverview(); + if (appState.showRouteButton) { + // Route button - show route overview + debugPrint('[HomeScreen] Showing route overview'); + appState.showRouteOverview(); } else { // Search button - enter search mode debugPrint('[HomeScreen] Entering search mode'); @@ -299,11 +299,12 @@ class _HomeScreenState extends State with TickerProviderStateMixin { _editSheetShown = false; } - // Auto-open navigation sheet during search/route modes - if ((appState.isInSearchMode || appState.isInRouteMode) && !_navigationSheetShown) { + // Auto-open navigation sheet when needed - simplified logic + final shouldShowNavSheet = appState.isInSearchMode || appState.showingOverview; + if (shouldShowNavSheet && !_navigationSheetShown) { _navigationSheetShown = true; WidgetsBinding.instance.addPostFrameCallback((_) => _openNavigationSheet()); - } else if (!appState.isInSearchMode && !appState.isInRouteMode) { + } else if (!shouldShowNavSheet) { _navigationSheetShown = false; } @@ -323,6 +324,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { child: Scaffold( key: _scaffoldKey, appBar: AppBar( + automaticallyImplyLeading: false, // Disable automatic back button title: SvgPicture.asset( 'assets/deflock-logo.svg', height: 28, @@ -377,7 +379,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { right: 0, child: LocationSearchBar( onResultSelected: _onSearchResultSelected, - onCancel: () => appState.cancelSearchMode(), + onCancel: () => appState.cancelNavigation(), ), ), // Bottom button bar (restored to original) diff --git a/lib/state/navigation_state.dart b/lib/state/navigation_state.dart index efb63cf..7bc5339 100644 --- a/lib/state/navigation_state.dart +++ b/lib/state/navigation_state.dart @@ -4,31 +4,31 @@ import 'package:latlong2/latlong.dart'; import '../models/search_result.dart'; import '../services/search_service.dart'; -/// Navigation modes for routing and search functionality +/// Simplified navigation modes - brutalist approach enum AppNavigationMode { - normal, // Default state - normal map view - search, // Search box visible, provisional pin active - searchInput, // Keyboard open, UI elements hidden - routeSetup, // Placing second pin for routing - routeCalculating, // Computing route with loading indicator - routePreview, // Route ready, showing start/cancel options - routeActive, // Following an active route - routeOverview, // Viewing active route overview + normal, // Regular map view + search, // Search/routing UI active + routeActive, // Following a route } -/// Manages all navigation, search, and routing state +/// Simplified navigation state - fewer modes, clearer logic class NavigationState extends ChangeNotifier { final SearchService _searchService = SearchService(); + // Core state - just 3 modes AppNavigationMode _mode = AppNavigationMode.normal; + // Simple flags instead of complex sub-states + bool _isSettingSecondPoint = false; + bool _isCalculating = false; + bool _showingOverview = false; + // Search state bool _isSearchLoading = false; List _searchResults = []; String _lastQuery = ''; - List _searchHistory = []; - // Provisional pin state (for route planning) + // Location state LatLng? _provisionalPinLocation; String? _provisionalPinAddress; @@ -39,14 +39,17 @@ class NavigationState extends ChangeNotifier { String? _routeEndAddress; List? _routePath; double? _routeDistance; - bool _settingRouteStart = true; // true = setting start, false = setting end + bool _nextPointIsStart = false; // What we're setting next // Getters AppNavigationMode get mode => _mode; + bool get isSettingSecondPoint => _isSettingSecondPoint; + bool get isCalculating => _isCalculating; + bool get showingOverview => _showingOverview; + bool get isSearchLoading => _isSearchLoading; List get searchResults => List.unmodifiable(_searchResults); String get lastQuery => _lastQuery; - List get searchHistory => List.unmodifiable(_searchHistory); LatLng? get provisionalPinLocation => _provisionalPinLocation; String? get provisionalPinAddress => _provisionalPinAddress; @@ -57,26 +60,22 @@ class NavigationState extends ChangeNotifier { String? get routeEndAddress => _routeEndAddress; List? get routePath => _routePath != null ? List.unmodifiable(_routePath!) : null; double? get routeDistance => _routeDistance; - bool get settingRouteStart => _settingRouteStart; + bool get settingRouteStart => _nextPointIsStart; // For sheet display compatibility - // Convenience getters - bool get isInSearchMode => _mode == AppNavigationMode.search || _mode == AppNavigationMode.searchInput; - bool get isInRouteMode => _mode == AppNavigationMode.routeSetup || - _mode == AppNavigationMode.routeCalculating || - _mode == AppNavigationMode.routePreview || - _mode == AppNavigationMode.routeActive || - _mode == AppNavigationMode.routeOverview; - bool get hasActiveRoute => _routePath != null; - bool get showProvisionalPin => _provisionalPinLocation != null && - (_mode == AppNavigationMode.search || - _mode == AppNavigationMode.routeSetup); + // Simplified convenience getters + bool get isInSearchMode => _mode == AppNavigationMode.search; + bool get isInRouteMode => _mode == AppNavigationMode.routeActive; + bool get hasActiveRoute => _routePath != null && _mode == AppNavigationMode.routeActive; + bool get showProvisionalPin => _provisionalPinLocation != null && (_mode == AppNavigationMode.search); + bool get showSearchButton => _mode == AppNavigationMode.normal; + bool get showRouteButton => _mode == AppNavigationMode.routeActive; - /// Enter search mode with provisional pin at current map center + /// BRUTALIST: Single entry point to search mode void enterSearchMode(LatLng mapCenter) { - debugPrint('[NavigationState] enterSearchMode called - current mode: $_mode, mapCenter: $mapCenter'); + debugPrint('[NavigationState] enterSearchMode - current mode: $_mode'); if (_mode != AppNavigationMode.normal) { - debugPrint('[NavigationState] Cannot enter search mode - current mode is $_mode (not normal)'); + debugPrint('[NavigationState] Cannot enter search mode - not in normal mode'); return; } @@ -84,82 +83,70 @@ class NavigationState extends ChangeNotifier { _provisionalPinLocation = mapCenter; _provisionalPinAddress = null; _clearSearchResults(); - debugPrint('[NavigationState] Entered search mode at $mapCenter'); + + debugPrint('[NavigationState] Entered search mode'); notifyListeners(); } - /// Enter search input mode (keyboard open) - void enterSearchInputMode() { - if (_mode != AppNavigationMode.search) return; - - _mode = AppNavigationMode.searchInput; - debugPrint('[NavigationState] Entered search input mode'); - notifyListeners(); - } - - /// Exit search input mode back to search - void exitSearchInputMode() { - if (_mode != AppNavigationMode.searchInput) return; - - _mode = AppNavigationMode.search; - debugPrint('[NavigationState] Exited search input mode'); - notifyListeners(); - } - - /// Cancel search mode and return to normal - void cancelSearchMode() { - debugPrint('[NavigationState] cancelSearchMode called - mode: $_mode, isInSearch: $isInSearchMode, isInRoute: $isInRouteMode'); - - if (!isInSearchMode && _mode != AppNavigationMode.routeSetup) return; + /// BRUTALIST: Single cancellation method - cleans up EVERYTHING + void cancel() { + debugPrint('[NavigationState] cancel() - cleaning up all state'); _mode = AppNavigationMode.normal; + + // Clear ALL provisional data _provisionalPinLocation = null; _provisionalPinAddress = null; + + // Clear ALL route data (except active route) + if (_mode != AppNavigationMode.routeActive) { + _routeStart = null; + _routeEnd = null; + _routeStartAddress = null; + _routeEndAddress = null; + _routePath = null; + _routeDistance = null; + } + + // Reset ALL flags + _isSettingSecondPoint = false; + _isCalculating = false; + _showingOverview = false; + _nextPointIsStart = false; + + // Clear search _clearSearchResults(); - // Clear ALL route data when canceling - _routeStart = null; - _routeEnd = null; - _routeStartAddress = null; - _routeEndAddress = null; - _routePath = null; - _routeDistance = null; - _settingRouteStart = true; - - debugPrint('[NavigationState] Cancelled search mode - cleaned up all data'); + debugPrint('[NavigationState] Everything cleaned up'); notifyListeners(); } - /// Update provisional pin location (when map moves during search) + /// Update provisional pin when map moves void updateProvisionalPinLocation(LatLng newLocation) { if (!showProvisionalPin) return; _provisionalPinLocation = newLocation; - // Clear address since location changed - _provisionalPinAddress = null; + _provisionalPinAddress = null; // Clear address when location changes notifyListeners(); } - /// Jump to search result and update provisional pin + /// Jump to search result void selectSearchResult(SearchResult result) { - if (!isInSearchMode) return; + if (_mode != AppNavigationMode.search) return; _provisionalPinLocation = result.coordinates; _provisionalPinAddress = result.displayName; - _mode = AppNavigationMode.search; // Exit search input mode _clearSearchResults(); + debugPrint('[NavigationState] Selected search result: ${result.displayName}'); notifyListeners(); } - /// Start route setup (user clicked "route to" or "route from") - void startRouteSetup({required bool settingStart}) { - debugPrint('[NavigationState] startRouteSetup called - settingStart: $settingStart, mode: $_mode, location: $_provisionalPinLocation'); + /// Start route planning - simplified logic + void startRoutePlanning({required bool thisLocationIsStart}) { + if (_mode != AppNavigationMode.search || _provisionalPinLocation == null) return; - if (_mode != AppNavigationMode.search || _provisionalPinLocation == null) { - debugPrint('[NavigationState] startRouteSetup - early return'); - return; - } + debugPrint('[NavigationState] Starting route planning - thisLocationIsStart: $thisLocationIsStart'); // Clear any previous route data _routeStart = null; @@ -169,118 +156,104 @@ class NavigationState extends ChangeNotifier { _routePath = null; _routeDistance = null; - if (settingStart) { - // "Route From" - this location is the START, now we need to pick END + // Set the current location as start or end + if (thisLocationIsStart) { _routeStart = _provisionalPinLocation; _routeStartAddress = _provisionalPinAddress; - _settingRouteStart = false; // Next, we'll be setting the END - debugPrint('[NavigationState] Set route start: $_routeStart, next will set END'); + _nextPointIsStart = false; // Next we'll set the END + debugPrint('[NavigationState] Set route start, next setting END'); } else { - // "Route To" - this location is the END, now we need to pick START _routeEnd = _provisionalPinLocation; _routeEndAddress = _provisionalPinAddress; - _settingRouteStart = true; // Next, we'll be setting the START - debugPrint('[NavigationState] Set route end: $_routeEnd, next will set START'); + _nextPointIsStart = true; // Next we'll set the START + debugPrint('[NavigationState] Set route end, next setting START'); } - _mode = AppNavigationMode.routeSetup; - // Keep provisional pin active for second location - debugPrint('[NavigationState] Started route setup (setting ${settingStart ? 'start' : 'end'})'); + // Enter second point selection mode + _isSettingSecondPoint = true; notifyListeners(); } - /// Lock in second route location - void selectRouteLocation() { - debugPrint('[NavigationState] selectRouteLocation called - mode: $_mode, provisional: $_provisionalPinLocation'); + /// Select the second route point + void selectSecondRoutePoint() { + if (!_isSettingSecondPoint || _provisionalPinLocation == null) return; - if (_mode != AppNavigationMode.routeSetup || _provisionalPinLocation == null) { - debugPrint('[NavigationState] selectRouteLocation - early return (mode: $_mode, location: $_provisionalPinLocation)'); - return; - } + debugPrint('[NavigationState] Selecting second route point - nextPointIsStart: $_nextPointIsStart'); - if (_settingRouteStart) { + // Set the second point + if (_nextPointIsStart) { _routeStart = _provisionalPinLocation; _routeStartAddress = _provisionalPinAddress; - debugPrint('[NavigationState] Set route start: $_routeStart'); } else { _routeEnd = _provisionalPinLocation; _routeEndAddress = _provisionalPinAddress; - debugPrint('[NavigationState] Set route end: $_routeEnd'); } - debugPrint('[NavigationState] Route points - start: $_routeStart, end: $_routeEnd'); - - // Start route calculation + _isSettingSecondPoint = false; _calculateRoute(); } - /// Calculate route (mock implementation for now) + /// Calculate route void _calculateRoute() { if (_routeStart == null || _routeEnd == null) return; - _mode = AppNavigationMode.routeCalculating; + debugPrint('[NavigationState] Calculating route...'); + _isCalculating = true; notifyListeners(); - // Mock route calculation with delay - Future.delayed(const Duration(seconds: 2), () { - if (_mode != AppNavigationMode.routeCalculating) return; + // Mock route calculation + Future.delayed(const Duration(seconds: 1), () { + if (!_isCalculating) return; // Canceled - // Create simple straight line route for now _routePath = [_routeStart!, _routeEnd!]; _routeDistance = const Distance().as(LengthUnit.Meter, _routeStart!, _routeEnd!); - - _mode = AppNavigationMode.routePreview; + _isCalculating = false; + _showingOverview = true; _provisionalPinLocation = null; // Hide provisional pin - debugPrint('[NavigationState] Route calculated: ${_routeDistance! / 1000.0} km'); + + debugPrint('[NavigationState] Route calculated: ${(_routeDistance! / 1000).toStringAsFixed(1)} km'); notifyListeners(); }); } /// Start following the route void startRoute() { - if (_mode != AppNavigationMode.routePreview || _routePath == null) return; + if (_routePath == null) return; _mode = AppNavigationMode.routeActive; - debugPrint('[NavigationState] Started route following'); - notifyListeners(); - } - - /// View route overview (from route button during active route) - void viewRouteOverview() { - if (_mode != AppNavigationMode.routeActive || _routePath == null) return; + _showingOverview = false; - _mode = AppNavigationMode.routeOverview; - debugPrint('[NavigationState] Viewing route overview'); + debugPrint('[NavigationState] Started following route'); notifyListeners(); } - /// Return to active route from overview - void returnToActiveRoute() { - if (_mode != AppNavigationMode.routeOverview) return; + /// Show route overview (from route button during active navigation) + void showRouteOverview() { + if (_mode != AppNavigationMode.routeActive) return; - _mode = AppNavigationMode.routeActive; - debugPrint('[NavigationState] Returned to active route'); + _showingOverview = true; + debugPrint('[NavigationState] Showing route overview'); notifyListeners(); } - /// Cancel route and return to normal mode + /// Hide route overview (back to active navigation) + void hideRouteOverview() { + if (_mode != AppNavigationMode.routeActive) return; + + _showingOverview = false; + debugPrint('[NavigationState] Hiding route overview'); + notifyListeners(); + } + + /// Cancel active route and return to normal void cancelRoute() { - if (!isInRouteMode) return; + if (_mode != AppNavigationMode.routeActive) return; - _mode = AppNavigationMode.normal; - _routeStart = null; - _routeEnd = null; - _routeStartAddress = null; - _routeEndAddress = null; - _routePath = null; - _routeDistance = null; - _provisionalPinLocation = null; - _provisionalPinAddress = null; - debugPrint('[NavigationState] Cancelled route'); - notifyListeners(); + debugPrint('[NavigationState] Canceling active route'); + cancel(); // Use the brutalist single cleanup method } - /// Search functionality (delegates to existing search service) + /// Search functionality Future search(String query) async { if (query.trim().isEmpty) { _clearSearchResults(); @@ -295,7 +268,7 @@ class NavigationState extends ChangeNotifier { try { final results = await _searchService.search(query.trim()); _searchResults = results; - debugPrint('[NavigationState] Found ${results.length} results for "$query"'); + debugPrint('[NavigationState] Found ${results.length} results'); } catch (e) { debugPrint('[NavigationState] Search failed: $e'); _searchResults = []; @@ -304,7 +277,6 @@ class NavigationState extends ChangeNotifier { _setSearchLoading(false); } - /// Clear search results void clearSearchResults() { _clearSearchResults(); } diff --git a/lib/widgets/map/map_overlays.dart b/lib/widgets/map/map_overlays.dart index 49f0d75..b945ce5 100644 --- a/lib/widgets/map/map_overlays.dart +++ b/lib/widgets/map/map_overlays.dart @@ -120,16 +120,16 @@ class MapOverlays extends StatelessWidget { builder: (context, appState, child) { return Column( children: [ - // Search/Route button (top of controls) - hide when in search/route modes - if (onSearchPressed != null && !appState.isInSearchMode && !appState.isInRouteMode) + // Navigation button - simplified logic + if (onSearchPressed != null && (appState.showSearchButton || appState.showRouteButton)) FloatingActionButton( mini: true, heroTag: "search_nav", onPressed: onSearchPressed, - tooltip: appState.hasActiveRoute ? 'Route Overview' : 'Search Location', - child: Icon(appState.hasActiveRoute ? Icons.route : Icons.search), + tooltip: appState.showRouteButton ? 'Route Overview' : 'Search Location', + child: Icon(appState.showRouteButton ? Icons.route : Icons.search), ), - if (onSearchPressed != null && !appState.isInSearchMode && !appState.isInRouteMode) + if (onSearchPressed != null && (appState.showSearchButton || appState.showRouteButton)) const SizedBox(height: 8), // Layer selector button diff --git a/lib/widgets/map_view.dart b/lib/widgets/map_view.dart index ef7f573..965e5e0 100644 --- a/lib/widgets/map_view.dart +++ b/lib/widgets/map_view.dart @@ -382,16 +382,41 @@ class MapViewState extends State { centerMarkers.add( Marker( point: appState.provisionalPinLocation!, - width: 48.0, - height: 48.0, + width: 32.0, + height: 32.0, child: const ProvisionalPin(), ), ); } + // Build start/end pins for route visualization + if (appState.showingOverview || appState.isInRouteMode) { + if (appState.routeStart != null) { + centerMarkers.add( + Marker( + point: appState.routeStart!, + width: 32.0, + height: 32.0, + child: const LocationPin(type: PinType.start), + ), + ); + } + if (appState.routeEnd != null) { + centerMarkers.add( + Marker( + point: appState.routeEnd!, + width: 32.0, + height: 32.0, + child: const LocationPin(type: PinType.end), + ), + ); + } + } + // Build route path visualization final routeLines = []; - if (appState.routePath != null && appState.routePath!.length > 1) { + if (appState.routePath != null && appState.routePath!.length > 1 && + (appState.showingOverview || appState.isInRouteMode)) { routeLines.add(Polyline( points: appState.routePath!, color: Colors.blue, diff --git a/lib/widgets/navigation_sheet.dart b/lib/widgets/navigation_sheet.dart index 6dddb81..eff872e 100644 --- a/lib/widgets/navigation_sheet.dart +++ b/lib/widgets/navigation_sheet.dart @@ -50,6 +50,20 @@ class NavigationSheet extends StatelessWidget { ); } + Widget _buildDragHandle() { + return Center( + child: Container( + width: 40, + height: 4, + margin: const EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: Colors.grey[400], + borderRadius: BorderRadius.circular(2), + ), + ), + ); + } + @override Widget build(BuildContext context) { return Consumer( @@ -58,258 +72,176 @@ class NavigationSheet extends StatelessWidget { final provisionalLocation = appState.provisionalPinLocation; final provisionalAddress = appState.provisionalPinAddress; - if (provisionalLocation == null) { + if (provisionalLocation == null && !appState.showingOverview) { return const SizedBox.shrink(); } - switch (navigationMode) { - case AppNavigationMode.search: - return Container( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // Drag handle - Center( - child: Container( - width: 40, - height: 4, - margin: const EdgeInsets.only(bottom: 16), - decoration: BoxDecoration( - color: Colors.grey[400], - borderRadius: BorderRadius.circular(2), + return Container( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildDragHandle(), + + // SEARCH MODE: Initial location with route options + if (navigationMode == AppNavigationMode.search && !appState.isSettingSecondPoint && !appState.isCalculating && !appState.showingOverview && provisionalLocation != null) ...[ + _buildLocationInfo( + label: 'Location', + coordinates: provisionalLocation, + address: provisionalAddress, + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: ElevatedButton.icon( + icon: const Icon(Icons.directions), + label: const Text('Route To'), + onPressed: () { + appState.startRoutePlanning(thisLocationIsStart: false); + }, ), ), - ), - - // Location info + const SizedBox(width: 12), + Expanded( + child: ElevatedButton.icon( + icon: const Icon(Icons.my_location), + label: const Text('Route From'), + onPressed: () { + appState.startRoutePlanning(thisLocationIsStart: true); + }, + ), + ), + ], + ), + ], + + // SETTING SECOND POINT: Show both points and select button + if (appState.isSettingSecondPoint && provisionalLocation != null) ...[ + // Show existing route points + if (appState.routeStart != null) ...[ _buildLocationInfo( - label: 'Location', - coordinates: provisionalLocation, - address: provisionalAddress, + label: 'Start', + coordinates: appState.routeStart!, + address: appState.routeStartAddress, + ), + const SizedBox(height: 12), + ], + if (appState.routeEnd != null) ...[ + _buildLocationInfo( + label: 'End', + coordinates: appState.routeEnd!, + address: appState.routeEndAddress, + ), + const SizedBox(height: 12), + ], + + // Show the point we're selecting + _buildLocationInfo( + label: appState.settingRouteStart ? 'Start (select)' : 'End (select)', + coordinates: provisionalLocation, + address: provisionalAddress, + ), + const SizedBox(height: 16), + + ElevatedButton.icon( + icon: const Icon(Icons.check), + label: const Text('Select Location'), + onPressed: () { + debugPrint('[NavigationSheet] Select Location button pressed'); + appState.selectSecondRoutePoint(); + }, + ), + ], + + // CALCULATING: Show loading + if (appState.isCalculating) ...[ + const Center( + child: SizedBox( + width: 40, + height: 40, + child: CircularProgressIndicator(), + ), + ), + const SizedBox(height: 16), + const Text('Calculating route...', textAlign: TextAlign.center), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () => appState.cancelNavigation(), + child: const Text('Cancel'), + ), + ], + + // ROUTE OVERVIEW: Show route details with start/cancel options + if (appState.showingOverview) ...[ + if (appState.routeStart != null) ...[ + _buildLocationInfo( + label: 'Start', + coordinates: appState.routeStart!, + address: appState.routeStartAddress, + ), + const SizedBox(height: 12), + ], + if (appState.routeEnd != null) ...[ + _buildLocationInfo( + label: 'End', + coordinates: appState.routeEnd!, + address: appState.routeEndAddress, + ), + const SizedBox(height: 12), + ], + if (appState.routeDistance != null) ...[ + Text( + 'Distance: ${(appState.routeDistance! / 1000).toStringAsFixed(1)} km', + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), - const SizedBox(height: 16), - - // Action buttons - Row( - children: [ + ], + + Row( + children: [ + if (navigationMode == AppNavigationMode.search) ...[ + // Route preview mode - start or cancel Expanded( child: ElevatedButton.icon( - icon: const Icon(Icons.directions), - label: const Text('Route To'), - onPressed: () { - appState.startRouteSetup(settingStart: false); - }, + icon: const Icon(Icons.play_arrow), + label: const Text('Start'), + onPressed: () => appState.startRoute(), ), ), const SizedBox(width: 12), Expanded( child: ElevatedButton.icon( - icon: const Icon(Icons.my_location), - label: const Text('Route From'), - onPressed: () { - appState.startRouteSetup(settingStart: true); - }, + icon: const Icon(Icons.close), + label: const Text('Cancel'), + onPressed: () => appState.cancelNavigation(), ), ), - ], - ), - ], - ), - ); - - case AppNavigationMode.routeSetup: - return Container( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // Drag handle - Center( - child: Container( - width: 40, - height: 4, - margin: const EdgeInsets.only(bottom: 16), - decoration: BoxDecoration( - color: Colors.grey[400], - borderRadius: BorderRadius.circular(2), - ), - ), - ), - - // Route points info - if (appState.routeStart != null) ...[ - _buildLocationInfo( - label: 'Start', - coordinates: appState.routeStart!, - address: appState.routeStartAddress, - ), - const SizedBox(height: 12), - ], - - if (appState.routeEnd != null) ...[ - _buildLocationInfo( - label: 'End', - coordinates: appState.routeEnd!, - address: appState.routeEndAddress, - ), - const SizedBox(height: 12), - ], - - _buildLocationInfo( - label: appState.settingRouteStart ? 'Start (select)' : 'End (select)', - coordinates: provisionalLocation, - address: provisionalAddress, - ), - - const SizedBox(height: 16), - - // Select location button - ElevatedButton.icon( - icon: const Icon(Icons.check), - label: const Text('Select Location'), - onPressed: () { - debugPrint('[NavigationSheet] Select Location button pressed'); - appState.selectRouteLocation(); - }, - ), - ], - ), - ); - - case AppNavigationMode.routeCalculating: - return Container( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Drag handle - Center( - child: Container( - width: 40, - height: 4, - margin: const EdgeInsets.only(bottom: 16), - decoration: BoxDecoration( - color: Colors.grey[400], - borderRadius: BorderRadius.circular(2), - ), - ), - ), - - const CircularProgressIndicator(), - const SizedBox(height: 16), - const Text('Calculating route...'), - const SizedBox(height: 16), - - ElevatedButton( - onPressed: () { - appState.cancelRoute(); - }, - child: const Text('Cancel'), - ), - ], - ), - ); - - case AppNavigationMode.routePreview: - case AppNavigationMode.routeOverview: - return Container( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // Drag handle - Center( - child: Container( - width: 40, - height: 4, - margin: const EdgeInsets.only(bottom: 16), - decoration: BoxDecoration( - color: Colors.grey[400], - borderRadius: BorderRadius.circular(2), - ), - ), - ), - - // Route info - if (appState.routeStart != null) ...[ - _buildLocationInfo( - label: 'Start', - coordinates: appState.routeStart!, - address: appState.routeStartAddress, - ), - const SizedBox(height: 12), - ], - - if (appState.routeEnd != null) ...[ - _buildLocationInfo( - label: 'End', - coordinates: appState.routeEnd!, - address: appState.routeEndAddress, - ), - const SizedBox(height: 12), - ], - - // Distance info - if (appState.routeDistance != null) ...[ - Text( - 'Distance: ${(appState.routeDistance! / 1000).toStringAsFixed(1)} km', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 16), - ], - - // Action buttons - Row( - children: [ - if (navigationMode == AppNavigationMode.routePreview) ...[ - Expanded( - child: ElevatedButton.icon( - icon: const Icon(Icons.play_arrow), - label: const Text('Start'), - onPressed: () { - appState.startRoute(); - }, - ), + ] else if (navigationMode == AppNavigationMode.routeActive) ...[ + // Active route overview - resume or cancel + Expanded( + child: ElevatedButton.icon( + icon: const Icon(Icons.play_arrow), + label: const Text('Resume'), + onPressed: () => appState.hideRouteOverview(), ), - const SizedBox(width: 12), - ] else if (navigationMode == AppNavigationMode.routeOverview) ...[ - Expanded( - child: ElevatedButton.icon( - icon: const Icon(Icons.play_arrow), - label: const Text('Resume'), - onPressed: () { - appState.returnToActiveRoute(); - }, - ), - ), - const SizedBox(width: 12), - ], + ), + const SizedBox(width: 12), Expanded( child: ElevatedButton.icon( icon: const Icon(Icons.close), - label: const Text('Cancel'), - onPressed: () { - appState.cancelRoute(); - }, + label: const Text('End Route'), + onPressed: () => appState.cancelRoute(), ), ), ], - ), - ], - ), - ); - - default: - return const SizedBox.shrink(); - } + ], + ), + ], + ], + ), + ); }, ); } diff --git a/lib/widgets/provisional_pin.dart b/lib/widgets/provisional_pin.dart index 5e08d89..2757dc1 100644 --- a/lib/widgets/provisional_pin.dart +++ b/lib/widgets/provisional_pin.dart @@ -1,19 +1,36 @@ import 'package:flutter/material.dart'; -/// A pin icon for marking provisional locations during search/routing -class ProvisionalPin extends StatelessWidget { +enum PinType { + provisional, // Orange - current selection + start, // Green - route start + end, // Red - route end +} + +/// A thumbtack-style pin for marking locations during search/routing +class LocationPin extends StatelessWidget { + final PinType type; final double size; - final Color color; - const ProvisionalPin({ + const LocationPin({ super.key, - this.size = 48.0, - this.color = Colors.red, + required this.type, + this.size = 32.0, // Smaller than before }); - + + Color get _pinColor { + switch (type) { + case PinType.provisional: + return Colors.orange; + case PinType.start: + return Colors.green; + case PinType.end: + return Colors.red; + } + } + @override Widget build(BuildContext context) { - return Container( + return SizedBox( width: size, height: size, child: Stack( @@ -21,34 +38,34 @@ class ProvisionalPin extends StatelessWidget { children: [ // Pin shadow Positioned( - bottom: 0, + bottom: 2, child: Container( - width: size * 0.3, - height: size * 0.15, + width: size * 0.4, + height: size * 0.2, decoration: BoxDecoration( color: Colors.black.withOpacity(0.3), - borderRadius: BorderRadius.circular(size * 0.15), + borderRadius: BorderRadius.circular(size * 0.1), ), ), ), - // Main pin + // Main thumbtack pin Icon( - Icons.location_pin, + Icons.push_pin, size: size, - color: color, + color: _pinColor, ), - // Inner dot + // Inner dot for better visibility Positioned( - top: size * 0.15, + top: size * 0.2, child: Container( - width: size * 0.25, - height: size * 0.25, + width: size * 0.3, + height: size * 0.3, decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, border: Border.all( - color: color.withOpacity(0.8), - width: 2, + color: _pinColor.withOpacity(0.8), + width: 1.5, ), ), ), @@ -57,4 +74,21 @@ class ProvisionalPin extends StatelessWidget { ), ); } +} + +// Legacy widget name for compatibility +class ProvisionalPin extends StatelessWidget { + final double size; + final Color color; + + const ProvisionalPin({ + super.key, + this.size = 32.0, + this.color = Colors.orange, + }); + + @override + Widget build(BuildContext context) { + return LocationPin(type: PinType.provisional, size: size); + } } \ No newline at end of file