Commit 11efdf92 authored by CLOUARD Regis's avatar CLOUARD Regis
Browse files

Merge branch 'Add_track_animation' into 'master'

Add track animation

See merge request !59
parents 182c6e24 a9b28621
...@@ -7,12 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ...@@ -7,12 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## 90 [3.1.0] - 2021-08-01 ## 90 [3.1.0] - 2021-087-23
### Fixed ### Fixed
- NFC for iOS - Disable insecure http url for the multimedia pages (touristic wall mode).
- Disable insecure http url for the multimedia pages (tourist mode).
### Added ### Added
......
...@@ -152,7 +152,7 @@ ...@@ -152,7 +152,7 @@
"developer_mode": "Developer mode", "developer_mode": "Developer mode",
"confirm_gps_title": "GPS Function", "confirm_gps_title": "GPS Function",
"confirm_gps_message": "Vikazimut needs location access even in background to perform. The GPS location is used to control the route, show the current track, and display statistics. No data is exported without permission.", "confirm_gps_message": "Vikazimut needs location access even in background to perform. The GPS location is used to control the route, show the current track, and display statistics. No data is exported without permission.",
"result_table_distance_column_tooltip": "The length of the track in meters\n(the distance between the two checkpoints in meters)\nthe overcost of length in percentage", "result_table_distance_column_tooltip": "The length of the track in meters\n(The distance between the two checkpoints in meters)\nThe overcost of length in percentage",
"result_table_pace_column_tooltip": "The pace expressed in min/km\n(the speed expressed in km/h)", "result_table_pace_column_tooltip": "The pace expressed in min/km\n(The speed expressed in km/h)",
"website_worldmap_page": "en/worldmap" "website_worldmap_page": "en/worldmap"
} }
...@@ -152,7 +152,7 @@ ...@@ -152,7 +152,7 @@
"developer_mode": "Mode développeur", "developer_mode": "Mode développeur",
"confirm_gps_title": "Fonction GPS", "confirm_gps_title": "Fonction GPS",
"confirm_gps_message": "Vikazimut nécessite la location de votre appareil y compris en arrière plan pour fonctionner. La position GPS est utilisée pour gérer le parcours, dessiner la trace et afficher des statistiques sur le parcours. Aucune donnée n'est exportée de l'appareil sans votre accord et la trace est intemporalisée.", "confirm_gps_message": "Vikazimut nécessite la location de votre appareil y compris en arrière plan pour fonctionner. La position GPS est utilisée pour gérer le parcours, dessiner la trace et afficher des statistiques sur le parcours. Aucune donnée n'est exportée de l'appareil sans votre accord et la trace est intemporalisée.",
"result_table_distance_column_tooltip": "La longueur du trajet effectué entre les deux postes en mètres\n(la distance à vol d'oiseau entre les deux postes en mètres)\nle surcoût de distance en pourcentage", "result_table_distance_column_tooltip": "La longueur du trajet effectué entre les deux postes en mètres\n(La distance à vol d'oiseau entre les deux postes en mètres)\nLe surcoût de distance en pourcentage",
"result_table_pace_column_tooltip": "La réduction kilométrique en min/km\n(la vitesse en km/h)", "result_table_pace_column_tooltip": "La réduction kilométrique en min/km\n(La vitesse en km/h)",
"website_worldmap_page": "worldmap" "website_worldmap_page": "worldmap"
} }
...@@ -11,8 +11,8 @@ import '../result.dart'; ...@@ -11,8 +11,8 @@ import '../result.dart';
class StatisticsResultView extends StatelessWidget { class StatisticsResultView extends StatelessWidget {
final Result result; final Result result;
late final String _title; late final String _title;
final GlobalKey tooltipColumn3Key = new GlobalKey(); final GlobalKey _tooltipColumn3Key = new GlobalKey();
final GlobalKey tooltipColumn4Key = new GlobalKey(); final GlobalKey _tooltipColumn4Key = new GlobalKey();
StatisticsResultView(this.result) { StatisticsResultView(this.result) {
_title = result.getMap()!.getName(); _title = result.getMap()!.getName();
...@@ -47,53 +47,59 @@ class StatisticsResultView extends StatelessWidget { ...@@ -47,53 +47,59 @@ class StatisticsResultView extends StatelessWidget {
List<DataColumn> _buildColumns() { List<DataColumn> _buildColumns() {
return [ return [
DataColumn(label: TextCentered(Translations.getString('control_point_index'))), DataColumn(label: Expanded(child: TextCentered(Translations.getString('control_point_index')))),
DataColumn(label: TextCentered(Translations.getString('interval_time'))), DataColumn(label: Expanded(child: TextCentered(Translations.getString('interval_time')))),
DataColumn( DataColumn(
label: Row( label: Expanded(
children: [ child: Row(
TextCentered(Translations.getString('interval_distance')), mainAxisAlignment: MainAxisAlignment.center,
Tooltip( children: [
key: tooltipColumn3Key, TextCentered(Translations.getString('interval_distance')),
message: Translations.getString("result_table_distance_column_tooltip"), Tooltip(
child: IconButton( key: _tooltipColumn3Key,
constraints: BoxConstraints(maxHeight: 18, maxWidth: 18), message: Translations.getString("result_table_distance_column_tooltip"),
padding: const EdgeInsets.all(0.0), child: IconButton(
icon: Icon( constraints: BoxConstraints(maxHeight: 18, maxWidth: 18),
Icons.help_outline, padding: const EdgeInsets.all(0.0),
color: Colors.white, icon: Icon(
size: 16.0, Icons.help_outline,
color: Colors.white,
size: 16.0,
),
onPressed: () {
final dynamic tooltip = _tooltipColumn3Key.currentState;
tooltip.ensureTooltipVisible();
},
), ),
onPressed: () {
final dynamic tooltip = tooltipColumn3Key.currentState;
tooltip.ensureTooltipVisible();
},
), ),
), ],
], ),
), ),
), ),
DataColumn( DataColumn(
label: Row( label: Expanded(
children: [ child: Row(
TextCentered(Translations.getString('interval_pace')), mainAxisAlignment: MainAxisAlignment.center,
Tooltip( children: [
key: tooltipColumn4Key, TextCentered(Translations.getString('interval_pace')),
message: Translations.getString("result_table_pace_column_tooltip"), Tooltip(
child: IconButton( key: _tooltipColumn4Key,
constraints: BoxConstraints(maxHeight: 18, maxWidth: 18), message: Translations.getString("result_table_pace_column_tooltip"),
padding: const EdgeInsets.all(0.0), child: IconButton(
icon: Icon(Icons.help_outline, color: Colors.white, size: 16.0), constraints: BoxConstraints(maxHeight: 18, maxWidth: 18),
onPressed: () { padding: const EdgeInsets.all(0.0),
final dynamic tooltip = tooltipColumn4Key.currentState; icon: Icon(Icons.help_outline, color: Colors.white, size: 16.0),
tooltip.ensureTooltipVisible(); onPressed: () {
}, final dynamic tooltip = _tooltipColumn4Key.currentState;
tooltip.ensureTooltipVisible();
},
),
), ),
), ],
], ),
), ),
), ),
DataColumn(label: TextCentered(Translations.getString('cumulative_time'))), DataColumn(label: Expanded(child: TextCentered(Translations.getString('cumulative_time')))),
]; ];
} }
...@@ -107,10 +113,10 @@ class StatisticsResultView extends StatelessWidget { ...@@ -107,10 +113,10 @@ class StatisticsResultView extends StatelessWidget {
} }
List<DataCell> cells = [ List<DataCell> cells = [
DataCell(Center(child: _buildControlNumberCell(timeTable, i))), DataCell(Center(child: _buildControlNumberCell(timeTable, i))),
DataCell(Center(child:_buildLegTimeCell(timeTable, i))), DataCell(Center(child: _buildLegTimeCell(timeTable, i))),
DataCell(Center(child:_buildLegDistanceCell(timeTable, i))), DataCell(Center(child: _buildLegDistanceCell(timeTable, i))),
DataCell(Center(child:_buildPaceCell(timeTable, i))), DataCell(Center(child: _buildPaceCell(timeTable, i))),
DataCell(Center(child:_buildCumulatedCell(timeTable, i))), DataCell(Center(child: _buildCumulatedCell(timeTable, i))),
]; ];
DataRow tableRow = DataRow(cells: cells); DataRow tableRow = DataRow(cells: cells);
rows.add(tableRow); rows.add(tableRow);
...@@ -120,11 +126,11 @@ class StatisticsResultView extends StatelessWidget { ...@@ -120,11 +126,11 @@ class StatisticsResultView extends StatelessWidget {
DataRow totalTableRow = DataRow( DataRow totalTableRow = DataRow(
color: MaterialStateProperty.all(primaryColorDisabled), color: MaterialStateProperty.all(primaryColorDisabled),
cells: [ cells: [
DataCell(Center(child:TextCenteredBold(Translations.getString('Total')))), DataCell(Center(child: TextCenteredBold(Translations.getString('Total')))),
DataCell(Center(child:TextCenteredBold('${result.getTotalTimeWithoutPenaltyAsString()}'))), DataCell(Center(child: TextCenteredBold('${result.getTotalTimeWithoutPenaltyAsString()}'))),
DataCell(Center(child:TextCenteredBold('${Translations.formatNumber(totalLength)}\n(${Translations.formatNumber(totalDistance)})\n+${Result.computeOvercost(totalLength, totalDistance)}%'))), DataCell(Center(child: TextCenteredBold('${Translations.formatNumber(totalLength)}\n(${Translations.formatNumber(totalDistance)})\n+${Result.computeOvercost(totalLength, totalDistance)}%'))),
DataCell(Center(child:TextCenteredBold('${formatPace(result.getGlobalPaceInMillis())}\n${formatSpeed(Result.calculateSpeedFromPace(result.getGlobalPaceInMillis()))}'))), DataCell(Center(child: TextCenteredBold('${formatPace(result.getGlobalPaceInMillis())}\n${formatSpeed(Result.calculateSpeedFromPace(result.getGlobalPaceInMillis()))}'))),
DataCell(Center(child:TextCenteredBold('${result.getTotalTimeWithoutPenaltyAsString()}'))), DataCell(Center(child: TextCenteredBold('${result.getTotalTimeWithoutPenaltyAsString()}'))),
], ],
); );
rows.add(totalTableRow); rows.add(totalTableRow);
......
This diff is collapsed.
import 'package:flutter/animation.dart'; import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';
import 'package:vikazimut/map/geodesic_point.dart'; import 'package:vikazimut/map/geodesic_point.dart';
import 'package:vikazimut/map/view/animated_route_layer.dart'; import 'package:vikazimut/map/view/animated_route_layer.dart';
import 'package:vikazimut/map/view/map_view.dart'; import 'package:vikazimut/map/view/map_view.dart';
...@@ -8,6 +9,7 @@ import 'abstract_track_view.dart'; ...@@ -8,6 +9,7 @@ import 'abstract_track_view.dart';
class TrackAnimation { class TrackAnimation {
AnimationController? _controller; AnimationController? _controller;
Function()? _action; Function()? _action;
int _currentLegIndex = 0;
void execute(AbstractRouteTraceViewState parent, MapView mapView, List<GeodesicPoint> track) async { void execute(AbstractRouteTraceViewState parent, MapView mapView, List<GeodesicPoint> track) async {
var animatedRouteLayer = AnimatedRouteLayer(mapView); var animatedRouteLayer = AnimatedRouteLayer(mapView);
...@@ -42,29 +44,36 @@ class TrackAnimation { ...@@ -42,29 +44,36 @@ class TrackAnimation {
_controller?.dispose(); _controller?.dispose();
} }
var _currentLegIndex = 0;
List<GeodesicPoint> getSubTrack(List<GeodesicPoint> track, int currentTime) { List<GeodesicPoint> getSubTrack(List<GeodesicPoint> track, int currentTime) {
// TODO Tests _currentLegIndex = findCurrentGPSPoint(track, currentTime, _currentLegIndex);
// TODO question garde t-on le temps réel ? return getSubList(track, currentTime, _currentLegIndex);
for (int i = _currentLegIndex; i < track.length; i++) { }
@visibleForTesting
static int findCurrentGPSPoint(List<GeodesicPoint> track, int currentTime, int currentLegIndex) {
for (int i = currentLegIndex; i < track.length; i++) {
if (track[i].getTimeInMillis() < currentTime) { if (track[i].getTimeInMillis() < currentTime) {
_currentLegIndex++; currentLegIndex++;
} else { } else {
break; break;
} }
} }
if (_currentLegIndex > 0) { return currentLegIndex;
var t2 = track[_currentLegIndex].getTimeInMillis(); }
var t1 = track[_currentLegIndex - 1].getTimeInMillis();
@visibleForTesting
static List<GeodesicPoint> getSubList(List<GeodesicPoint> track, int currentTime, int currentLegIndex) {
if (currentLegIndex > 0) {
var t1 = track[currentLegIndex - 1].getTimeInMillis();
var t2 = track[currentLegIndex].getTimeInMillis();
double x = (currentTime - t1) / (t2 - t1); double x = (currentTime - t1) / (t2 - t1);
var latitude = x * track[_currentLegIndex].getLatitude() + (1.0 - x) * track[_currentLegIndex - 1].getLatitude(); var latitude = x * track[currentLegIndex].getLatitude() + (1.0 - x) * track[currentLegIndex - 1].getLatitude();
var longitude = x * track[_currentLegIndex].getLongitude() + (1.0 - x) * track[_currentLegIndex - 1].getLongitude(); var longitude = x * track[currentLegIndex].getLongitude() + (1.0 - x) * track[currentLegIndex - 1].getLongitude();
var list = track.sublist(0, _currentLegIndex - 1); var subList = track.sublist(0, currentLegIndex);
list.add(new GeodesicPoint(latitude, longitude)); subList.add(new GeodesicPoint(latitude, longitude));
return list; return subList;
} else { } else {
return track.sublist(0, 0); return [];
} }
} }
......
...@@ -8,7 +8,7 @@ void main() { ...@@ -8,7 +8,7 @@ void main() {
const double COORDINATE_PRECISION = 1e-9; const double COORDINATE_PRECISION = 1e-9;
test('_readKmlFile_1', () async { test('_readKmlFile_1', () async {
File xmlFile = new File('test_resources/kml1.xml'); File xmlFile = new File('test/resources/kml1.xml');
LatLonBox bounds = OrienteeringMapKmlFileReader.readFile(xmlFile); LatLonBox bounds = OrienteeringMapKmlFileReader.readFile(xmlFile);
expect(bounds, isNotNull); expect(bounds, isNotNull);
expect(bounds.north, moreOrLessEquals(49.218965810, epsilon: COORDINATE_PRECISION)); expect(bounds.north, moreOrLessEquals(49.218965810, epsilon: COORDINATE_PRECISION));
...@@ -19,7 +19,7 @@ void main() { ...@@ -19,7 +19,7 @@ void main() {
}); });
test('readKmlFile_2', () async { test('readKmlFile_2', () async {
File xmlFile = new File('test_resources/kml2.xml'); File xmlFile = new File('test/resources/kml2.xml');
LatLonBox bounds = OrienteeringMapKmlFileReader.readFile(xmlFile); LatLonBox bounds = OrienteeringMapKmlFileReader.readFile(xmlFile);
expect(bounds, isNotNull); expect(bounds, isNotNull);
expect(bounds.north, moreOrLessEquals(49.23319176120304, epsilon: COORDINATE_PRECISION)); expect(bounds.north, moreOrLessEquals(49.23319176120304, epsilon: COORDINATE_PRECISION));
......
...@@ -10,7 +10,7 @@ void main() { ...@@ -10,7 +10,7 @@ void main() {
test('Read formatted XML example 1', () async { test('Read formatted XML example 1', () async {
final String name = "EnsiCaen"; final String name = "EnsiCaen";
File xmlFile = new File('test_resources/ensicaen.xml'); File xmlFile = new File('test/resources/ensicaen.xml');
OrienteeringMap map = OrienteeringMapXmlFileReader.readFile(xmlFile, name); OrienteeringMap map = OrienteeringMapXmlFileReader.readFile(xmlFile, name);
expect(map.getName(), name); expect(map.getName(), name);
int controlPointsCount = map.getControlPointsCount(); int controlPointsCount = map.getControlPointsCount();
...@@ -28,7 +28,7 @@ void main() { ...@@ -28,7 +28,7 @@ void main() {
test('Read formatted XML example 2', () async { test('Read formatted XML example 2', () async {
final String name = "Jeune"; final String name = "Jeune";
File xmlFile = new File('test_resources/mdjeunes.xml'); File xmlFile = new File('test/resources/mdjeunes.xml');
OrienteeringMap map = OrienteeringMapXmlFileReader.readFile(xmlFile, name); OrienteeringMap map = OrienteeringMapXmlFileReader.readFile(xmlFile, name);
expect(name, map.getName()); expect(name, map.getName());
int controlPointsCount = map.getControlPointsCount(); int controlPointsCount = map.getControlPointsCount();
...@@ -46,14 +46,14 @@ void main() { ...@@ -46,14 +46,14 @@ void main() {
test('Read Xml File URL empty', () async { test('Read Xml File URL empty', () async {
final String name = "EnsiCaen"; final String name = "EnsiCaen";
File xmlFile = new File('test_resources/ensicaen.xml'); File xmlFile = new File('test/resources/ensicaen.xml');
OrienteeringMap map = OrienteeringMapXmlFileReader.readFile(xmlFile, name); OrienteeringMap map = OrienteeringMapXmlFileReader.readFile(xmlFile, name);
expect(map.getControlPoint(1).getText(), null); expect(map.getControlPoint(1).getText(), null);
}); });
test('Read XmlFile when augmented with control point texts', () async { test('Read XmlFile when augmented with control point texts', () async {
final String name = "EnsiCaen"; final String name = "EnsiCaen";
File xmlFile = new File('test_resources/ensicaen-augmented.xml'); File xmlFile = new File('test/resources/ensicaen-augmented.xml');
OrienteeringMap map = OrienteeringMapXmlFileReader.readFile(xmlFile, name); OrienteeringMap map = OrienteeringMapXmlFileReader.readFile(xmlFile, name);
expect(map.getControlPoint(1).getText(), "Text 1"); expect(map.getControlPoint(1).getText(), "Text 1");
expect(map.getControlPoint(2).getText(), "https://google.com"); expect(map.getControlPoint(2).getText(), "https://google.com");
......
...@@ -86,7 +86,7 @@ void main() { ...@@ -86,7 +86,7 @@ void main() {
} }
List<GeodesicPoint> readGPXFile(String fileName) { List<GeodesicPoint> readGPXFile(String fileName) {
final file = new File('test_resources/$fileName'); final file = new File('test/resources/$fileName');
String contents = file.readAsStringSync(); String contents = file.readAsStringSync();
// <trkpt lat="49.285776" lon="-0.538634"><time>1970-01-01T00:00:19.967Z</time><ele>94.1</ele> // <trkpt lat="49.285776" lon="-0.538634"><time>1970-01-01T00:00:19.967Z</time><ele>94.1</ele>
List<GeodesicPoint> routerPoints = []; List<GeodesicPoint> routerPoints = [];
......
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment