Commit 400c2b51 authored by CLOUARD Regis's avatar CLOUARD Regis
Browse files

Merge branch '118_Add-the-quality-of-the-signal-in-GPS-mode' into 'master'

118 add the quality of the signal in gps mode

See merge request !103
parents 313e278e 18e903f1
......@@ -4,7 +4,14 @@
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 3.4.5 [141]
## 3.4.6 [143]
## Added
Added GPS accuracy display at the bottom of the course screen
## Updated
- Updated Portuguese translation
## 3.4.5 [143]
## Added
- Added launch course directly from QR code
......
......@@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) {
}
android {
compileSdkVersion 32
compileSdkVersion 33
ndkVersion flutter.ndkVersion
compileOptions {
......@@ -47,7 +47,7 @@ android {
defaultConfig {
applicationId "fr.ensicaen.vikazimut"
minSdkVersion 23
targetSdkVersion 32
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
......
......@@ -40,11 +40,6 @@
"enter_nickname": "Zadat přezdívku",
"error_no_nickname": "Zadejte prosím přezdívku",
"history_activity_title": "Předchozí tratě",
"user_map_name": "Mapa:",
"course_date": "Trať z",
"walk_date": "Procházka z",
"geocaching_date": "Geocaching z",
"course_time": "Čas:",
"with_chronometer": "Čas a statistiky",
"without_gps": "Bez pomoci GPS",
"with_gps": "S pomocí GPS",
......@@ -216,5 +211,7 @@
"map_downloading_error_message3": "Nelze stáhnout mapu. Nesprávný klíč.",
"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"
"export_trace_to_gpx_file_success": "Soubor GPX vytvořený v %s",
"gps_info_accuracy": "Přesnost GPS:",
"gps_info_detection_radius": "Detekční rádius:"
}
......@@ -40,11 +40,6 @@
"enter_nickname": "Geben Sie ein Pseudonym ein",
"error_no_nickname": "Bitte geben Sie ein Pseudonym ein",
"history_activity_title": "Frühere Läufe",
"user_map_name": "Karte:",
"course_date": "Lauf vom",
"walk_date": "Spaziergang vom",
"geocaching_date": "Geocaching vom",
"course_time": "Zeit:",
"with_chronometer": "Mit Zeitmessung und Statistik",
"without_gps": "Ohne GPS-Unterstützung",
"with_gps": "Mit GPS-Unterstützung",
......@@ -216,5 +211,7 @@
"map_downloading_error_message3": "Die Karte kann nicht geladen werden. Falscher Schlüssel.",
"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"
"export_trace_to_gpx_file_success": "GPX-Datei erstellt in %s",
"gps_info_accuracy": "GPS-Genauigkeit:",
"gps_info_detection_radius": "Erfassungsradius:"
}
......@@ -40,11 +40,6 @@
"enter_nickname": "Enter a nickname",
"error_no_nickname": "Please enter a nickname",
"history_activity_title": "Previous Courses",
"user_map_name": "Map:",
"course_date": "Course of",
"walk_date": "Walk of",
"geocaching_date": "Geocaching of",
"course_time": "Time:",
"with_chronometer": "Time and statistics",
"without_gps": "Without GPS assistance",
"with_gps": "With GPS assistance",
......@@ -216,5 +211,7 @@
"map_downloading_error_message3": "Cannot download the map. Incorrect key.",
"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"
"export_trace_to_gpx_file_success": "GPX file generated in %s",
"gps_info_accuracy": "GPS accuracy:",
"gps_info_detection_radius": "Detection radius:"
}
......@@ -40,11 +40,6 @@
"enter_nickname": "Entrez un pseudonyme",
"error_no_nickname": "Veuillez entrer un pseudonyme",
"history_activity_title": "Courses précédentes",
"user_map_name": "Carte\u00A0:",
"course_date": "Course du",
"walk_date": "Promenade du",
"geocaching_date": "Geocaching du",
"course_time": "Temps\u00A0:",
"with_chronometer": "Avec chronomètre et statistiques",
"without_gps": "Sans assistance GPS",
"with_gps": "Avec assistance GPS",
......@@ -216,5 +211,7 @@
"map_downloading_error_message3": "Impossible de charger la carte. Clé incorrecte.",
"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"
"export_trace_to_gpx_file_success": "Fichier GPX généré dans %s",
"gps_info_accuracy": "Précision GPS :",
"gps_info_detection_radius": "Rayon de détection :"
}
......@@ -41,11 +41,6 @@
"enter_nickname": "Entrare un nickname",
"error_no_nickname": "Per favore inserisci un nickname",
"history_activity_title": "Gare precedenti",
"user_map_name": "Mappa:",
"course_date": "Percorso del",
"walk_date": "Passeggiato del",
"geocaching_date": "Geocaching del",
"course_time": "Tempo:",
"with_chronometer": "Con cronometro e statistiche",
"without_gps": "Senza assistenza GPS",
"with_gps": "Con assistenza GPS",
......@@ -216,5 +211,7 @@
"map_downloading_error_message3": "Impossibile di caricare la mappa. Chiave errata.",
"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"
"export_trace_to_gpx_file_success": "File creato in %s",
"gps_info_accuracy": "Precisione GPS:",
"gps_info_detection_radius": "Raggio di rilevamento:"
}
......@@ -19,7 +19,7 @@
"enable_gps_service": "Ativar",
"disable_gps_service": "Recusar",
"nfc_unavailable_message": "Não pode validar os pontos por NFC porque o NFC não está ativado ou o seu telemóvel não tem leitor de NFC.",
"gps_disabled_message": "A função localização do seu telemóvel está desativada.\nQue ativá-la?",
"gps_disabled_message": "A função localização do seu telemóvel está desativada.\nQuer ativá-la?",
"gps_denied_message": "A aplicação não tem autorização para utilizar o GPS.\nQuer autorizar?",
"no_network_and_no_stored_map_error_message": "Sem ligação à rede e sem mapa previamente gravado.\nNão é possível continuar.",
"qr_code_menu_item": "Ler código QR",
......@@ -31,7 +31,7 @@
"show_statistics": "Análise",
"total_time_message": "Duração do percurso",
"unknown_checkpoint_message": "Este ponto não pertence ao percurso.",
"wrong_checkpoint_message": "Tem de validar primeiro o ponto %s.\nQuer ignorar este ponto %s?",
"wrong_checkpoint_message": "Tem de validar primeiro o ponto %s.\nQuer ignorar este ponto %s?",
"scanned_checkpoint": "Ponto %s validado",
"quit_course": "Abandonar o percurso",
"validate_missing_checkpoint": "Assinalar o ponto em falta",
......@@ -40,18 +40,13 @@
"enter_nickname": "Insira um pseudónimo",
"error_no_nickname": "Por favor, insira um pseudónimo",
"history_activity_title": "Percursos anteriores",
"user_map_name": "Mapa:",
"course_date": "Prova de",
"walk_date": "Passeio de",
"geocaching_date": "Geocaching de",
"course_time": "Tempo:",
"with_chronometer": "Com cronómetro e estatísticas",
"without_gps": "Sem ajuda GPS",
"with_gps": "Com ajuda GPS",
"vikazimut_presentation_ensicaen": "A app para telemóvel é um projeto de estudantes de informática da ENSICAEN\n(École Nationale Supérieure d’Ingénieurs de Caen, Normandia)",
"vikazimut_presentation_unicaen": "e de estudantes de informática da Universidade de Caen, Normandia",
"vikazimut_student": "Estudantes",
"vikazimut_supervisors": "Ensinantes",
"vikazimut_supervisors": "Responsáveis",
"sport_mode": "Modo desportivo",
"sport_quiz_mode": "Modo geocaching",
"walk_mode": "Modo passeio",
......@@ -85,7 +80,7 @@
"no": "Não",
"force_validation_cp_detailed": "Forçar a validação do ponto n°%s (%s)?",
"force_validation_cp_simple": "Forçar a validação do ponto n°%s?",
"yes_validate": "Sim, forçar",
"yes_validate": "Sim, validar",
"missing_cp_notification_plus_report": "Assinalar e validar o ponto em falta n°%s (%s)?",
"missing_cp_error_no_location": "Não é possível determinar a sua posição para verificar se está perto do ponto.",
"missing_cp_error_distance": "Para forçar a validação de um ponto em falta, tem de estar a menos de %1$d m da sua localização.\nProcure melhor!",
......@@ -103,7 +98,7 @@
"route_length_message": "Distância percorrida",
"theoretical_length_message": "Comprimento teórico:",
"back_pressed_error": "Para abandonar o percurso, utilize e menu e selecione",
"impossible_without_location_provider": "Infelizmente, a app Vikazimut não pode ser utilizada sem geolocalização.",
"impossible_without_location_provider": "Lamentamos, mas a app Vikazimut não pode ser utilizada sem geolocalização.",
"display_help_menu_item": "Manual",
"map_choice_title": "%s percurso",
"general_conditions_of_use_item": "CGU",
......@@ -116,18 +111,18 @@
"qrcode_start_button_message_walk_mode": "Carregue no botão para ler o código QR do ponto de partida",
"penalty_message": "dos quais, penalizações:",
"panic": "Estou perdido!",
"map_list_downloading_message": "Procurar na lista de mapas disponíveis no servidor...",
"checkpoint_index": "Número\nponto",
"map_list_downloading_message": "Procurar na lista\nde mapas disponíveis no servidor...",
"checkpoint_index": "Número do\nponto",
"interval_time": "Tempo\ninter.",
"interval_distance": "Comprimento\n(Distância)",
"interval_pace": "VD\n(Velocidade)",
"cumulative_time": "Tempo\nacumulado",
"really_exit": "Sair da página de resultados",
"result_exit_message": "Quer sair da página de resultados?",
"missing_map_message": "Não é possível mostrar os resultados porque o mapa de suporte foi eliminado do seu telemóvel. Tem de carregar novamente o mapa em modo desportivo ou passeio para poder ver os resultados.",
"missing_map_message": "Não é possível mostrar os resultados porque o mapa de suporte foi eliminado do seu telemóvel. Tem de carregar novamente o mapa em modo desportivo ou de passeio para poder ver os resultados.",
"data_protection": "PRIVACIDADE E PROTEÇÃO DE DADOS: Nenhuns dados pessoais, com exceção do seu pseudónimo e do track GPS desreferenciado horariamente (referenciado a 1 de janeiro de 1970), são enviados para o servidor.",
"yes_bypass": "Sim, quero ignorar",
"no_continue": "Não, continuo a procurar",
"no_continue": "Não, continuo",
"altitude_message": "Desnível positivo",
"quiz_message": "Resultado do questionário",
"course_success": "Percurso completo",
......@@ -159,62 +154,64 @@
"confirm_title": "Confirmação",
"walk_started": "Começou!",
"confirm_gps_title": "Função GPS",
"confirm_gps_message": "A Vikazimut precisa da localização do seu telemóvel, incluindo em pano de fundo, para funcionar. A posição GPS é utilizada para gerir o percurso, desenhar o track e mostrar as estatísticas do percurso. Nenhuns dados serão exportados do telemóvel sem o seu consentimento e o track é desreferenciado horariamente.",
"result_table_distance_column_tooltip": "Comprimento do trajeto entre dois pontos (m)\n(Distância em linha reta entre os dois pontos (m))\nDistância suplementar (%)",
"result_table_pace_column_tooltip": "Velocidade de deslocamento (min/km)\n(Velocidade (km/h))",
"confirm_gps_message": "A Vikazimut precisa da localização do seu telemóvel, incluindo em segundo plano, para funcionar. A posição GPS é utilizada para gerir o percurso, desenhar o track e mostrar as estatísticas do percurso. Nenhuns dados serão exportados do telemóvel sem o seu consentimento e, nesse caso, os dados exportados serão anonimizados e desreferenciados horariamente.",
"result_table_distance_column_tooltip": "Comprimento do trajeto efetuado entre os dois pontos (m).\n(Distância em linha reta entre os dois pontos (m)).\nAcréscimo de distância (%).",
"result_table_pace_column_tooltip": "Redução quilométrica (min/km).\n(Velocidade (km/h)).",
"website_worldmap_page": "worldmap",
"vikazimut_translator": "Tradutores",
"animation_error_title": "Não é possível abrir a animação",
"animation_error_message": "O track GPS está vazio",
"ad_club_label_text": "Percurso traçado por",
"ad_federation_url_text": "https://orienteering.sport/",
"ad_federation_url_text": "https://orienteering.sport",
"ad_federation_label_text": "Contactar um clube de orientação",
"ad_federation_button_text": "IOF",
"map_choice_qrcode": "Ler um percurso",
"map_choice_remove_all": "Retirar tudo",
"error_course_qrcode": "Código QR inválido.\nTalvez tenha lido o código QR do ponto de partida. Nesse caso, carregue o percurso manualmente.",
"map_choice_remove_all": "Descarregar tudo",
"error_course_qrcode": "O código QR não corresponde a nenhum percurso.\nTalvez tenha lido o código QR do ponto de partida. Nesse caso, carregue o percurso manualmente a partir de um dos modos seguintes.",
"loading_course_message": "Carregamento do percurso",
"race_analysis_title": "Análise do percurso",
"race_speed_histogram": "Histograma de velocidades",
"gps_background_notification_title": "Correndo em segundo plano",
"gps_background_notification_message": "Vikazimut continua a receber sua localização",
"mode_with_multimedia_contents": "Com conteúdo multimídia",
"orienteer_profile_rank_wood_one": "Wood 1",
"orienteer_profile_rank_wood_two": "Wood 2",
"orienteer_profile_rank_bronze_one": "Bronze 1",
"orienteer_profile_rank_bronze_two": "Bronze 2",
"orienteer_profile_rank_silver_one": "Silver 1",
"orienteer_profile_rank_silver_two": "Silver 2",
"orienteer_profile_rank_gold_one": "Gold 1",
"orienteer_profile_rank_gold_two": "Gold 2",
"orienteer_profile_rank_diamond_one": "Diamond 1",
"orienteer_profile_rank_diamond_two": "Diamond 2",
"orienteer_profile_profile_description_title": "Features",
"orienteer_profile_fiery_profile_description_text": "Large speed range.\nLot of stops.\nLot of high speeds.",
"orienteer_profile_profile_advice_title": "Advices",
"orienteer_profile_fiery_profile_advice_text": "Improve anticipation:\nRun slower so you can read the map all the time.\nPrepare your routes in advance so you don't have to stop.",
"orienteer_profile_walker_profile_description_text": "Low speed amplitude.\nFew stops, continuous running.",
"orienteer_profile_walker_profile_advice_text": "Take advantage of the handrails to run faster.\nTake less support points validating your progress in order to be able to run without asking questions.",
"orienteer_profile_expert_profile_description_text": "Low speed range.\nNo stop.\nHigh average speed.",
"orienteer_profile_expert_profile_advice_text": "Congratulations, it's perfect",
"orienteer_profile_typical_profile": "Typical Orienteer Profiles",
"orienteer_profile_estimated_level": "Estimated Level",
"orienteer_profile_estimated_level_error": "... not enough track length to estimate a level",
"orienteer_profile_typical_profile_description": "The level is estimated from the analysis of the speed histogram weighted by the elevation gain. The scale is divided into 10 levels of progression corresponding roughly to three orienteer profiles below",
"orienteer_profile_fiery_profile_title": "Fiery\nRunner",
"orienteer_profile_walker_profile_title": "Walker\nOrienteer",
"orienteer_profile_expert_profile_title": "Expert\nOrienteer",
"validation_only_equipped_route": "(se curso equipado)",
"race_speed_histogram_subtitle": "weighted by the elevation gain",
"error_course_qrcode_unset": "The course has not been set by the planner to be start automatically from the QR code.\nLaunch the course manually be selecting one of the mode below.",
"playful_course": "Playful course with tourist content or quiz",
"secret_key_dialog_title": "Digite a chave secreta",
"gps_background_notification_title": "Execução em segundo plano",
"gps_background_notification_message": "A Vikazimut continua a receber a sua posição",
"mode_with_multimedia_contents": "Com conteúdo multimédia",
"orienteer_profile_rank_wood_one": "Madeira1",
"orienteer_profile_rank_wood_two": "Madeira2",
"orienteer_profile_rank_bronze_one": "Bronze1",
"orienteer_profile_rank_bronze_two": "Bronze2",
"orienteer_profile_rank_silver_one": "Prata1",
"orienteer_profile_rank_silver_two": "Prata2",
"orienteer_profile_rank_gold_one": "Ouro1",
"orienteer_profile_rank_gold_two": "Ouro2",
"orienteer_profile_rank_diamond_one": "Diamante1",
"orienteer_profile_rank_diamond_two": "Diamante2",
"orienteer_profile_profile_description_title": "Características",
"orienteer_profile_fiery_profile_description_text": "Grande amplitude de velocidades.\nNumerosas fases de paragem e de velocidades elevadas.",
"orienteer_profile_profile_advice_title": "Conselhos",
"orienteer_profile_fiery_profile_advice_text": "Melhorar a antecipação:\nCorrer mais devagar para poder ler o mapa em permanência.\nPreparar os itinerários antecipadamente para não ter de parar.",
"orienteer_profile_walker_profile_description_text": "Fraca amplitude de velocidade.\nPoucas paragens, movimento contínuo.",
"orienteer_profile_walker_profile_advice_text": "Aproveitar os trechos com referências claras para correr mais rapidamente.\n Utilizar menos pontos de referência para validar a sua progressão a fim de poder correr sem se questionar.",
"orienteer_profile_expert_profile_description_text": "Fraca amplitude de velocidade.\nSem paragens.\nVelocidade média elevada.",
"orienteer_profile_expert_profile_advice_text": "PARABÉNS, está perfeito!",
"orienteer_profile_typical_profile": "Perfis-tipo de orientista",
"orienteer_profile_estimated_level": "Nível estimado",
"orienteer_profile_estimated_level_error": "... sem tempo de percurso suficiente para estimar um nível",
"orienteer_profile_typical_profile_description": "O nível é estimado a partir da análise do histograma de velocidades ponderadas pelo desnível positivo. A escala está dividida em 10 veis de progressão genericamente correspondentes aos três perfis de orientista seguintes.",
"orienteer_profile_fiery_profile_title": "Corredor\nrápido",
"orienteer_profile_walker_profile_title": "Orientista\nde lazer",
"orienteer_profile_expert_profile_title": "Orientista\nexperiente",
"validation_only_equipped_route": "(se o percurso estiver equipado)",
"race_speed_histogram_subtitle": "ponderadas pelo desnível positivo",
"error_course_qrcode_unset": "O percurso não foi configurado pelo traçador para arrancar automaticamente a partir do código QR.\nCarregue o percurso manualmente a partir de um dos modos seguintes.",
"playful_course": "Percurso lúdico com conteúdo turístico ou quiz",
"secret_key_dialog_title": "Insira a chave de acesso",
"secret_key_dialog_label": "Chave secreta",
"secret_key_dialog_button": "Carregar percurso",
"secret_key_dialog_button": "Carregar o percurso",
"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": "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"
"export_trace_to_gpx_file": "Exportare GPX",
"export_trace_to_gpx_file_error": "Impossível criar o ficheiro GPX (direitos, memória?)",
"export_trace_to_gpx_file_success": "Ficheiro GPX gerado em %s",
"gps_info_accuracy": "Precisão do GPS:",
"gps_info_detection_radius": "Raio de detecção:"
}
......@@ -56,6 +56,10 @@ abstract class AbstractCourseViewState extends State<AbstractCourseView> impleme
Widget get chronometerView => _coursePresenter.getChronometerWidget();
Widget get gpsInfoView => _coursePresenter.getGpsInfoWidget();
String get getGpsSignalQuality => _coursePresenter.getGpsSignalQuality();
OrienteeringMap get map => widget._map;
MapView? getMapView() => _mapView;
......@@ -84,6 +88,7 @@ abstract class AbstractCourseViewState extends State<AbstractCourseView> impleme
child: Scaffold(
appBar: buildCourseAppBarWidget(context),
body: buildCourseBodyWidget(context),
bottomNavigationBar: buildCourseBottomWidget(context),
),
),
);
......@@ -91,6 +96,8 @@ abstract class AbstractCourseViewState extends State<AbstractCourseView> impleme
PreferredSizeWidget buildCourseAppBarWidget(BuildContext context);
Widget? buildCourseBottomWidget(BuildContext context);
Widget buildCourseBodyWidget(BuildContext context) {
return Container(
width: double.infinity,
......
import 'package:vikazimut/constants.dart';
import 'package:vikazimut/course/course_presenter.dart';
import 'package:vikazimut/map/checkpoint_out_of_bounds_exception.dart';
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";
static const String START_BEACON_ID = "1111";
static const String END_BEACON_ID = "9999";
final CoursePresenter _coursePresenter;
AbstractCourseController(CoursePresenter coursePresenter) : _coursePresenter = coursePresenter;
......@@ -25,9 +24,9 @@ abstract class AbstractCourseController {
void punchNamedCheckpointFromBeacon(String checkpointName) {
try {
int checkpointId;
if (checkpointName == START_IBEACON_ID) {
if (checkpointName == START_BEACON_ID) {
checkpointId = 0;
} else if (checkpointName == END_IBEACON_ID) {
} else if (checkpointName == END_BEACON_ID) {
checkpointId = _coursePresenter.getLastCheckpointId();
} else {
checkpointId = _coursePresenter.getCheckpointIdFromName(checkpointName);
......
......@@ -9,6 +9,7 @@ import 'package:vikazimut/database/database_gateway.dart';
import 'package:vikazimut/device/beacon/beacon_scanner.dart';
import 'package:vikazimut/device/chronometer.dart';
import 'package:vikazimut/device/gps_checkpoint_validation.dart';
import 'package:vikazimut/device/gps_info.dart';
import 'package:vikazimut/device/gps_location_listener.dart';
import 'package:vikazimut/device/gps_tracker_service.dart';
import 'package:vikazimut/device/nfc/nfc_scanner.dart';
......@@ -34,6 +35,7 @@ class CoursePresenter extends ChangeNotifier {
late final Course _course;
final GpsTrackerService _gpsTracker = GpsTrackerService();
final Chronometer _chronometer = Chronometer();
final GpsInfo _gpsInfo = GpsInfo();
final AudioPlayer _audioPlayer = AudioPlayer();
bool _withWebPage = false;
bool _isStarted = false;
......@@ -46,6 +48,7 @@ class CoursePresenter extends ChangeNotifier {
CoursePresenter(AbstractCourseViewState widget, OrienteeringMap map) : _view = widget {
_course = Course(map, _chronometer);
_addGPSListener(_view);
_addGPSListener(_gpsInfo);
_adjustAudioVolume();
}
......@@ -382,17 +385,15 @@ class CoursePresenter extends ChangeNotifier {
return _course.isStartAndFinishAtSamePosition();
}
int getQuizTotalPoints() {
return _course.quizTotalPoints;
}
int getQuizTotalPoints() => _course.quizTotalPoints;
void setQuizTotalPoints(int points) {
_course.quizTotalPoints = points;
}
void setQuizTotalPoints(int points) => _course.quizTotalPoints = points;
Widget getChronometerWidget() {
return _chronometer.getWidget();
}
Widget getChronometerWidget() => _chronometer.getWidget();
Widget getGpsInfoWidget() => _gpsInfo.getWidget(GPSCheckpointValidation.minDistanceToCpInMeter);
String getGpsSignalQuality() => _gpsTracker.getGpsSignalQuality();
bool isCloseToCheckpoint(int checkpointId) {
GeodesicPoint? currentLocation = getLastKnownLocation();
......@@ -407,8 +408,7 @@ class CoursePresenter extends ChangeNotifier {
if (currentLocation == null) {
return false;
} else {
double distance = currentLocation.distanceToInMeter(checkpointLocation);
return distance <= minDistance;
return currentLocation.distanceToInMeter(checkpointLocation) <= minDistance;
}
}
}
......@@ -18,7 +18,6 @@ import 'package:vikazimut/utils/i18n.dart';
import 'start_view_item/menu_tooltip.dart';
import 'start_view_item/start_stop_icon_menu_item.dart';
import 'subview/course_settings_view.dart';
import 'subview/last_punched_view.dart';
import 'subview/sport_course_settings_view.dart';
......@@ -32,16 +31,16 @@ class SportCourseView extends AbstractCourseView {
class _SportCourseViewState extends AbstractCourseViewState {
final GlobalKey<StartStopButtonState> _startMenuIconKey = GlobalKey<StartStopButtonState>();
final GlobalKey<PopupMenuButtonState> _popupMenuKey = GlobalKey<PopupMenuButtonState>();
ValidationMode? _validationMode;
final ValueNotifier<ValidationMode?> _validationMode = ValueNotifier<ValidationMode?>(null);
LocationLayer? _locationHelpOverlay;
MarkerLayer? _pointOverlay;
int _assistanceCount = 0;
bool _isPrepared = false;
bool _isReady = false;
bool _isStarted = false;
@override
void initializeCourseMenu(ValidationMode validationMode, MenuTooltip menuTooltip) {
_validationMode = validationMode;
_validationMode.value = validationMode;
_startMenuIconKey.currentState?.addIconMenu(validationMode);
WidgetsBinding.instance.addPostFrameCallback((_) {
menuTooltip.show(_startMenuIconKey);
......@@ -71,7 +70,7 @@ class _SportCourseViewState extends AbstractCourseViewState {
void handlePrepareCourse() {
super.handlePrepareCourse();
_popupMenuKey.currentState?.setState(() {
_isPrepared = true;
_isReady = true;
});
MapView? view = getMapView();
if (view != null) {
......@@ -129,11 +128,12 @@ class _SportCourseViewState extends AbstractCourseViewState {
@override
PreferredSizeWidget buildCourseAppBarWidget(BuildContext context) {
return PreferredSize(
preferredSize: const Size.fromHeight(80.0),
preferredSize: const Size.fromHeight(75.0),
child: AppBar(
toolbarHeight: 80,
toolbarHeight: 75,
automaticallyImplyLeading: false,
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
chronometerView,
LastPunchedText(),
......@@ -174,13 +174,13 @@ class _SportCourseViewState extends AbstractCourseViewState {
const PopupMenuDivider(height: 10),
PopupMenuItem(
value: 2,
enabled: _validationMode == ValidationMode.MANUAL || _isPrepared,
enabled: _validationMode.value == ValidationMode.MANUAL || _isReady,
child: Text(
I18n.getString("skip_missing_checkpoint"),
style: _validationMode == ValidationMode.MANUAL || _isPrepared ? const TextStyle(color: Colors.white) : const TextStyle(color: kPrimaryColorDisabled),
style: _validationMode.value == ValidationMode.MANUAL || _isReady ? const TextStyle(color: Colors.white) : const TextStyle(color: kPrimaryColorDisabled),
),
),
if (_validationMode == ValidationMode.MANUAL)
if (_validationMode.value == ValidationMode.MANUAL)
PopupMenuItem(
value: 3,
child: Text(
......@@ -213,7 +213,7 @@ class _SportCourseViewState extends AbstractCourseViewState {
Navigator.push(context, MaterialPageRoute(builder: (context) => ManualView()));
break;
case 2:
if (_validationMode == ValidationMode.MANUAL || _validationMode == ValidationMode.BEACON) {
if (_validationMode.value == ValidationMode.MANUAL || _validationMode.value == ValidationMode.BEACON) {
forceValidationQRCheckpoint();
} else {
forceValidationGPSCheckpoint(detailed: true);
......@@ -242,4 +242,34 @@ class _SportCourseViewState extends AbstractCourseViewState {
@override
CourseSettingsView buildCourseSettingsDialog(BuildContext context, CourseFormat? defaultCourseFormat, ValidationMode? defaultValidationMode) => SportCourseSettingsView(context, super.widget.withWebPage, defaultCourseFormat, defaultValidationMode);
@override
void dispose() {
super.dispose();
_validationMode.dispose();
}
@override
Widget? buildCourseBottomWidget(BuildContext context) {
return ValueListenableBuilder<ValidationMode?>(
valueListenable: _validationMode,
builder: (BuildContext context, ValidationMode? value, child) {
if (value == ValidationMode.GPS) {
return BottomAppBar(
color: Colors.black,
child: Theme(
data: Theme.of(context).copyWith(
iconTheme: const IconThemeData(color: Colors.white),
),
child: DefaultTextStyle(
style: const TextStyle(color: Colors.white),
child: gpsInfoView,
),
),
);
} else {
return const SizedBox.shrink();
}
});
}
}
......@@ -15,7 +15,6 @@ import 'package:vikazimut/result/global_result/walk_global_result_view.dart';
import 'package:vikazimut/theme.dart';
import 'package:vikazimut/utils/i18n.dart';
import 'abstract_course_view.dart';
import 'start_view_item/menu_tooltip.dart';
import 'start_view_item/start_stop_icon_menu_item.dart';
import 'subview/last_punched_view.dart';
......@@ -108,7 +107,7 @@ class _WalkCourseViewState extends AbstractCourseViewState {
@override
PreferredSizeWidget buildCourseAppBarWidget(BuildContext context) {
return AppBar(
toolbarHeight: 80,
toolbarHeight: 75,
automaticallyImplyLeading: false,
title: Column(
children: [
......@@ -189,4 +188,7 @@ class _WalkCourseViewState extends AbstractCourseViewState {
@override
CourseSettingsView buildCourseSettingsDialog(BuildContext context, CourseFormat? defaultCourseFormat, ValidationMode? defaultValidationMode) => WalkCourseSettingsView(context, defaultCourseFormat, defaultValidationMode);
@override
Widget? buildCourseBottomWidget(BuildContext context) => null;
}
......@@ -3,29 +3,29 @@ import 'dart:async';
import 'package:flutter_beacon/flutter_beacon.dart';
class BeaconScanner {
static const List<String> BEACON_UUID = [
'01122334-4556-6778-899A-ABBCCDDEEFF0',
'FDA50693A-4E24-FB1A-FCFC-6EB07647825',
];
static const BEACON_IDENTIFIER = 'Vikazimut';
static const int THRESHOLD = -80;
static StreamSubscription<RangingResult>? _streamRanging;
static int threshold = -70;
static bool _isAlive = false;
BeaconScanner._();
static Future<void> initializeBeaconSupport() async {
await flutterBeacon.initializeAndCheckScanning;
_isAlive = true;
await flutterBeacon.initializeAndCheckScanning;
}
static void start({required void Function(String) callback}) {
if (!_isAlive) {
return;