directions_instruction.dart 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import 'dart:async';
  2. import 'dart:math';
  3. import 'package:flutter/material.dart';
  4. import 'package:latlong2/latlong.dart';
  5. import '../models/directions.dart';
  6. import '../utils/geometry_utils.dart';
  7. /// Maximum distance the user can be from the line before recalculating the directions
  8. const maxDistanceFromLine = 100;
  9. class DirectionsInstruction extends StatefulWidget {
  10. final Directions directions;
  11. final Stream<LatLng> currentPositionStream;
  12. final StreamController<int> currentWaypointIndexController;
  13. final bool Function(LatLng) recalculateDirections;
  14. const DirectionsInstruction({
  15. required this.directions,
  16. required this.currentPositionStream,
  17. required this.currentWaypointIndexController,
  18. required this.recalculateDirections,
  19. Key? key,
  20. }) : super(key: key);
  21. @override
  22. State<DirectionsInstruction> createState() => _DirectionsInstructionState();
  23. }
  24. class _DirectionsInstructionState extends State<DirectionsInstruction> {
  25. /// The current step the user is in the directions
  26. int _currentSegmentIndex = 0;
  27. /// Next waypoint the user has to pass
  28. int _nextWaypointIndex = 0;
  29. late StreamSubscription<LatLng> _currentPositionSubscription;
  30. /// Line the user is currently following, defined by the previous and next waypoints
  31. Segment get _currentLine {
  32. final startLine = widget.directions.waypointsCoordinates[max(0, _nextWaypointIndex - 1)];
  33. final endLine = widget.directions.waypointsCoordinates[_nextWaypointIndex];
  34. return Segment(start: startLine, end: endLine);
  35. }
  36. @override
  37. initState() {
  38. super.initState();
  39. _currentPositionSubscription = widget.currentPositionStream.listen(_handleCurrentPositionUpdates);
  40. }
  41. @override
  42. void dispose() {
  43. _currentPositionSubscription.cancel();
  44. super.dispose();
  45. }
  46. void _handleCurrentPositionUpdates(currentPosition) {
  47. _updateDirectionsFromCurrentPosition(currentPosition);
  48. _updateWaypointFromCurrentPosition(currentPosition);
  49. }
  50. void _updateWaypointFromCurrentPosition(LatLng currentPosition) {
  51. final nextWaypointIndex = _getNextWaypointIndex(currentPosition);
  52. widget.currentWaypointIndexController.add(nextWaypointIndex);
  53. setState(() {
  54. _nextWaypointIndex = nextWaypointIndex;
  55. _currentSegmentIndex = _getNewSegmentIndex(nextWaypointIndex);
  56. });
  57. }
  58. /// Get new directions if the user is too far from the current segment
  59. void _updateDirectionsFromCurrentPosition(LatLng currentPosition) {
  60. var distanceFromCurrentSegment = DistanceUtils.distToSegment(currentPosition, _currentLine);
  61. if (distanceFromCurrentSegment > maxDistanceFromLine) {
  62. // If we could recalculate the directions, we reset the segment and waypoint to zero
  63. if (widget.recalculateDirections(currentPosition)) {
  64. setState(() {
  65. _currentSegmentIndex = 0;
  66. _nextWaypointIndex = 0;
  67. });
  68. }
  69. }
  70. }
  71. num _getDistanceFromWaypoint(LatLng currentPosition, int waypointIndex) {
  72. final currentWaypoint = widget.directions.waypointsCoordinates[waypointIndex];
  73. return DistanceUtils.distBetweenTwoPoints(currentPosition, currentWaypoint);
  74. }
  75. int _getNewSegmentIndex(int nextWaypointIndex) {
  76. final currentSegment = widget.directions.segments[_currentSegmentIndex];
  77. var newSegmentIndex = _currentSegmentIndex;
  78. if (nextWaypointIndex > currentSegment.waypoints[1]) {
  79. newSegmentIndex++;
  80. } else if (nextWaypointIndex <= currentSegment.waypoints[0]) {
  81. newSegmentIndex--;
  82. }
  83. return max(0, min(newSegmentIndex, widget.directions.segments.length - 1));
  84. }
  85. int _getNextWaypointIndex(LatLng currentPosition) {
  86. final parameterProjection = DistanceUtils.getParameterFromProjection(currentPosition, _currentLine);
  87. final distanceFromNextWaypoint = _getDistanceFromWaypoint(currentPosition, _nextWaypointIndex);
  88. final distanceFromNextNextWaypoint = _nextWaypointIndex < widget.directions.waypointsCoordinates.length - 1
  89. ? _getDistanceFromWaypoint(currentPosition, _nextWaypointIndex + 1)
  90. : double.infinity;
  91. final isProjectedPointOnCurrentSegment = parameterProjection > 0 && parameterProjection < 1;
  92. // Approaching the next waypoint while following the line
  93. // We want to detect it before passing the waypoint
  94. if (distanceFromNextWaypoint < 3 && isProjectedPointOnCurrentSegment) {
  95. return _nextWaypointIndex + 1;
  96. }
  97. // Went back past previous waypoint
  98. if (parameterProjection < 0) {
  99. return max(0, _nextWaypointIndex - 1);
  100. }
  101. // Not closely following the line, but still went past next waypoint
  102. if (distanceFromNextWaypoint > distanceFromNextNextWaypoint || parameterProjection > 1) {
  103. return _nextWaypointIndex + 1;
  104. }
  105. return _nextWaypointIndex;
  106. }
  107. @override
  108. Widget build(BuildContext context) {
  109. final segments = widget.directions.segments;
  110. return Column(
  111. children: [
  112. SizedBox(height: MediaQuery.of(context).size.height * 0.05),
  113. Text(
  114. "${segments[_currentSegmentIndex + 1].instruction} in ${segments[_currentSegmentIndex].distance.round()}m",
  115. style: const TextStyle(fontSize: 25),
  116. ),
  117. if (_currentSegmentIndex < segments.length - 2)
  118. Text(
  119. "Next: ${segments[_currentSegmentIndex + 2].instruction}",
  120. style: const TextStyle(fontSize: 20),
  121. ),
  122. ],
  123. );
  124. }
  125. }