Commit e317037c authored by CLOUARD Regis's avatar CLOUARD Regis
Browse files

Merge branch '109_New-release' into 'master'

Version 3.4.5

See merge request !101
parents 85f6bde6 965dfc83
......@@ -12,7 +12,7 @@ visant la réalisation d'une application mobile pour la pratique de la course d'
L’application Vikazimut a pour objectif de faciliter la pratique de la course d’orientation. Elle remplace la carte papier, la boussole et le poinçon de validation des points de contrôle.
Un parcours d'orientation consiste en une suite de points de contrôle matérialisés sur le terrain par une balise type fédération internationale de course d’orientation (IOF) contenant un code QR, un tag NFC ou un iBeacon. Un mode automatique permet de faire la validation des points de passage directement à partir de la position GPS sans utiliser les codes QR ou les tags NFC ou les iBeacons.
Un parcours d'orientation consiste en une suite de points de contrôle matérialisés sur le terrain par une balise type fédération internationale de course d’orientation (IOF) contenant un code QR, un tag NFC ou un iBeacon. Un mode automatique permet de faire la validation des points de passage directement à partir de la position GPS sans utiliser de balises physiques.
L’orienteur utilise l’application pour se repérer à partir de la carte et valider son passage aux points de contrôle avec le lecteur de code QR, le lecteur de tag NFC, le lecteur de iBeacons ou la position GPS.
......@@ -25,8 +25,8 @@ et un mode promenade qui affiche en plus la positon de l’orienteur sur la cart
## Projet lié
- Le site Web en Flutter ([projet gitlab](https://gitlab.com/vikazimut/vikazimut-website)).
- Le serveur Web en Symfony ([projet gitlab](https://gitlab.com/vikazimut/vikazimut-server)).
- Le site Web en Flutter - partie frontend ([projet gitlab](https://gitlab.com/vikazimut/vikazimut-website)).
- Le serveur Web en Symfony - partie backend ([projet gitlab](https://gitlab.com/vikazimut/vikazimut-server)).
## Téléchargement de l'application
......
......@@ -214,7 +214,7 @@
"map_downloading_error_message1": "Nelze stáhnout mapu. Není síťové připojení.",
"map_downloading_error_message2": "Nelze stáhnout mapu. Neznámá mapa.",
"map_downloading_error_message3": "Nelze stáhnout mapu. Nesprávný klíč.",
"export_trace_to_gpx_file": "GPX soubor",
"export_trace_to_gpx_file": "Vývozní GPX",
"export_trace_to_gpx_file_error": "Cannot create file GPX (permission or memory?)",
"export_trace_to_gpx_file_success": "Soubor GPX vytvořený v %s"
}
......@@ -214,7 +214,7 @@
"map_downloading_error_message1": "Die Karte kann nicht geladen werden. Keine Netzwerkverbindung.",
"map_downloading_error_message2": "Die Karte kann nicht geladen werden. Unbekannte Karte.",
"map_downloading_error_message3": "Die Karte kann nicht geladen werden. Falscher Schlüssel.",
"export_trace_to_gpx_file": "GPX-Datei",
"export_trace_to_gpx_file": "Export GPX",
"export_trace_to_gpx_file_error": "Cannot create file GPX (permission or memory?)",
"export_trace_to_gpx_file_success": "GPX-Datei erstellt in %s"
}
......@@ -214,7 +214,7 @@
"map_downloading_error_message1": "Cannot download the map. No network connection.",
"map_downloading_error_message2": "Cannot download the map. Unknown map.",
"map_downloading_error_message3": "Cannot download the map. Incorrect key.",
"export_trace_to_gpx_file": "GPX file",
"export_trace_to_gpx_file": "Export GPX",
"export_trace_to_gpx_file_error": "Cannot create file GPX (permission or memory?)",
"export_trace_to_gpx_file_success": "GPX file generated in %s"
}
......@@ -214,7 +214,7 @@
"map_downloading_error_message1": "Impossible de charger la carte. Pas de connexion réseau.",
"map_downloading_error_message2": "Impossible de charger la carte. Carte inconnue.",
"map_downloading_error_message3": "Impossible de charger la carte. Clé incorrecte.",
"export_trace_to_gpx_file": "Fichier GPX",
"export_trace_to_gpx_file_error": "Impossible de créer le fichier GPX (droit, mémoire ?)",
"export_trace_to_gpx_file": "Exporter GPX",
"export_trace_to_gpx_file_error": "Impossible de créer le fichier GPX (permission, mémoire ?)",
"export_trace_to_gpx_file_success": "Fichier GPX généré dans %s"
}
......@@ -214,7 +214,7 @@
"map_downloading_error_message1": "Impossibile di caricare la mappa. Nessuna connessione di rete.",
"map_downloading_error_message2": "Impossibile di caricare la mappa. Mappa sconosciuta.",
"map_downloading_error_message3": "Impossibile di caricare la mappa. Chiave errata.",
"export_trace_to_gpx_file": "File GPX",
"export_trace_to_gpx_file": "Esportare GPX",
"export_trace_to_gpx_file_error": "Cannot create file GPX (permission or memory?)",
"export_trace_to_gpx_file_success": "File creato in %s"
}
......@@ -214,7 +214,7 @@
"map_downloading_error_message1": "Não é possível carregar o mapa. Sem ligação à rede.",
"map_downloading_error_message2": "Não é possível carregar o mapa. Mapa desconhecido.",
"map_downloading_error_message3": "Não é possível carregar o mapa. Chave incorreta.",
"export_trace_to_gpx_file": "Arquivo GPX",
"export_trace_to_gpx_file": "Exportar GPX",
"export_trace_to_gpx_file_error": "Cannot create file GPX (permission or memory?)",
"export_trace_to_gpx_file_success": "Arquivo GPX criado no %s"
}
......@@ -5,9 +5,9 @@ import 'package:vikazimut/utils/i18n.dart';
import '../course_presenter.dart';
abstract class AbstractCourseController {
static const String START_IBEACON_ID = "1111";
static const String END_IBEACON_ID = "9999";
final CoursePresenter _coursePresenter;
static int _previousWarningTimeInMillisecond = 0;
static int _previousWarningCheckpoint = -1; // TODO ici
AbstractCourseController(CoursePresenter coursePresenter) : _coursePresenter = coursePresenter;
......@@ -23,43 +23,19 @@ abstract class AbstractCourseController {
}
void punchNamedCheckpointFromBeacon(String checkpointName) {
// TODO à nettoyer
void displayWarningMessage(int checkpointId, String message) {
if (checkpointId == _previousWarningCheckpoint) {
int currentTimeInMillisecond = DateTime.now().millisecondsSinceEpoch;
if (currentTimeInMillisecond - _previousWarningTimeInMillisecond > 10 * 1000) {
_coursePresenter.invalidate(I18n.getString("checkpoint_error_title"), message);
_previousWarningTimeInMillisecond = currentTimeInMillisecond;
}
} else {
_coursePresenter.invalidate(I18n.getString("checkpoint_error_title"), message);
_previousWarningTimeInMillisecond = DateTime.now().millisecondsSinceEpoch;
_previousWarningCheckpoint = checkpointId;
}
}
try {
int checkpointId;
if (checkpointName == "1111") {
if (checkpointName == START_IBEACON_ID) {
checkpointId = 0;
} else if (checkpointName == "9999") {
} else if (checkpointName == END_IBEACON_ID) {
checkpointId = _coursePresenter.getLastCheckpointId();
} else {
// TODO cette methode peut retourner une exception CheckpointOutOfBoundsException
checkpointId = _coursePresenter.getCheckpointIdFromName(checkpointName);
}
if (!_coursePresenter.isAlreadyPunched(checkpointId)) {
punchCheckpointFromGPS(checkpointId);
_previousWarningCheckpoint = checkpointId; // TODO Supprimer cette variable
_previousWarningTimeInMillisecond = DateTime.now().millisecondsSinceEpoch; // TODO Supprimer cette variable
} else {
// TODO
// displayWarningMessage(checkpointId, I18n.getString("already_scanned_checkpoint_message", [checkpointId.toString()]));
}
} on CheckpointOutOfBoundsException {
// TODO à garder ?
displayWarningMessage(-1, I18n.getString("unknown_checkpoint_message"));
}
} catch (_) {}
}
void punchCheckpointFromDevice(int checkpointId) {
......
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_beacon/flutter_beacon.dart';
import 'package:vikazimut/main.dart';
class BeaconScanner {
// static const VIKAZIMUT_UUID = 'FDA50693A4E24FB1AFCFC6EB07647825'; TODO remove
static const BEACON_UUID = '0112233445566778899AABBCCDDEEFF0';
static const List<String> BEACON_UUID = ['0112233445566778899AABBCCDDEEFF0', 'FDA50693A4E24FB1AFCFC6EB07647825'];
static const BEACON_IDENTIFIER = 'Vikazimut';
static StreamSubscription<RangingResult>? _streamRanging;
static int threshold = -70;
......@@ -20,64 +17,18 @@ class BeaconScanner {
}
static void start({required void Function(String) callback}) {
if (!_isAlive) return;
// // TODO supprimer
// ScaffoldMessenger.of(globalKeyContext.currentContext!).removeCurrentSnackBar();
// ScaffoldMessenger.of(globalKeyContext.currentContext!).showSnackBar(const SnackBar(
// content: Text("En écoute de iBeacons..."),
// duration: Duration(seconds: 3),
// ));
final regions = <Region>[
Region(
identifier: BEACON_IDENTIFIER,
proximityUUID: BEACON_UUID,
),
Region(
identifier: BEACON_IDENTIFIER,
proximityUUID: 'FDA50693A4E24FB1AFCFC6EB07647825', // TODO supprimer ??
),
];
if (!_isAlive) {
return;
}
final List<Region> regions = BEACON_UUID.map((uuid) => Region(identifier: BEACON_IDENTIFIER, proximityUUID: uuid)).toList();
_streamRanging = flutterBeacon.ranging(regions).listen((RangingResult result) {
// TODO remettre
List<Beacon> beacons = result.beacons.where((beacon) => beacon.rssi >= threshold).toList();
// TODO Ne pas trier mais appeler la fonction sur tous les iBeacons
// beacons.sort(compareBasedOnRssiValue_);
// if (beacons.isNotEmpty) {
// TODO Supprimer cet affichage après les alpha tests
String message = "Postes:";
for (var beacon in beacons) {
message += " ${beacon.minor} (rssi: ${beacon.rssi})";
}
ScaffoldMessenger.of(globalKeyContext.currentContext!).removeCurrentSnackBar();
ScaffoldMessenger.of(globalKeyContext.currentContext!).showSnackBar(SnackBar(
content: Text(message),
duration: const Duration(seconds: 3),
));
// TODO lancer callback sur tous les iBeacons
// TODO pour cela supprimer le message d'erreur quand on revalide une balise
for (var beacon in beacons) {
callback(beacon.minor.toString());
}
// if (beacons.isNotEmpty) {
// callback(beacons.first.minor.toString());
// }
// }
});
}
// // TODO supprimer
// @visibleForTesting
// static int compareBasedOnRssiValue_(Beacon a, Beacon b) {
// int compare = b.rssi.compareTo(a.rssi);
// if (compare == 0) {
// compare = b.minor.compareTo(a.minor);
// }
// return compare;
// }
static void stop() {
_streamRanging?.cancel();
if (_isAlive) {
......
......@@ -31,8 +31,9 @@ class _QrCodeScannerState extends State<QrCodeScanner> {
super.reassemble();
if (Platform.isAndroid) {
_controller!.pauseCamera();
} else if (Platform.isIOS) {
_controller!.resumeCamera();
}
_controller!.resumeCamera();
}
@override
......@@ -64,7 +65,7 @@ class _QrCodeScannerState extends State<QrCodeScanner> {
void _onQRViewCreated(QRViewController controller) {
_controller = controller;
_controller!.resumeCamera(); // TODO temporary fix for version 1.0.0 of the qr_code_scanner package
_controller!.resumeCamera(); // TODO temporary fix for qr_code_scanner package 1.0.1
_listen = controller.scannedDataStream.listen((Barcode scanData) {
_listen?.cancel();
Navigator.pop<String>(context, scanData.code);
......
......@@ -23,7 +23,7 @@ abstract class AbstractGlobalResultPresenter {
Result? get result => _result;
bool get isSport;
bool get isSportMode;
int? get totalCheckPoints => _result?.totalCheckpoints;
......@@ -59,38 +59,38 @@ abstract class AbstractGlobalResultPresenter {
return null;
}
void disableSentFlagForCourseEntity() {
ResultDatabaseGateway.disableSentFlagForCourseResult(resultId);
}
void onSendResultToServer(BuildContext context) async {
ResultSendingPresenter(this).onSendResultToServer(context);
}
void handlePositiveSendingResultsToServerResponse() {
disableSentFlagForCourseEntity();
_disableSentFlagForCourseEntity();
_result?.isSent = true;
_view?.disableSendButton();
launchWebBrowserWithTheRoutePage();
_launchWebBrowserWithTheRoutePage();
}
void handleNegativeSendingResultsToServerResponse(String message) {
_view?.displayMessage(message);
}
void launchWebBrowserWithTheRoutePage() async {
var url = Uri.tryParse(getWebResultPageURL(_result!.routeId));
if (url != null && await navigator.canLaunchUrl(url)) {
await navigator.launchUrl(url);
}
}
void exportGPXFile(Result result) async {
String? filename = await GPXFile.save(result);
if (filename == null) {
_view!.displayMessage(I18n.getString("export_trace_to_gpx_file_error"));
_view?.displayMessage(I18n.getString("export_trace_to_gpx_file_error"));
} else {
_view!.displayMessage(I18n.getString("export_trace_to_gpx_file_success", [filename]));
_view?.displayMessage(I18n.getString("export_trace_to_gpx_file_success", [filename]));
}
}
void _disableSentFlagForCourseEntity() {
ResultDatabaseGateway.disableSentFlagForCourseResult(resultId);
}
void _launchWebBrowserWithTheRoutePage() async {
var url = Uri.tryParse(getWebResultPageURL(_result!.routeId));
if (url != null && await navigator.canLaunchUrl(url)) {
await navigator.launchUrl(url);
}
}
}
......@@ -125,7 +125,7 @@ class AbstractResultDisplayState extends State<AbstractGlobalResultView> {
CourseTimeWidget(snapshot.data!, widget.presenter.createPenaltyMessage()),
CourseLengthWidget(snapshot.data!),
CourseAltitudeWidget(snapshot.data!),
CoursePaceWidget(snapshot.data!, widget.presenter.isSport),
CoursePaceWidget(snapshot.data!, widget.presenter.isSportMode),
Padding(
padding: const EdgeInsets.fromLTRB(5, 0, 5, 0),
child: Row(
......
......@@ -7,7 +7,7 @@ import 'package:vikazimut/result/result.dart';
class GPXFile {
static Future<String?> save(Result result) async {
final filename = "${result.mapName}-${DateTime.now()}.gpx";
final filename = "${result.mapName}-${result.totalTimeWithoutPenaltyAsString}.gpx";
final file = await _localFile(filename);
String gpxTrack = buildGPXContentFromResult_(result);
var isSaved = await _writeCounter(file, gpxTrack) != null;
......
......@@ -7,7 +7,7 @@ class SportGlobalResultPresenter extends AbstractGlobalResultPresenter {
SportGlobalResultPresenter({required int resultId}) : super(resultId);
@override
bool get isSport => true;
bool get isSportMode => true;
@override
bool get isStatisticsAvailable => true;
......
......@@ -7,7 +7,7 @@ class WalkGlobalResultPresenter extends AbstractGlobalResultPresenter {
WalkGlobalResultPresenter({required int resultId}) : super(resultId);
@override
bool get isSport => false;
bool get isSportMode => false;
@override
bool get isStatisticsAvailable => false;
......
......@@ -5,7 +5,7 @@ description: Application de course d'orientation
# pub.dev using `pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 3.4.5-b.10+141
version: 3.4.5+142
environment:
sdk: ">=2.17.0 <3.0.0"
......@@ -44,7 +44,7 @@ dependencies:
geolocator: ^9.0.1
flutter_compass: ^0.7.0
qr_code_scanner: 1.0.0
qr_code_scanner: 1.0.1
nfc_manager: ^3.1.1
flutter_beacon: ^0.5.1
......
import 'package:flutter_beacon/flutter_beacon.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:vikazimut/device/beacon/beacon_scanner.dart';
import 'package:vikazimut/device/gps_checkpoint_validation.dart';
void main() {
......@@ -13,26 +11,4 @@ void main() {
var actual = GPSCheckpointValidation.getGpsDetectionRadius_(1500, null);
expect(actual, GPSCheckpointValidation.MIN_DISTANCE_TO_CP_IN_METERS_SMALL_SCALE);
});
// // TODO remove ??
// test('Test compare beacon when equals', () async {
// Beacon b1 = const Beacon(proximityUUID: BeaconScanner.BEACON_UUID, major: 1, minor: 2, rssi: -70, accuracy: 1);
// Beacon b2 = const Beacon(proximityUUID: BeaconScanner.BEACON_UUID, major: 1, minor: 2, rssi: -70, accuracy: 1);
// var actual = BeaconScanner.compareBasedOnRssiValue_(b1, b2);
// expect(actual, 0);
// });
//
// test('Test compare beacon when less', () async {
// Beacon b1 = const Beacon(proximityUUID: BeaconScanner.BEACON_UUID, major: 1, minor: 2, rssi: -69, accuracy: 1);
// Beacon b2 = const Beacon(proximityUUID: BeaconScanner.BEACON_UUID, major: 1, minor: 2, rssi: -70, accuracy: 1);
// var actual = BeaconScanner.compareBasedOnRssiValue_(b1, b2);
// expect(actual, lessThan(0));
// });
//
// test('Test compare beacon when geater', () async {
// Beacon b1 = const Beacon(proximityUUID: BeaconScanner.BEACON_UUID, major: 1, minor: 2, rssi: -71, accuracy: 1);
// Beacon b2 = const Beacon(proximityUUID: BeaconScanner.BEACON_UUID, major: 1, minor: 2, rssi: -70, accuracy: 1);
// var actual = BeaconScanner.compareBasedOnRssiValue_(b1, b2);
// expect(actual, greaterThan(0));
// });
}
......@@ -2,7 +2,7 @@
<!-- Generator: OCAD Version 12.3.1 -->
<kml xmlns="http://earth.google.com/kml/2.2">
<Folder>
<name></name>
<name>Name</name>
<GroundOverlay>
<name>tile_0_0.jpg</name>
<drawOrder>75</drawOrder>
......
Supports Markdown
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