mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-05-18 14:45:07 +02:00
Better pins, bugfixes
This commit is contained in:
+21
-16
@@ -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<LatLng>? 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<void> searchNavigation(String query) async {
|
||||
await _navigationState.search(query);
|
||||
|
||||
@@ -189,12 +189,12 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
void _onNavigationButtonPressed() {
|
||||
final appState = context.read<AppState>();
|
||||
|
||||
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<HomeScreen> 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<HomeScreen> 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<HomeScreen> with TickerProviderStateMixin {
|
||||
right: 0,
|
||||
child: LocationSearchBar(
|
||||
onResultSelected: _onSearchResultSelected,
|
||||
onCancel: () => appState.cancelSearchMode(),
|
||||
onCancel: () => appState.cancelNavigation(),
|
||||
),
|
||||
),
|
||||
// Bottom button bar (restored to original)
|
||||
|
||||
+111
-139
@@ -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<SearchResult> _searchResults = [];
|
||||
String _lastQuery = '';
|
||||
List<String> _searchHistory = [];
|
||||
|
||||
// Provisional pin state (for route planning)
|
||||
// Location state
|
||||
LatLng? _provisionalPinLocation;
|
||||
String? _provisionalPinAddress;
|
||||
|
||||
@@ -39,14 +39,17 @@ class NavigationState extends ChangeNotifier {
|
||||
String? _routeEndAddress;
|
||||
List<LatLng>? _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<SearchResult> get searchResults => List.unmodifiable(_searchResults);
|
||||
String get lastQuery => _lastQuery;
|
||||
List<String> 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<LatLng>? 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<void> 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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -382,16 +382,41 @@ class MapViewState extends State<MapView> {
|
||||
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 = <Polyline>[];
|
||||
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,
|
||||
|
||||
+160
-228
@@ -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<AppState>(
|
||||
@@ -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();
|
||||
}
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user