| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- import 'dart:async';
- import 'dart:math';
- import 'package:flutter/material.dart';
- import 'package:latlong2/latlong.dart';
- import '../models/directions.dart';
- import '../utils/geometry_utils.dart';
- /// Maximum distance the user can be from the line before recalculating the directions
- const maxDistanceFromLine = 100;
- class DirectionsInstruction extends StatefulWidget {
- final Directions directions;
- final Stream<LatLng> currentPositionStream;
- final StreamController<int> currentWaypointIndexController;
- final bool Function(LatLng) recalculateDirections;
- const DirectionsInstruction({
- required this.directions,
- required this.currentPositionStream,
- required this.currentWaypointIndexController,
- required this.recalculateDirections,
- Key? key,
- }) : super(key: key);
- @override
- State<DirectionsInstruction> createState() => _DirectionsInstructionState();
- }
- class _DirectionsInstructionState extends State<DirectionsInstruction> {
- /// The current step the user is in the directions
- int _currentSegmentIndex = 0;
- /// Next waypoint the user has to pass
- int _nextWaypointIndex = 0;
- late StreamSubscription<LatLng> _currentPositionSubscription;
- /// Line the user is currently following, defined by the previous and next waypoints
- Segment get _currentLine {
- final startLine = widget.directions.waypointsCoordinates[max(0, _nextWaypointIndex - 1)];
- final endLine = widget.directions.waypointsCoordinates[_nextWaypointIndex];
- return Segment(start: startLine, end: endLine);
- }
- @override
- initState() {
- super.initState();
- _currentPositionSubscription = widget.currentPositionStream.listen(_handleCurrentPositionUpdates);
- }
- @override
- void dispose() {
- _currentPositionSubscription.cancel();
- super.dispose();
- }
- void _handleCurrentPositionUpdates(currentPosition) {
- _updateDirectionsFromCurrentPosition(currentPosition);
- _updateWaypointFromCurrentPosition(currentPosition);
- }
- void _updateWaypointFromCurrentPosition(LatLng currentPosition) {
- final nextWaypointIndex = _getNextWaypointIndex(currentPosition);
- widget.currentWaypointIndexController.add(nextWaypointIndex);
- setState(() {
- _nextWaypointIndex = nextWaypointIndex;
- _currentSegmentIndex = _getNewSegmentIndex(nextWaypointIndex);
- });
- }
- /// Get new directions if the user is too far from the current segment
- void _updateDirectionsFromCurrentPosition(LatLng currentPosition) {
- var distanceFromCurrentSegment = DistanceUtils.distToSegment(currentPosition, _currentLine);
- if (distanceFromCurrentSegment > maxDistanceFromLine) {
- // If we could recalculate the directions, we reset the segment and waypoint to zero
- if (widget.recalculateDirections(currentPosition)) {
- setState(() {
- _currentSegmentIndex = 0;
- _nextWaypointIndex = 0;
- });
- }
- }
- }
- num _getDistanceFromWaypoint(LatLng currentPosition, int waypointIndex) {
- final currentWaypoint = widget.directions.waypointsCoordinates[waypointIndex];
- return DistanceUtils.distBetweenTwoPoints(currentPosition, currentWaypoint);
- }
- int _getNewSegmentIndex(int nextWaypointIndex) {
- final currentSegment = widget.directions.segments[_currentSegmentIndex];
- var newSegmentIndex = _currentSegmentIndex;
- if (nextWaypointIndex > currentSegment.waypoints[1]) {
- newSegmentIndex++;
- } else if (nextWaypointIndex <= currentSegment.waypoints[0]) {
- newSegmentIndex--;
- }
- return max(0, min(newSegmentIndex, widget.directions.segments.length - 1));
- }
- int _getNextWaypointIndex(LatLng currentPosition) {
- final parameterProjection = DistanceUtils.getParameterFromProjection(currentPosition, _currentLine);
- final distanceFromNextWaypoint = _getDistanceFromWaypoint(currentPosition, _nextWaypointIndex);
- final distanceFromNextNextWaypoint = _nextWaypointIndex < widget.directions.waypointsCoordinates.length - 1
- ? _getDistanceFromWaypoint(currentPosition, _nextWaypointIndex + 1)
- : double.infinity;
- final isProjectedPointOnCurrentSegment = parameterProjection > 0 && parameterProjection < 1;
- // Approaching the next waypoint while following the line
- // We want to detect it before passing the waypoint
- if (distanceFromNextWaypoint < 3 && isProjectedPointOnCurrentSegment) {
- return _nextWaypointIndex + 1;
- }
- // Went back past previous waypoint
- if (parameterProjection < 0) {
- return max(0, _nextWaypointIndex - 1);
- }
- // Not closely following the line, but still went past next waypoint
- if (distanceFromNextWaypoint > distanceFromNextNextWaypoint || parameterProjection > 1) {
- return _nextWaypointIndex + 1;
- }
- return _nextWaypointIndex;
- }
- @override
- Widget build(BuildContext context) {
- final segments = widget.directions.segments;
- return Column(
- children: [
- SizedBox(height: MediaQuery.of(context).size.height * 0.05),
- Text(
- "${segments[_currentSegmentIndex + 1].instruction} in ${segments[_currentSegmentIndex].distance.round()}m",
- style: const TextStyle(fontSize: 25),
- ),
- if (_currentSegmentIndex < segments.length - 2)
- Text(
- "Next: ${segments[_currentSegmentIndex + 2].instruction}",
- style: const TextStyle(fontSize: 20),
- ),
- ],
- );
- }
- }
|