feat: Enhance map functionality with download area selection and user location pointer

- Added download area selection feature in MapProvider with methods to enter, exit, and update bounds.
- Implemented DownloadAreaOverlay widget for user interface during download area selection.
- Integrated download area preview in MapManagementScreen with visual feedback.
- Introduced LocationPointer widget to indicate user location and heading on the map.
- Updated localization strings in app_sl.arb for improved clarity and accuracy.
- Refactored map marker generation to utilize the new LocationPointer for user location representation.
- Improved error handling in LocationTrackingService for initial position setting.
This commit is contained in:
Janez T
2025-10-28 19:09:11 +01:00
parent fed4aa19f1
commit da3302b030
14 changed files with 769 additions and 272 deletions

View File

@@ -1,3 +1,4 @@
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
untranslated-messages-file: lib/l10n/untranslated.json

View File

@@ -69,7 +69,7 @@
"autoSystem": "Automatski (Sustav)",
"followSystemTheme": "Slijedi sistemsku temu",
"followSystemTheme": "Slijedi temu sustava",
"showRxTxIndicators": "Prikaži RX/TX indikatore",
@@ -141,11 +141,11 @@
"technologiesUsed": "Korištene tehnologije:",
"technologiesList": "• Flutter za višeplatformski razvoj\n• BLE (Bluetooth Low Energy) za mesh mrežu\n• OpenStreetMap za kartografiju\n• Provider za upravljanje stanjem\n• SharedPreferences za lokalno pohranu",
"technologiesList": "• Flutter za višeplatformski razvoj\n• BLE (Bluetooth Low Energy) za mesh mrežu\n• OpenStreetMap za kartografiju\n• Provider za upravljanje stanjem\n• SharedPreferences za lokalnu pohranu",
"moreInfo": "Više informacija",
"learnMoreAbout": "Saznajte više o MeshCore SAR",
"learnMoreAbout": "Saznajte više o MeshCore SAR-u",
"developer": "Programer",
@@ -175,7 +175,7 @@
"locationBroadcast": "Emitiranje lokacije: {latitude}, {longitude}",
"defaultPinInfo": "Zadani PIN za uređaje bez zaslona je 123456. Problemi s uparivanjem? Zaboravite bluetooth uređaj u postavkama sustava.",
"defaultPinInfo": "Zadani PIN za uređaje bez zaslona je 123456. Problemi s uparivanjem? Zaboravite Bluetooth uređaj u postavkama sustava.",
"noMessagesYet": "Još nema poruka",
@@ -217,7 +217,7 @@
"deleteDrawing": "Izbriši crtež",
"drawingTools": "Alati za crtanje",
"drawLine": "Nacrtaj liniju",
@@ -433,7 +433,7 @@
"accuracy": "Točnost",
"distance": "Udaljenost",
"bearing": "Azimut",
@@ -467,7 +467,7 @@
"degreesLeft": "{degrees}° lijevo",
"latLonFormat": "Ššir: {latitude} Dužina: {longitude}",
"latLonFormat": "Šir: {latitude} Duž: {longitude}",
"noContactsYet": "Još nema kontakata",
@@ -487,7 +487,7 @@
"cacheSize": "Veličina predmemorije",
"storeName": "Ime pohrane",
"storeName": "Naziv spremišta",
"noCacheStatistics": "Statistika predmemorije nije dostupna",
@@ -571,7 +571,7 @@
"resetMapRotationTooltip": "Vrati kartu na sjever",
"showMapDebugInfo": "Prikaži debug informacije karte",
"showMapDebugInfo": "Prikaži informacije za otklanjanje pogrešaka karte",
"displayZoomLevelBounds": "Prikaži razinu zumiranja i granice",
@@ -585,7 +585,7 @@
"esriSatellite": "ESRI satelit",
"googleHybrid": "Google hibridno",
"googleHybrid": "Google hibridna karta",
"googleRoadmap": "Google cestovna karta",
@@ -769,8 +769,6 @@
"trailHiddenRecording": "Putanja je skrivena (još se snima)",
"distance": "Udaljenost",
"duration": "Trajanje",
"points": "Točke",
@@ -849,7 +847,7 @@
"lat": "Šir",
"lon": "Dug",
"lon": "Duž",
"useCurrentLocation": "Koristi trenutnu lokaciju",
@@ -879,28 +877,27 @@
"templateEmoji": "Emoji",
"emojiRequired": "Emoji je obavezan",
"nameRequired": "Ime je obavezno",
"templateDescription": "Description (Optional)",
"templateDescriptionHint": "Add additional context...",
"templateColor": "Color",
"previewFormat": "Preview (SAR Message Format)",
"templateDescription": "Opis (neobavezno)",
"templateDescriptionHint": "Dodajte dodatni kontekst...",
"templateColor": "Boja",
"previewFormat": "Pregled (format SAR poruke)",
"importFromClipboard": "Uvezi",
"exportToClipboard": "Izvezi",
"deleteTemplateConfirmation": "Delete template '{name}'?",
"templateAdded": "Template added",
"templateUpdated": "Template updated",
"templateDeleted": "Template deleted",
"templatesImported": "{count, plural, =0{No templates imported} =1{Imported 1 template} other{Imported {count} templates}}",
"templatesExported": "{count, plural, =1{Exported 1 template to clipboard} other{Exported {count} templates to clipboard}}",
"importFailed": "Import failed: {error}",
"exportFailed": "Export failed: {error}",
"deleteTemplateConfirmation": "Izbrisati predložak '{name}'?",
"templateAdded": "Predložak dodan",
"templateUpdated": "Predložak ažuriran",
"templateDeleted": "Predložak izbrisan",
"templatesImported": "{count, plural, =0{Nema uvezenih predložaka} =1{Uvezen 1 predložak} other{Uvezeno {count} predložaka}}",
"templatesExported": "{count, plural, =1{Izvezen 1 predložak u međuspremnik} other{Izvezeno {count} predložaka u međuspremnik}}",
"importFailed": "Uvoz nije uspio: {error}",
"exportFailed": "Izvoz nije uspio: {error}",
"resetToDefaults": "Vrati na zadane",
"resetToDefaultsConfirmation": "Ovo će izbrisati sve prilagođene predloške i vratiti 4 zadana predloška. Nastaviti?",
"reset": "Vrati",
"resetComplete": "Predlošci vraćeni na zadane",
"noTemplates": "No templates available",
"tapAddToCreate": "Tap + to create your first template",
"noTemplates": "Nema dostupnih predložaka",
"tapAddToCreate": "Dodirnite + za izradu prvog predloška",
"ok": "OK",
"delete": "Obriši",
"permissionsSection": "Dozvole",
"locationPermission": "Dozvola za lokaciju",
@@ -918,7 +915,7 @@
"sarNavyBlueDescription": "Profesionalni/Operativni Način",
"selectRecipient": "Odaberi primatelja",
"broadcastToAllNearby": "Emituj svima u blizini",
"broadcastToAllNearby": "Emitiraj svima u blizini",
"searchRecipients": "Pretraži primatelje...",
"noContactsFound": "Nema kontakata",
"noRoomsFound": "Nema soba",
@@ -946,9 +943,9 @@
"sampleObjectVehicle": " Napušteno vozilo - provjeriti vlasnika",
"sampleObjectCamping": " Otkrivena oprema za kampiranje",
"sampleObjectTrailMarker": " Oznaka staze pronađena izvan puta",
"sampleMsgAllTeamsCheckIn": "Svi timovi se jave",
"sampleMsgAllTeamsCheckIn": "Svi timovi, javite se",
"sampleMsgWeatherUpdate": "Ažuriranje vremena: Vedro nebo, temp 18°C",
"sampleMsgBaseCamp": "Bazni kamp uspostavljen na okupljalište",
"sampleMsgBaseCamp": "Bazni kamp uspostavljen na okupljalištu",
"sampleMsgTeamAlpha": "Tim se kreće prema sektoru 2",
"sampleMsgRadioCheck": "Provjera radija - sve stanice odgovorite",
"sampleMsgWaterSupply": "Opskrba vodom dostupna na kontrolnoj točki 3",
@@ -985,33 +982,33 @@
"rectangleDrawing": "Pravokutni crtež",
"coordinatesCopiedToClipboard": "Koordinate kopirane u međuspremnik",
"drawingShared": "Crtež karte",
"drawingShared": "Crtež podijeljen",
"drawingHidden": "Crtež sakriven s karte",
"alreadyShared": "{count, plural, =1{1 već podijeljeno} other{{count} već podijeljeno}}",
"newDrawingsShared": "Podijeljeno {count} novi{plural} crtež{plural}",
"drawingTools": "Alati za crtanje",
"shareDrawing": "Podijeli Crtež",
"shareDrawing": "Podijeli crtež",
"shareWithAllNearbyDevices": "Podijeli sa svim obližnjim uređajima",
"shareToRoom": "Podijeli u Sobu",
"sendToPersistentStorage": "Pošalji u trajnu pohranu sobe",
"deleteDrawingConfirm": "Jeste li sigurni da želite izbrisati ovaj crtež?",
"drawingDeleted": "Crtež izbrisan",
"yourDrawingsCount": "Vaši Crteži ({count})",
"yourDrawingsCount": "Vaši crteži ({count})",
"shared": "Podijeljeno",
"line": "Linija",
"rectangle": "Pravokutnik",
"updateAvailable": "Dostupno Ažuriranje",
"currentVersion": "Trenutna",
"latestVersion": "Najnovija",
"downloadUpdate": "Preuzmi",
"updateAvailable": "Dostupno ažuriranje",
"currentVersion": "Trenutna verzija",
"latestVersion": "Najnovija verzija",
"downloadUpdate": "Preuzmi ažuriranje",
"updateLater": "Kasnije",
"cadastralParcels": "Katastarske čestice",
"forestRoads": "Šumske ceste",
"showCadastralParcels": "Prikaži katastarske čestice",
"showForestRoads": "Prikaži šumske ceste",
"wmsOverlays": "WMS Prekrivanja",
"wmsOverlays": "WMS prekrivanja",
"recentMessages": "Nedavne poruke",
@@ -1036,31 +1033,31 @@
"wizardNext": "Dalje",
"wizardGetStarted": "Započni",
"wizardWelcomeTitle": "Dobrodošli u MeshCore SAR",
"wizardWelcomeDescription": "Moćan alat za komunikaciju bez mreže za spasilačke operacije. Povežite se sa svojim timom koristeći mesh radio tehnologiju kada tradicionalne mreže nisu dostupne.",
"wizardConnectingTitle": "Povezivanje s Radiom",
"wizardConnectingDescription": "Povežite svoj pametni telefon s MeshCore radio uređajem putem Bluetootha kako biste započeli komunikaciju bez mreže.",
"wizardConnectingFeature1": "Skeniraj obližnje MeshCore uređaje",
"wizardConnectingFeature2": "Uparite se s vašim radiom putem Bluetootha",
"wizardConnectingFeature3": "Radi potpuno offline - internet nije potreban",
"wizardSimpleModeTitle": "Jednostavan Način",
"wizardSimpleModeDescription": "Novi u mesh umrežavanju? Omogućite jednostavan način za pojednostavljeno sučelje samo s bitnim funkcijama.",
"wizardSimpleModeFeature1": "Sučelje prijateljsko početnicima s osnovnim funkcijama",
"wizardSimpleModeFeature2": "Prebacite na Napredni način bilo kada u Postavkama",
"wizardWelcomeDescription": "Moćan alat za komunikaciju izvan mreže za spasilačke operacije. Povežite se s timom uz mesh radijsku tehnologiju kada tradicionalne mreže nisu dostupne.",
"wizardConnectingTitle": "Povezivanje s radiom",
"wizardConnectingDescription": "Povežite telefon s MeshCore radijskim uređajem putem Bluetootha i započnite komunikaciju izvan mreže.",
"wizardConnectingFeature1": "Skenira obližnje MeshCore uređaje",
"wizardConnectingFeature2": "Uparivanje s radijem putem Bluetootha",
"wizardConnectingFeature3": "Radi potpuno izvan mreže internet nije potreban",
"wizardSimpleModeTitle": "Jednostavan način",
"wizardSimpleModeDescription": "Prvi put koristite mesh mrežu? Uključite jednostavan način za pojednostavljeno sučelje s osnovnim funkcijama.",
"wizardSimpleModeFeature1": "Sučelje prilagođeno početnicima s osnovnim funkcijama",
"wizardSimpleModeFeature2": "U svakom trenutku prebacite na napredni način u Postavkama",
"wizardChannelTitle": "Kanali",
"wizardChannelDescription": "Emitirajte poruke svima na kanalu, savršeno za objave i koordinaciju cijele ekipe.",
"wizardChannelDescription": "Šaljite poruke svima na kanalu — idealno za obavijesti i koordinaciju tima.",
"wizardChannelFeature1": "Javni kanal za opću komunikaciju ekipe",
"wizardChannelFeature2": "Stvorite prilagođene kanale za specifične grupe",
"wizardChannelFeature3": "Poruke se automatski prosljeđuju putem mreže",
"wizardContactsTitle": "Kontakti",
"wizardContactsDescription": "Vaši članovi tima se automatski pojavljuju kada se pridruže mesh mreži. Pošaljite im direktne poruke ili pogledajte njihovu lokaciju.",
"wizardContactsDescription": "Članovi tima se prikazuju automatski kada se pridruže mesh mreži. Šaljite im izravne poruke ili pogledajte njihovu lokaciju.",
"wizardContactsFeature1": "Kontakti se automatski otkrivaju",
"wizardContactsFeature2": "Šaljite privatne direktne poruke",
"wizardContactsFeature3": "Pogledajte razinu baterije i vrijeme zadnjeg viđenja",
"wizardMapTitle": "Karta & Lokacija",
"wizardMapDescription": "Pratite svoj tim u stvarnom vremenu i označite važne lokacije za spasilačke operacije.",
"wizardMapFeature1": "SAR oznake za pronađene osobe, požare i područja postavljanja",
"wizardContactsFeature3": "Prikažite stanje baterije i vrijeme zadnje aktivnosti",
"wizardMapTitle": "Karta i lokacija",
"wizardMapDescription": "Pratite tim u stvarnom vremenu i označavajte ključne lokacije za spasilačke operacije.",
"wizardMapFeature1": "SAR oznake za pronađene osobe, požare i točke okupljanja",
"wizardMapFeature2": "GPS praćenje članova tima u stvarnom vremenu",
"wizardMapFeature3": "Preuzmite offline karte za udaljena područja",
"wizardMapFeature3": "Preuzmite karte za rad izvan mreže",
"wizardMapFeature4": "Crtajte oblike i dijelite taktičke informacije",
"viewWelcomeTutorial": "Pogledaj uputu dobrodošlice"
}

View File

@@ -120,7 +120,7 @@ class AppLocalizationsHr extends AppLocalizations {
String get autoSystem => 'Automatski (Sustav)';
@override
String get followSystemTheme => 'Slijedi sistemsku temu';
String get followSystemTheme => 'Slijedi temu sustava';
@override
String get showRxTxIndicators => 'Prikaži RX/TX indikatore';
@@ -240,13 +240,13 @@ class AppLocalizationsHr extends AppLocalizations {
@override
String get technologiesList =>
'• Flutter za višeplatformski razvoj\n• BLE (Bluetooth Low Energy) za mesh mrežu\n• OpenStreetMap za kartografiju\n• Provider za upravljanje stanjem\n• SharedPreferences za lokalno pohranu';
'• Flutter za višeplatformski razvoj\n• BLE (Bluetooth Low Energy) za mesh mrežu\n• OpenStreetMap za kartografiju\n• Provider za upravljanje stanjem\n• SharedPreferences za lokalnu pohranu';
@override
String get moreInfo => 'Više informacija';
@override
String get learnMoreAbout => 'Saznajte više o MeshCore SAR';
String get learnMoreAbout => 'Saznajte više o MeshCore SAR-u';
@override
String get developer => 'Programer';
@@ -306,7 +306,7 @@ class AppLocalizationsHr extends AppLocalizations {
@override
String get defaultPinInfo =>
'Zadani PIN za uređaje bez zaslona je 123456. Problemi s uparivanjem? Zaboravite bluetooth uređaj u postavkama sustava.';
'Zadani PIN za uređaje bez zaslona je 123456. Problemi s uparivanjem? Zaboravite Bluetooth uređaj u postavkama sustava.';
@override
String get noMessagesYet => 'Još nema poruka';
@@ -318,7 +318,7 @@ class AppLocalizationsHr extends AppLocalizations {
String get deleteContact => 'Izbriši kontakt';
@override
String get delete => 'Obriši';
String get delete => 'Izbriši';
@override
String get viewOnMap => 'Prikaži na karti';
@@ -815,7 +815,7 @@ class AppLocalizationsHr extends AppLocalizations {
@override
String latLonFormat(String latitude, String longitude) {
return 'Ššir: $latitude Dužina: $longitude';
return 'Šir: $latitude Duž: $longitude';
}
@override
@@ -847,7 +847,7 @@ class AppLocalizationsHr extends AppLocalizations {
String get cacheSize => 'Veličina predmemorije';
@override
String get storeName => 'Ime pohrane';
String get storeName => 'Naziv spremišta';
@override
String get noCacheStatistics => 'Statistika predmemorije nije dostupna';
@@ -992,7 +992,8 @@ class AppLocalizationsHr extends AppLocalizations {
String get resetMapRotationTooltip => 'Vrati kartu na sjever';
@override
String get showMapDebugInfo => 'Prikaži debug informacije karte';
String get showMapDebugInfo =>
'Prikaži informacije za otklanjanje pogrešaka karte';
@override
String get displayZoomLevelBounds => 'Prikaži razinu zumiranja i granice';
@@ -1014,7 +1015,7 @@ class AppLocalizationsHr extends AppLocalizations {
String get esriSatellite => 'ESRI satelit';
@override
String get googleHybrid => 'Google hibridno';
String get googleHybrid => 'Google hibridna karta';
@override
String get googleRoadmap => 'Google cestovna karta';
@@ -1310,12 +1311,12 @@ class AppLocalizationsHr extends AppLocalizations {
@override
String exportFailed(String error) {
return 'Export failed: $error';
return 'Izvoz nije uspio: $error';
}
@override
String importFailed(String error) {
return 'Import failed: $error';
return 'Uvoz nije uspio: $error';
}
@override
@@ -1495,7 +1496,7 @@ class AppLocalizationsHr extends AppLocalizations {
String get lat => 'Šir';
@override
String get lon => 'Dug';
String get lon => 'Duž';
@override
String get useCurrentLocation => 'Koristi trenutnu lokaciju';
@@ -1563,16 +1564,16 @@ class AppLocalizationsHr extends AppLocalizations {
String get nameRequired => 'Ime je obavezno';
@override
String get templateDescription => 'Description (Optional)';
String get templateDescription => 'Opis (neobavezno)';
@override
String get templateDescriptionHint => 'Add additional context...';
String get templateDescriptionHint => 'Dodajte dodatni kontekst...';
@override
String get templateColor => 'Color';
String get templateColor => 'Boja';
@override
String get previewFormat => 'Preview (SAR Message Format)';
String get previewFormat => 'Pregled (format SAR poruke)';
@override
String get importFromClipboard => 'Uvezi';
@@ -1582,26 +1583,26 @@ class AppLocalizationsHr extends AppLocalizations {
@override
String deleteTemplateConfirmation(String name) {
return 'Delete template \'$name\'?';
return 'Izbrisati predložak \'$name\'?';
}
@override
String get templateAdded => 'Template added';
String get templateAdded => 'Predložak dodan';
@override
String get templateUpdated => 'Template updated';
String get templateUpdated => 'Predložak ažuriran';
@override
String get templateDeleted => 'Template deleted';
String get templateDeleted => 'Predložak izbrisan';
@override
String templatesImported(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Imported $count templates',
one: 'Imported 1 template',
zero: 'No templates imported',
other: 'Uvezeno $count predložaka',
one: 'Uvezen 1 predložak',
zero: 'Nema uvezenih predložaka',
);
return '$_temp0';
}
@@ -1611,8 +1612,8 @@ class AppLocalizationsHr extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Exported $count templates to clipboard',
one: 'Exported 1 template to clipboard',
other: 'Izvezeno $count predložaka u međuspremnik',
one: 'Izvezen 1 predložak u međuspremnik',
);
return '$_temp0';
}
@@ -1631,10 +1632,10 @@ class AppLocalizationsHr extends AppLocalizations {
String get resetComplete => 'Predlošci vraćeni na zadane';
@override
String get noTemplates => 'No templates available';
String get noTemplates => 'Nema dostupnih predložaka';
@override
String get tapAddToCreate => 'Tap + to create your first template';
String get tapAddToCreate => 'Dodirnite + za izradu prvog predloška';
@override
String get ok => 'OK';
@@ -1691,7 +1692,7 @@ class AppLocalizationsHr extends AppLocalizations {
String get selectRecipient => 'Odaberi primatelja';
@override
String get broadcastToAllNearby => 'Emituj svima u blizini';
String get broadcastToAllNearby => 'Emitiraj svima u blizini';
@override
String get searchRecipients => 'Pretraži primatelje...';
@@ -1773,14 +1774,14 @@ class AppLocalizationsHr extends AppLocalizations {
String get sampleObjectTrailMarker => ' Oznaka staze pronađena izvan puta';
@override
String get sampleMsgAllTeamsCheckIn => 'Svi timovi se jave';
String get sampleMsgAllTeamsCheckIn => 'Svi timovi, javite se';
@override
String get sampleMsgWeatherUpdate =>
'Ažuriranje vremena: Vedro nebo, temp 18°C';
@override
String get sampleMsgBaseCamp => 'Bazni kamp uspostavljen na okupljalište';
String get sampleMsgBaseCamp => 'Bazni kamp uspostavljen na okupljalištu';
@override
String get sampleMsgTeamAlpha => 'Tim se kreće prema sektoru 2';
@@ -1893,7 +1894,7 @@ class AppLocalizationsHr extends AppLocalizations {
'Koordinate kopirane u međuspremnik';
@override
String get drawingShared => 'Crtež karte';
String get drawingShared => 'Crtež podijeljen';
@override
String get drawingHidden => 'Crtež sakriven s karte';
@@ -1915,7 +1916,7 @@ class AppLocalizationsHr extends AppLocalizations {
}
@override
String get shareDrawing => 'Podijeli Crtež';
String get shareDrawing => 'Podijeli crtež';
@override
String get shareWithAllNearbyDevices =>
@@ -1936,7 +1937,7 @@ class AppLocalizationsHr extends AppLocalizations {
@override
String yourDrawingsCount(int count) {
return 'Vaši Crteži ($count)';
return 'Vaši crteži ($count)';
}
@override
@@ -1949,16 +1950,16 @@ class AppLocalizationsHr extends AppLocalizations {
String get rectangle => 'Pravokutnik';
@override
String get updateAvailable => 'Dostupno Ažuriranje';
String get updateAvailable => 'Dostupno ažuriranje';
@override
String get currentVersion => 'Trenutna';
String get currentVersion => 'Trenutna verzija';
@override
String get latestVersion => 'Najnovija';
String get latestVersion => 'Najnovija verzija';
@override
String get downloadUpdate => 'Preuzmi';
String get downloadUpdate => 'Preuzmi ažuriranje';
@override
String get updateLater => 'Kasnije';
@@ -1976,7 +1977,7 @@ class AppLocalizationsHr extends AppLocalizations {
String get showForestRoads => 'Prikaži šumske ceste';
@override
String get wmsOverlays => 'WMS Prekrivanja';
String get wmsOverlays => 'WMS prekrivanja';
@override
String get recentMessages => 'Nedavne poruke';
@@ -2048,47 +2049,47 @@ class AppLocalizationsHr extends AppLocalizations {
@override
String get wizardWelcomeDescription =>
'Moćan alat za komunikaciju bez mreže za spasilačke operacije. Povežite se sa svojim timom koristeći mesh radio tehnologiju kada tradicionalne mreže nisu dostupne.';
'Moćan alat za komunikaciju izvan mreže za spasilačke operacije. Povežite se s timom uz mesh radijsku tehnologiju kada tradicionalne mreže nisu dostupne.';
@override
String get wizardConnectingTitle => 'Povezivanje s Radiom';
String get wizardConnectingTitle => 'Povezivanje s radiom';
@override
String get wizardConnectingDescription =>
'Povežite svoj pametni telefon s MeshCore radio uređajem putem Bluetootha kako biste započeli komunikaciju bez mreže.';
'Povežite telefon s MeshCore radijskim uređajem putem Bluetootha i započnite komunikaciju izvan mreže.';
@override
String get wizardConnectingFeature1 => 'Skeniraj obližnje MeshCore uređaje';
String get wizardConnectingFeature1 => 'Skenira obližnje MeshCore uređaje';
@override
String get wizardConnectingFeature2 =>
'Uparite se s vašim radiom putem Bluetootha';
'Uparivanje s radijem putem Bluetootha';
@override
String get wizardConnectingFeature3 =>
'Radi potpuno offline - internet nije potreban';
'Radi potpuno izvan mreže internet nije potreban';
@override
String get wizardSimpleModeTitle => 'Jednostavan Način';
String get wizardSimpleModeTitle => 'Jednostavan način';
@override
String get wizardSimpleModeDescription =>
'Novi u mesh umrežavanju? Omogućite jednostavan način za pojednostavljeno sučelje samo s bitnim funkcijama.';
'Prvi put koristite mesh mrežu? Uključite jednostavan način za pojednostavljeno sučelje s osnovnim funkcijama.';
@override
String get wizardSimpleModeFeature1 =>
'Sučelje prijateljsko početnicima s osnovnim funkcijama';
'Sučelje prilagođeno početnicima s osnovnim funkcijama';
@override
String get wizardSimpleModeFeature2 =>
'Prebacite na Napredni način bilo kada u Postavkama';
'U svakom trenutku prebacite na napredni način u Postavkama';
@override
String get wizardChannelTitle => 'Kanali';
@override
String get wizardChannelDescription =>
'Emitirajte poruke svima na kanalu, savršeno za objave i koordinaciju cijele ekipe.';
'Šaljite poruke svima na kanalu — idealno za obavijesti i koordinaciju tima.';
@override
String get wizardChannelFeature1 => 'Javni kanal za opću komunikaciju ekipe';
@@ -2106,7 +2107,7 @@ class AppLocalizationsHr extends AppLocalizations {
@override
String get wizardContactsDescription =>
'Vaši članovi tima se automatski pojavljuju kada se pridruže mesh mreži. Pošaljite im direktne poruke ili pogledajte njihovu lokaciju.';
'Članovi tima se prikazuju automatski kada se pridruže mesh mreži. Šaljite im izravne poruke ili pogledajte njihovu lokaciju.';
@override
String get wizardContactsFeature1 => 'Kontakti se automatski otkrivaju';
@@ -2116,26 +2117,25 @@ class AppLocalizationsHr extends AppLocalizations {
@override
String get wizardContactsFeature3 =>
'Pogledajte razinu baterije i vrijeme zadnjeg viđenja';
'Prikažite stanje baterije i vrijeme zadnje aktivnosti';
@override
String get wizardMapTitle => 'Karta & Lokacija';
String get wizardMapTitle => 'Karta i lokacija';
@override
String get wizardMapDescription =>
'Pratite svoj tim u stvarnom vremenu i označite važne lokacije za spasilačke operacije.';
'Pratite tim u stvarnom vremenu i označavajte ključne lokacije za spasilačke operacije.';
@override
String get wizardMapFeature1 =>
'SAR oznake za pronađene osobe, požare i područja postavljanja';
'SAR oznake za pronađene osobe, požare i točke okupljanja';
@override
String get wizardMapFeature2 =>
'GPS praćenje članova tima u stvarnom vremenu';
@override
String get wizardMapFeature3 =>
'Preuzmite offline karte za udaljena područja';
String get wizardMapFeature3 => 'Preuzmite karte za rad izvan mreže';
@override
String get wizardMapFeature4 =>

View File

@@ -306,7 +306,7 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String get defaultPinInfo =>
'Privzeta PIN koda za naprave brez zaslona je 123456. Težave s seznanitvijo? Pozabite napravo bluetooth v sistemskih nastavitvah.';
'Privzeta PIN koda za naprave brez zaslona je 123456. Težave s seznanitvijo? Pozabite napravo Bluetooth v sistemskih nastavitvah.';
@override
String get noMessagesYet => 'Še ni sporočil';
@@ -829,7 +829,7 @@ class AppLocalizationsSl extends AppLocalizations {
String get teamMembers => 'Člani ekipe';
@override
String get repeaters => 'Repetitorji';
String get repeaters => 'Ponavljalniki';
@override
String get rooms => 'Sobe';
@@ -992,7 +992,8 @@ class AppLocalizationsSl extends AppLocalizations {
String get resetMapRotationTooltip => 'Ponastavi zemljevid na sever';
@override
String get showMapDebugInfo => 'Prikaži debug informacije zemljevida';
String get showMapDebugInfo =>
'Prikaži informacije za razhroščevanje zemljevida';
@override
String get displayZoomLevelBounds => 'Prikaži nivo povečave in meje';
@@ -1014,7 +1015,7 @@ class AppLocalizationsSl extends AppLocalizations {
String get esriSatellite => 'ESRI satelit';
@override
String get googleHybrid => 'Google hibridno';
String get googleHybrid => 'Google hibridni zemljevid';
@override
String get googleRoadmap => 'Google cestni zemljevid';
@@ -1313,12 +1314,12 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String exportFailed(String error) {
return 'Export failed: $error';
return 'Izvoz ni uspel: $error';
}
@override
String importFailed(String error) {
return 'Import failed: $error';
return 'Uvoz ni uspel: $error';
}
@override
@@ -1497,7 +1498,7 @@ class AppLocalizationsSl extends AppLocalizations {
String get lat => 'Šir';
@override
String get lon => 'Dol';
String get lon => 'Dolž';
@override
String get useCurrentLocation => 'Uporabi trenutno lokacijo';
@@ -1509,7 +1510,7 @@ class AppLocalizationsSl extends AppLocalizations {
String get chatNode => 'Vozlišče za klepet';
@override
String get repeater => 'Ojačevalnik';
String get repeater => 'Ponavljalnik';
@override
String get roomChannel => 'Soba/Kanal';
@@ -1565,16 +1566,16 @@ class AppLocalizationsSl extends AppLocalizations {
String get nameRequired => 'Ime je obvezno';
@override
String get templateDescription => 'Description (Optional)';
String get templateDescription => 'Opis (neobvezno)';
@override
String get templateDescriptionHint => 'Add additional context...';
String get templateDescriptionHint => 'Dodajte dodatni kontekst...';
@override
String get templateColor => 'Color';
String get templateColor => 'Barva';
@override
String get previewFormat => 'Preview (SAR Message Format)';
String get previewFormat => 'Predogled (oblika SAR sporočila)';
@override
String get importFromClipboard => 'Uvozi';
@@ -1584,26 +1585,26 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String deleteTemplateConfirmation(String name) {
return 'Delete template \'$name\'?';
return 'Izbrišem predlogo \'$name\'?';
}
@override
String get templateAdded => 'Template added';
String get templateAdded => 'Predloga dodana';
@override
String get templateUpdated => 'Template updated';
String get templateUpdated => 'Predloga posodobljena';
@override
String get templateDeleted => 'Template deleted';
String get templateDeleted => 'Predloga izbrisana';
@override
String templatesImported(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Imported $count templates',
one: 'Imported 1 template',
zero: 'No templates imported',
other: 'Uvoženih $count predlog',
one: 'Uvožena 1 predloga',
zero: 'Ni uvoženih predlog',
);
return '$_temp0';
}
@@ -1613,8 +1614,8 @@ class AppLocalizationsSl extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Exported $count templates to clipboard',
one: 'Exported 1 template to clipboard',
other: 'Izvoženih $count predlog v odložišče',
one: 'Izvožena 1 predloga v odložišče',
);
return '$_temp0';
}
@@ -1633,10 +1634,10 @@ class AppLocalizationsSl extends AppLocalizations {
String get resetComplete => 'Predloge ponastavljene na privzeto';
@override
String get noTemplates => 'No templates available';
String get noTemplates => 'Ni razpoložljivih predlog';
@override
String get tapAddToCreate => 'Tap + to create your first template';
String get tapAddToCreate => 'Tapnite + za ustvarjanje prve predloge';
@override
String get ok => 'OK';
@@ -1839,7 +1840,7 @@ class AppLocalizationsSl extends AppLocalizations {
'Helikopter za evakuacijo prihod 10 minut';
@override
String get sampleMsgEmergencyResolved => 'Nujnost rešena - vse čisto';
String get sampleMsgEmergencyResolved => 'Nujna situacija rešena vse čisto';
@override
String get sampleMsgEmergencyStagingArea => ' Nujno zbirališče';
@@ -1894,7 +1895,7 @@ class AppLocalizationsSl extends AppLocalizations {
String get coordinatesCopiedToClipboard => 'Koordinate kopirane v odložišče';
@override
String get drawingShared => 'Risba zemljevida';
String get drawingShared => 'Risba deljena';
@override
String get drawingHidden => 'Risba skrita z zemljevida';
@@ -1916,7 +1917,7 @@ class AppLocalizationsSl extends AppLocalizations {
}
@override
String get shareDrawing => 'Deli Risbo';
String get shareDrawing => 'Deli risbo';
@override
String get shareWithAllNearbyDevices => 'Deli z vsemi bližnjimi napravami';
@@ -1936,7 +1937,7 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String yourDrawingsCount(int count) {
return 'Vaše Risbe ($count)';
return 'Vaše risbe ($count)';
}
@override
@@ -1949,16 +1950,16 @@ class AppLocalizationsSl extends AppLocalizations {
String get rectangle => 'Pravokotnik';
@override
String get updateAvailable => 'Na Voljo Posodobitev';
String get updateAvailable => 'Na voljo je posodobitev';
@override
String get currentVersion => 'Trenutna';
String get currentVersion => 'Trenutna različica';
@override
String get latestVersion => 'Najnovejša';
String get latestVersion => 'Najnovejša različica';
@override
String get downloadUpdate => 'Prenesi';
String get downloadUpdate => 'Prenesi posodobitev';
@override
String get updateLater => 'Kasneje';
@@ -1976,7 +1977,7 @@ class AppLocalizationsSl extends AppLocalizations {
String get showForestRoads => 'Prikaži gozdne ceste';
@override
String get wmsOverlays => 'WMS Prekrivanja';
String get wmsOverlays => 'WMS prekrivanja';
@override
String get recentMessages => 'Nedavna sporočila';
@@ -2047,47 +2048,47 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String get wizardWelcomeDescription =>
'Močno orodje za komunikacijo brez omrežja za reševalne operacije. Povežite se s svojo ekipo z uporabo mrežne radijske tehnologije, ko tradicionalna omrežja niso na voljo.';
'Zmogljivo orodje za komunikacijo brez omrežja za reševalne operacije. S svojo ekipo se povežite z mesh radijsko tehnologijo, ko tradicionalna omrežja niso na voljo.';
@override
String get wizardConnectingTitle => 'Povezava z Radijem';
String get wizardConnectingTitle => 'Povezava z radijem';
@override
String get wizardConnectingDescription =>
'Povežite svoj pametni telefon z napravo MeshCore radio preko Bluetootha za začetek komunikacije brez omrežja.';
'Svoj telefon povežite z radijsko napravo MeshCore prek Bluetootha in začnite komunicirati brez omrežja.';
@override
String get wizardConnectingFeature1 => 'Iskanje bližnjih naprav MeshCore';
String get wizardConnectingFeature1 => 'Poišče bližnje naprave MeshCore';
@override
String get wizardConnectingFeature2 =>
'Združite s svojim radijem preko Bluetootha';
'Seznanitev z radijsko napravo prek Bluetootha';
@override
String get wizardConnectingFeature3 =>
'Deluje popolnoma brez povezave - internet ni potreben';
'Deluje povsem brez povezave internet ni potreben';
@override
String get wizardSimpleModeTitle => 'Preprost Način';
String get wizardSimpleModeTitle => 'Preprost način';
@override
String get wizardSimpleModeDescription =>
'Novi v mrežnem omrežju? Omogočite preprost način za poenostavljen vmesnik z le bistvenimi funkcijami.';
'Ste novi v mesh omrežju? Vključite preprost način za poenostavljen vmesnik z osnovnimi funkcijami.';
@override
String get wizardSimpleModeFeature1 =>
'Vmesnik prijazen za začetnike z osnovnimi funkcijami';
'Vmesnik, prilagojen začetnikom, z osnovnimi funkcijami';
@override
String get wizardSimpleModeFeature2 =>
'Kadarkoli preklopite na napredni način v nastavitvah';
'Na napredni način lahko kadarkoli preklopite v nastavitvah';
@override
String get wizardChannelTitle => 'Kanali';
@override
String get wizardChannelDescription =>
'Oddajajte sporočila vsem na kanalu, popolno za objave in usklajevanje cele ekipe.';
'Pošiljajte sporočila vsem na kanalu — idealno za obvestila in koordinacijo ekipe.';
@override
String get wizardChannelFeature1 =>
@@ -2099,47 +2100,47 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String get wizardChannelFeature3 =>
'Sporočila so samodejno posredovana preko mreže';
'Sporočila se samodejno posredujejo prek mreže';
@override
String get wizardContactsTitle => 'Stiki';
@override
String get wizardContactsDescription =>
'Vaši člani ekipe se samodejno prikažejo, ko se pridružijo mrežnemu omrežju. Pošljite jim neposredna sporočila ali si oglejte njihovo lokacijo.';
'Člani ekipe se prikažejo samodejno, ko se pridružijo mesh omrežju. Pošiljajte jim neposredna sporočila ali si oglejte njihovo lokacijo.';
@override
String get wizardContactsFeature1 => 'Stiki se odkrijejo samodejno';
String get wizardContactsFeature1 => 'Samodejno odkrivanje stikov';
@override
String get wizardContactsFeature2 =>
'Pošiljajte zasebna neposredna sporočila';
'Pošiljanje zasebnih neposrednih sporočil';
@override
String get wizardContactsFeature3 =>
'Oglejte si raven baterije in čas zadnjega ogleda';
'Prikaz stanja baterije in časa zadnje aktivnosti';
@override
String get wizardMapTitle => 'Zemljevid & Lokacija';
String get wizardMapTitle => 'Zemljevid in lokacija';
@override
String get wizardMapDescription =>
'Sledite svoji ekipi v realnem času in označite pomembne lokacije za reševalne operacije.';
'Spremljajte svojo ekipo v realnem času in označujte ključne lokacije za reševalne operacije.';
@override
String get wizardMapFeature1 =>
'SAR označevalci za najdene osebe, požare in območja postavitve';
'SAR označevalci za najdene osebe, požare in zbirna mesta';
@override
String get wizardMapFeature2 => 'Sledenje GPS članov ekipe v realnem času';
String get wizardMapFeature2 => 'Sledenje članom ekipe z GPS v realnem času';
@override
String get wizardMapFeature3 =>
'Prenesite zemljevide brez povezave za oddaljene kraje';
'Prenesite zemljevide za uporabo brez povezave';
@override
String get wizardMapFeature4 =>
'Narišite oblike in delite taktične informacije';
'Rišite oblike in delite taktične informacije';
@override
String get viewWelcomeTutorial => 'Ogled vadnice dobrodošlice';

View File

@@ -175,7 +175,7 @@
"locationBroadcast": "Oddajanje lokacije: {latitude}, {longitude}",
"defaultPinInfo": "Privzeta PIN koda za naprave brez zaslona je 123456. Težave s seznanitvijo? Pozabite napravo bluetooth v sistemskih nastavitvah.",
"defaultPinInfo": "Privzeta PIN koda za naprave brez zaslona je 123456. Težave s seznanitvijo? Pozabite napravo Bluetooth v sistemskih nastavitvah.",
"noMessagesYet": "Še ni sporočil",
@@ -185,6 +185,8 @@
"delete": "Izbriši",
"viewOnMap": "Poglej na zemljevidu",
"refresh": "Osveži",
@@ -359,7 +361,7 @@
"noGpsData": "Ni GPS podatkov",
"distance": "Razdalja",
"pingingDirect": "Pinganje {name} (neposredno preko poti)...",
@@ -435,6 +437,8 @@
"distance": "Razdalja",
"bearing": "Azimut",
"direction": "Smer",
@@ -475,7 +479,7 @@
"teamMembers": "Člani ekipe",
"repeaters": "Repetitorji",
"repeaters": "Ponavljalniki",
"rooms": "Sobe",
@@ -571,7 +575,7 @@
"resetMapRotationTooltip": "Ponastavi zemljevid na sever",
"showMapDebugInfo": "Prikaži debug informacije zemljevida",
"showMapDebugInfo": "Prikaži informacije za razhroščevanje zemljevida",
"displayZoomLevelBounds": "Prikaži nivo povečave in meje",
@@ -585,7 +589,7 @@
"esriSatellite": "ESRI satelit",
"googleHybrid": "Google hibridno",
"googleHybrid": "Google hibridni zemljevid",
"googleRoadmap": "Google cestni zemljevid",
@@ -769,8 +773,6 @@
"trailHiddenRecording": "Pot je skrita (še se snema)",
"distance": "Razdalja",
"duration": "Trajanje",
"points": "Točke",
@@ -849,7 +851,7 @@
"lat": "Šir",
"lon": "Dol",
"lon": "Dolž",
"useCurrentLocation": "Uporabi trenutno lokacijo",
@@ -857,7 +859,7 @@
"chatNode": "Vozlišče za klepet",
"repeater": "Ojačevalnik",
"repeater": "Ponavljalnik",
"roomChannel": "Soba/Kanal",
@@ -879,28 +881,27 @@
"templateEmoji": "Emoji",
"emojiRequired": "Emoji je obvezen",
"nameRequired": "Ime je obvezno",
"templateDescription": "Description (Optional)",
"templateDescriptionHint": "Add additional context...",
"templateColor": "Color",
"previewFormat": "Preview (SAR Message Format)",
"templateDescription": "Opis (neobvezno)",
"templateDescriptionHint": "Dodajte dodatni kontekst...",
"templateColor": "Barva",
"previewFormat": "Predogled (oblika SAR sporočila)",
"importFromClipboard": "Uvozi",
"exportToClipboard": "Izvozi",
"deleteTemplateConfirmation": "Delete template '{name}'?",
"templateAdded": "Template added",
"templateUpdated": "Template updated",
"templateDeleted": "Template deleted",
"templatesImported": "{count, plural, =0{No templates imported} =1{Imported 1 template} other{Imported {count} templates}}",
"templatesExported": "{count, plural, =1{Exported 1 template to clipboard} other{Exported {count} templates to clipboard}}",
"importFailed": "Import failed: {error}",
"exportFailed": "Export failed: {error}",
"deleteTemplateConfirmation": "Izbrišem predlogo '{name}'?",
"templateAdded": "Predloga dodana",
"templateUpdated": "Predloga posodobljena",
"templateDeleted": "Predloga izbrisana",
"templatesImported": "{count, plural, =0{Ni uvoženih predlog} =1{Uvožena 1 predloga} other{Uvoženih {count} predlog}}",
"templatesExported": "{count, plural, =1{Izvožena 1 predloga v odložišče} other{Izvoženih {count} predlog v odložišče}}",
"importFailed": "Uvoz ni uspel: {error}",
"exportFailed": "Izvoz ni uspel: {error}",
"resetToDefaults": "Ponastavi na privzeto",
"resetToDefaultsConfirmation": "To bo izbrisalo vse prilagojene predloge in obnovilo 4 privzete predloge. Nadaljevati?",
"reset": "Ponastavi",
"resetComplete": "Predloge ponastavljene na privzeto",
"noTemplates": "No templates available",
"tapAddToCreate": "Tap + to create your first template",
"noTemplates": "Ni razpoložljivih predlog",
"tapAddToCreate": "Tapnite + za ustvarjanje prve predloge",
"ok": "OK",
"delete": "Izbriši",
"permissionsSection": "Dovoljenja",
"locationPermission": "Dovoljenje za lokacijo",
@@ -965,7 +966,7 @@
"sampleMsgPriorityHelicopter": "PRIORITETA: Potrebna podpora helikopterja",
"sampleMsgMedicalTeamEnRoute": "Medicinska ekipa na poti do vaše lokacije",
"sampleMsgEvacHelicopter": "Helikopter za evakuacijo prihod 10 minut",
"sampleMsgEmergencyResolved": "Nujnost rešena - vse čisto",
"sampleMsgEmergencyResolved": "Nujna situacija rešena vse čisto",
"sampleMsgEmergencyStagingArea": " Nujno zbirališče",
"sampleMsgEmergencyServices": "Nujne službe obveščene in se odzivajo",
"sampleAlphaTeamLead": "Vodja Ekipe",
@@ -985,33 +986,33 @@
"rectangleDrawing": "Pravokotna risba",
"coordinatesCopiedToClipboard": "Koordinate kopirane v odložišče",
"drawingShared": "Risba zemljevida",
"drawingShared": "Risba deljena",
"drawingHidden": "Risba skrita z zemljevida",
"alreadyShared": "{count, plural, =1{1 že deljeno} other{{count} že deljeno}}",
"newDrawingsShared": "Deljeno {count} nov{plural} risb{plural}",
"drawingTools": "Orodja za risanje",
"shareDrawing": "Deli Risbo",
"shareDrawing": "Deli risbo",
"shareWithAllNearbyDevices": "Deli z vsemi bližnjimi napravami",
"shareToRoom": "Deli v Sobo",
"sendToPersistentStorage": "Pošlji v trajno shrambo sobe",
"deleteDrawingConfirm": "Ali ste prepričani, da želite izbrisati to risbo?",
"drawingDeleted": "Risba izbrisana",
"yourDrawingsCount": "Vaše Risbe ({count})",
"yourDrawingsCount": "Vaše risbe ({count})",
"shared": "Deljeno",
"line": "Črta",
"rectangle": "Pravokotnik",
"updateAvailable": "Na Voljo Posodobitev",
"currentVersion": "Trenutna",
"latestVersion": "Najnovejša",
"downloadUpdate": "Prenesi",
"updateAvailable": "Na voljo je posodobitev",
"currentVersion": "Trenutna različica",
"latestVersion": "Najnovejša različica",
"downloadUpdate": "Prenesi posodobitev",
"updateLater": "Kasneje",
"cadastralParcels": "Katastrske parcele",
"forestRoads": "Gozdne ceste",
"showCadastralParcels": "Prikaži katastrske parcele",
"showForestRoads": "Prikaži gozdne ceste",
"wmsOverlays": "WMS Prekrivanja",
"wmsOverlays": "WMS prekrivanja",
"recentMessages": "Nedavna sporočila",
@@ -1036,31 +1037,31 @@
"wizardNext": "Naprej",
"wizardGetStarted": "Začni",
"wizardWelcomeTitle": "Dobrodošli v MeshCore SAR",
"wizardWelcomeDescription": "Močno orodje za komunikacijo brez omrežja za reševalne operacije. Povežite se s svojo ekipo z uporabo mrežne radijske tehnologije, ko tradicionalna omrežja niso na voljo.",
"wizardConnectingTitle": "Povezava z Radijem",
"wizardConnectingDescription": "Povežite svoj pametni telefon z napravo MeshCore radio preko Bluetootha za začetek komunikacije brez omrežja.",
"wizardConnectingFeature1": "Iskanje bližnjih naprav MeshCore",
"wizardConnectingFeature2": "Združite s svojim radijem preko Bluetootha",
"wizardConnectingFeature3": "Deluje popolnoma brez povezave - internet ni potreben",
"wizardSimpleModeTitle": "Preprost Način",
"wizardSimpleModeDescription": "Novi v mrežnem omrežju? Omogočite preprost način za poenostavljen vmesnik z le bistvenimi funkcijami.",
"wizardSimpleModeFeature1": "Vmesnik prijazen za začetnike z osnovnimi funkcijami",
"wizardSimpleModeFeature2": "Kadarkoli preklopite na napredni način v nastavitvah",
"wizardWelcomeDescription": "Zmogljivo orodje za komunikacijo brez omrežja za reševalne operacije. S svojo ekipo se povežite z mesh radijsko tehnologijo, ko tradicionalna omrežja niso na voljo.",
"wizardConnectingTitle": "Povezava z radijem",
"wizardConnectingDescription": "Svoj telefon povežite z radijsko napravo MeshCore prek Bluetootha in začnite komunicirati brez omrežja.",
"wizardConnectingFeature1": "Poišče bližnje naprave MeshCore",
"wizardConnectingFeature2": "Seznanitev z radijsko napravo prek Bluetootha",
"wizardConnectingFeature3": "Deluje povsem brez povezave internet ni potreben",
"wizardSimpleModeTitle": "Preprost način",
"wizardSimpleModeDescription": "Ste novi v mesh omrežju? Vključite preprost način za poenostavljen vmesnik z osnovnimi funkcijami.",
"wizardSimpleModeFeature1": "Vmesnik, prilagojen začetnikom, z osnovnimi funkcijami",
"wizardSimpleModeFeature2": "Na napredni način lahko kadarkoli preklopite v nastavitvah",
"wizardChannelTitle": "Kanali",
"wizardChannelDescription": "Oddajajte sporočila vsem na kanalu, popolno za objave in usklajevanje cele ekipe.",
"wizardChannelDescription": "Pošiljajte sporočila vsem na kanalu — idealno za obvestila in koordinacijo ekipe.",
"wizardChannelFeature1": "Javni kanal za splošno komunikacijo ekipe",
"wizardChannelFeature2": "Ustvarite kanale po meri za določene skupine",
"wizardChannelFeature3": "Sporočila so samodejno posredovana preko mreže",
"wizardChannelFeature3": "Sporočila se samodejno posredujejo prek mreže",
"wizardContactsTitle": "Stiki",
"wizardContactsDescription": "Vaši člani ekipe se samodejno prikažejo, ko se pridružijo mrežnemu omrežju. Pošljite jim neposredna sporočila ali si oglejte njihovo lokacijo.",
"wizardContactsFeature1": "Stiki se odkrijejo samodejno",
"wizardContactsFeature2": "Pošiljajte zasebna neposredna sporočila",
"wizardContactsFeature3": "Oglejte si raven baterije in čas zadnjega ogleda",
"wizardMapTitle": "Zemljevid & Lokacija",
"wizardMapDescription": "Sledite svoji ekipi v realnem času in označite pomembne lokacije za reševalne operacije.",
"wizardMapFeature1": "SAR označevalci za najdene osebe, požare in območja postavitve",
"wizardMapFeature2": "Sledenje GPS članov ekipe v realnem času",
"wizardMapFeature3": "Prenesite zemljevide brez povezave za oddaljene kraje",
"wizardMapFeature4": "Narišite oblike in delite taktične informacije",
"wizardContactsDescription": "Člani ekipe se prikažejo samodejno, ko se pridružijo mesh omrežju. Pošiljajte jim neposredna sporočila ali si oglejte njihovo lokacijo.",
"wizardContactsFeature1": "Samodejno odkrivanje stikov",
"wizardContactsFeature2": "Pošiljanje zasebnih neposrednih sporočil",
"wizardContactsFeature3": "Prikaz stanja baterije in časa zadnje aktivnosti",
"wizardMapTitle": "Zemljevid in lokacija",
"wizardMapDescription": "Spremljajte svojo ekipo v realnem času in označujte ključne lokacije za reševalne operacije.",
"wizardMapFeature1": "SAR označevalci za najdene osebe, požare in zbirna mesta",
"wizardMapFeature2": "Sledenje članom ekipe z GPS v realnem času",
"wizardMapFeature3": "Prenesite zemljevide za uporabo brez povezave",
"wizardMapFeature4": "Rišite oblike in delite taktične informacije",
"viewWelcomeTutorial": "Ogled vadnice dobrodošlice"
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -1,4 +1,5 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/location_trail.dart';
@@ -27,6 +28,10 @@ class MapProvider with ChangeNotifier {
// Imported trail (from GPX)
LocationTrail? _importedTrail;
// Download area selection
bool _isSelectingDownloadArea = false;
LatLngBounds? _downloadAreaBounds;
LatLng? get targetLocation => _targetLocation;
double? get targetZoom => _targetZoom;
bool get shouldAnimate => _shouldAnimate;
@@ -48,6 +53,10 @@ class MapProvider with ChangeNotifier {
// Imported trail getters
LocationTrail? get importedTrail => _importedTrail;
// Download area getters
bool get isSelectingDownloadArea => _isSelectingDownloadArea;
LatLngBounds? get downloadAreaBounds => _downloadAreaBounds;
void navigateToLocation({
required LatLng location,
double zoom = 15.0,
@@ -302,4 +311,24 @@ class MapProvider with ChangeNotifier {
_isTrailVisible = true;
notifyListeners();
}
/// Enter download area selection mode with initial bounds
void enterDownloadAreaMode(LatLngBounds initialBounds) {
_isSelectingDownloadArea = true;
_downloadAreaBounds = initialBounds;
notifyListeners();
}
/// Exit download area selection mode
void exitDownloadAreaMode() {
_isSelectingDownloadArea = false;
_downloadAreaBounds = null;
notifyListeners();
}
/// Update the download area bounds (while dragging/resizing)
void updateDownloadAreaBounds(LatLngBounds bounds) {
_downloadAreaBounds = bounds;
notifyListeners();
}
}

View File

@@ -887,7 +887,44 @@ class _MapManagementScreenState extends State<MapManagementScreen> {
);
}
/// Convert zoom level to user-friendly description
String _getZoomDescription(int zoom) {
if (zoom <= 5) {
return 'Continental view (very low detail)';
} else if (zoom <= 8) {
return 'Country view (low detail)';
} else if (zoom <= 10) {
return 'Regional view (basic detail)';
} else if (zoom <= 12) {
return 'City view (moderate detail)';
} else if (zoom <= 15) {
return 'Neighborhood view (good detail)';
} else if (zoom <= 17) {
return 'Street view (high detail)';
} else {
return 'Building view (very high detail)';
}
}
Widget _buildDownloadCard() {
// Calculate current bounds for preview
LatLngBounds? previewBounds;
try {
final north = double.tryParse(_northController.text);
final south = double.tryParse(_southController.text);
final east = double.tryParse(_eastController.text);
final west = double.tryParse(_westController.text);
if (north != null && south != null && east != null && west != null) {
previewBounds = LatLngBounds(
LatLng(south, west),
LatLng(north, east),
);
}
} catch (e) {
// Invalid bounds, preview will be null
}
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
@@ -900,6 +937,80 @@ class _MapManagementScreenState extends State<MapManagementScreen> {
),
const SizedBox(height: 16),
// Preview map (if bounds are valid)
if (previewBounds != null) ...[
Container(
height: 200,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(8),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Stack(
children: [
FlutterMap(
options: MapOptions(
initialCenter: previewBounds.center,
initialZoom: 12.0,
minZoom: 1,
maxZoom: 19,
interactionOptions: const InteractionOptions(
flags: InteractiveFlag.none, // Static preview
),
onMapReady: () {
// Fit bounds after map is ready would require MapController
// For now, center on bounds center
},
),
children: [
TileLayer(
urlTemplate: _selectedLayer.urlTemplate,
userAgentPackageName: 'com.meshcore.sar',
),
// Blue rectangle showing download area
PolygonLayer(
polygons: [
Polygon(
points: [
LatLng(previewBounds.north, previewBounds.west),
LatLng(previewBounds.north, previewBounds.east),
LatLng(previewBounds.south, previewBounds.east),
LatLng(previewBounds.south, previewBounds.west),
],
color: Colors.blue.withValues(alpha: 0.2),
borderColor: Colors.blue,
borderStrokeWidth: 3.0,
),
],
),
],
),
// Label overlay
Positioned(
top: 8,
left: 8,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.6),
borderRadius: BorderRadius.circular(4),
),
child: Text(
'Download Area Preview',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.white,
),
),
),
),
],
),
),
),
const SizedBox(height: 16),
],
// Map Layer Selection
DropdownButtonFormField<MapLayer>(
initialValue: _selectedLayer,
@@ -1010,13 +1121,22 @@ class _MapManagementScreenState extends State<MapManagementScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(AppLocalizations.of(context)!.minZoom(_minZoom)),
Text(
AppLocalizations.of(context)!.minZoom(_minZoom),
style: const TextStyle(fontWeight: FontWeight.w500),
),
Text(
_getZoomDescription(_minZoom),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.grey[600],
),
),
Slider(
value: _minZoom.toDouble(),
min: 1,
max: 19,
divisions: 18,
label: '$_minZoom',
label: '$_minZoom - ${_getZoomDescription(_minZoom)}',
onChanged: _isDownloading
? null
: (value) {
@@ -1036,13 +1156,22 @@ class _MapManagementScreenState extends State<MapManagementScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(AppLocalizations.of(context)!.maxZoom(_maxZoom)),
Text(
AppLocalizations.of(context)!.maxZoom(_maxZoom),
style: const TextStyle(fontWeight: FontWeight.w500),
),
Text(
_getZoomDescription(_maxZoom),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.grey[600],
),
),
Slider(
value: _maxZoom.toDouble(),
min: 1,
max: 19,
divisions: 18,
label: '$_maxZoom',
label: '$_maxZoom - ${_getZoomDescription(_maxZoom)}',
onChanged: _isDownloading
? null
: (value) {

View File

@@ -38,6 +38,7 @@ import '../widgets/map/drawing_toolbar.dart';
import '../widgets/map/location_trail_layer.dart';
import '../widgets/map/trail_controls.dart';
import '../widgets/map/map_message_overlay.dart';
import '../widgets/map/download_area_overlay.dart';
import '../widgets/messages/sar_update_sheet.dart';
import '../l10n/app_localizations.dart';
import 'map_management_screen.dart';
@@ -708,21 +709,10 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
try {
// Get current map bounds
final bounds = _mapController.camera.visibleBounds;
final currentZoom = _mapController.camera.zoom.round();
// Navigate to Map Management screen with pre-populated data
final appProvider = context.read<AppProvider>();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MapManagementScreen(
tileCacheService: appProvider.tileCacheService,
initialLayer: _currentLayer,
initialBounds: bounds,
initialZoom: currentZoom,
),
),
);
// Enter download area selection mode (show preview overlay)
final mapProvider = context.read<MapProvider>();
mapProvider.enterDownloadAreaMode(bounds);
} catch (e) {
debugPrint('Error accessing map camera: $e');
}
@@ -1550,13 +1540,15 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
widget.onNavigateToMessages?.call();
},
),
// User location marker
// User location marker with directional pointer
if (_markerService.generateUserLocationMarker(
position: _locationService.currentPosition,
heading: _currentHeading,
context: context,
) != null)
_markerService.generateUserLocationMarker(
position: _locationService.currentPosition,
heading: _currentHeading,
context: context,
)!,
// Measurement point 1 marker
@@ -1745,6 +1737,36 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
}
},
),
// Download area selection polygon (rendered on top when in selection mode)
Consumer<MapProvider>(
builder: (context, mapProvider, _) {
if (!mapProvider.isSelectingDownloadArea || mapProvider.downloadAreaBounds == null) {
return const SizedBox.shrink();
}
final bounds = mapProvider.downloadAreaBounds!;
// Add padding to the bounds so the rectangle is visible within the screen
// Calculate 5% padding on each side
final latPadding = (bounds.north - bounds.south) * 0.05;
final lonPadding = (bounds.east - bounds.west) * 0.05;
return PolygonLayer(
polygons: [
Polygon(
points: [
LatLng(bounds.north - latPadding, bounds.west + lonPadding), // Top-left
LatLng(bounds.north - latPadding, bounds.east - lonPadding), // Top-right
LatLng(bounds.south + latPadding, bounds.east - lonPadding), // Bottom-right
LatLng(bounds.south + latPadding, bounds.west + lonPadding), // Bottom-left
],
color: Colors.blue.withValues(alpha: 0.2),
borderColor: Colors.blue,
borderStrokeWidth: 3.0,
),
],
);
},
),
],
),
)
@@ -1761,6 +1783,52 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
],
),
),
// Download area overlay (shown when in download area selection mode)
Consumer<MapProvider>(
builder: (context, mapProvider, _) {
if (!mapProvider.isSelectingDownloadArea || mapProvider.downloadAreaBounds == null) {
return const SizedBox.shrink();
}
return DownloadAreaOverlay(
bounds: mapProvider.downloadAreaBounds!,
onConfirm: () {
// Navigate to map management screen with selected bounds
final bounds = mapProvider.downloadAreaBounds!;
final zoom = _mapController.camera.zoom.round();
// Find matching layer from MapLayer.allLayers to avoid instance mismatch
// Only pass initialLayer if it's in allLayers (standard layers only)
MapLayer? initialLayer;
try {
initialLayer = MapLayer.allLayers.firstWhere(
(layer) => layer.type == _currentLayer.type,
);
} catch (e) {
// Current layer not in allLayers (WMS/vector), don't pass it
initialLayer = null;
}
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => MapManagementScreen(
tileCacheService: _tileCache,
initialBounds: bounds,
initialLayer: initialLayer,
initialZoom: zoom,
),
),
);
// Exit download area selection mode
mapProvider.exitDownloadAreaMode();
},
onCancel: () {
// Exit download area selection mode
mapProvider.exitDownloadAreaMode();
},
);
},
),
// Exit fullscreen button - top left (only shown in fullscreen mode)
if (_isFullscreen)
Positioned(

View File

@@ -412,9 +412,10 @@ class LocationTrackingService {
debugPrint('✅ [LocationTracking] Initial position set without broadcast');
debugPrint(' Next broadcast allowed in ${minTimeIntervalSeconds}s');
} catch (e) {
debugPrint(' [LocationTracking] Failed to set initial position: $e');
onError?.call('Failed to set initial position: $e');
debugPrint('⚠️ [LocationTracking] Failed to set initial position: $e');
debugPrint(' Will retry on next GPS update');
// Don't mark as set on failure, so it will retry on next update
// Don't call onError - this is not critical since it will retry automatically
}
}

View File

@@ -5,6 +5,7 @@ import 'package:latlong2/latlong.dart';
import 'package:geolocator/geolocator.dart';
import '../models/contact.dart';
import '../models/sar_marker.dart';
import '../widgets/map/location_pointer.dart';
/// Centralized service for map marker management.
///
@@ -222,47 +223,31 @@ class MapMarkerService {
}).toList();
}
/// Generate user location marker.
/// Generate user location marker with directional pointer.
///
/// Parameters:
/// - [position]: Current GPS position
/// - [heading]: Current heading in degrees (0-360, where 0 = North)
/// Pass null or -1 if heading unavailable
/// - [context]: Build context for theme access
///
/// Returns null if position is unavailable.
Marker? generateUserLocationMarker({
required Position? position,
double? heading,
required BuildContext context,
}) {
if (position == null) return null;
return Marker(
point: LatLng(position.latitude, position.longitude),
width: 40,
height: 40,
rotate: false, // Don't rotate with map
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.3),
shape: BoxShape.circle,
),
child: Container(
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
shape: BoxShape.circle,
boxShadow: const [
BoxShadow(
color: Colors.black26,
blurRadius: 4,
),
],
),
child: const Icon(
Icons.my_location,
color: Colors.white,
size: 16,
),
),
width: 60,
height: 60,
rotate: false, // Don't rotate with map - we handle rotation internally
child: LocationPointer(
heading: heading,
color: Theme.of(context).colorScheme.primary,
size: 60,
),
);
}

View File

@@ -468,8 +468,9 @@ class MeshCoreBleService {
required double latitude,
required double longitude,
}) async {
// This command returns OK (0x00) response, so wait for acknowledgment
await _commandSender.writeDataAndWaitForAck(
// This command updates device's advertised location
// Fire-and-forget - no ACK needed since actual broadcast happens via sendSelfAdvert
await _commandSender.writeData(
FrameBuilder.buildSetAdvertLatLon(
latitude: latitude,
longitude: longitude,

View File

@@ -0,0 +1,108 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
/// Overlay widget that displays controls for download area selection.
/// The actual polygon should be rendered inside FlutterMap's children.
class DownloadAreaOverlay extends StatelessWidget {
final LatLngBounds bounds;
final VoidCallback onConfirm;
final VoidCallback onCancel;
const DownloadAreaOverlay({
super.key,
required this.bounds,
required this.onConfirm,
required this.onCancel,
});
@override
Widget build(BuildContext context) {
return Stack(
children: [
// Control buttons at the top
Positioned(
top: 16,
left: 16,
right: 16,
child: Card(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Download Area Selection',
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'The blue rectangle shows the area to be downloaded. '
'To change the area, tap Cancel and select download again.',
style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.center,
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: onCancel,
icon: const Icon(Icons.close),
label: const Text('Cancel'),
),
),
const SizedBox(width: 12),
Expanded(
child: FilledButton.icon(
onPressed: onConfirm,
icon: const Icon(Icons.check),
label: const Text('Confirm'),
),
),
],
),
],
),
),
),
),
// Area info at the bottom
Positioned(
bottom: 16,
left: 16,
right: 16,
child: Card(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Area Bounds',
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 4),
Text(
'N: ${bounds.north.toStringAsFixed(4)}° '
'S: ${bounds.south.toStringAsFixed(4)}°',
style: Theme.of(context).textTheme.bodySmall,
),
Text(
'E: ${bounds.east.toStringAsFixed(4)}° '
'W: ${bounds.west.toStringAsFixed(4)}°',
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
),
),
],
);
}
}

View File

@@ -0,0 +1,175 @@
import 'package:flutter/material.dart';
/// A navigation arrow pointer that indicates the user's location and direction of travel.
///
/// The pointer consists of:
/// - An outer semi-transparent circle representing GPS accuracy
/// - An inner triangular arrow pointing in the direction of travel/heading
/// - Optional rotation based on compass or GPS heading
class LocationPointer extends StatelessWidget {
/// The heading in degrees (0-360, where 0 = North, 90 = East)
/// If null or -1, the pointer will not rotate
final double? heading;
/// The primary color for the pointer
final Color color;
/// The size of the entire widget
final double size;
const LocationPointer({
super.key,
this.heading,
required this.color,
this.size = 40.0,
});
@override
Widget build(BuildContext context) {
// Determine if we have valid heading data
final hasValidHeading = heading != null && heading! >= 0;
// Calculate rotation angle (convert heading to radians)
final rotationAngle = hasValidHeading ? (heading! * 3.14159 / 180.0) : 0.0;
return SizedBox(
width: size,
height: size,
child: Stack(
alignment: Alignment.center,
children: [
// Outer accuracy circle (very subtle, light blue)
Container(
width: size * 0.6,
height: size * 0.6,
decoration: BoxDecoration(
color: const Color(0xFF4285F4).withValues(alpha: 0.2),
shape: BoxShape.circle,
),
),
// Inner rotatable arrow pointer (much larger - 90% of size)
Transform.rotate(
angle: rotationAngle,
child: CustomPaint(
size: Size(size * 0.9, size * 0.9),
painter: _NavigationPointerPainter(
color: color,
hasValidHeading: hasValidHeading,
),
),
),
],
),
);
}
}
/// Custom painter that draws a navigation arrow pointer
class _NavigationPointerPainter extends CustomPainter {
final Color color;
final bool hasValidHeading;
_NavigationPointerPainter({
required this.color,
required this.hasValidHeading,
});
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final width = size.width;
final height = size.height;
if (hasValidHeading) {
// Create navigation arrow with V-shaped cutout at bottom
final arrowPath = Path();
// Top point (sharp tip)
arrowPath.moveTo(center.dx, height * 0.08);
// Right side down to bottom right
arrowPath.lineTo(center.dx + width * 0.42, height * 0.92);
// V-cutout at bottom - right side to center
arrowPath.lineTo(center.dx, height * 0.70);
// V-cutout - center to left side
arrowPath.lineTo(center.dx - width * 0.42, height * 0.92);
// Left side back up to top
arrowPath.lineTo(center.dx, height * 0.08);
arrowPath.close();
// Draw shadow for depth
final shadowPaint = Paint()
..color = Colors.black.withValues(alpha: 0.25)
..style = PaintingStyle.fill
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4);
canvas.save();
canvas.translate(2, 2);
canvas.drawPath(arrowPath, shadowPaint);
canvas.restore();
// Left side (lighter cyan) - #75D5F0
final leftSidePath = Path();
leftSidePath.moveTo(center.dx, height * 0.08);
leftSidePath.lineTo(center.dx - width * 0.42, height * 0.92);
leftSidePath.lineTo(center.dx, height * 0.70);
leftSidePath.close();
final leftPaint = Paint()
..color = const Color(0xFF75D5F0)
..style = PaintingStyle.fill;
canvas.drawPath(leftSidePath, leftPaint);
// Right side (darker cyan) - #00B8E6
final rightSidePath = Path();
rightSidePath.moveTo(center.dx, height * 0.08);
rightSidePath.lineTo(center.dx, height * 0.70);
rightSidePath.lineTo(center.dx + width * 0.42, height * 0.92);
rightSidePath.close();
final rightPaint = Paint()
..color = const Color(0xFF00B8E6)
..style = PaintingStyle.fill;
canvas.drawPath(rightSidePath, rightPaint);
// Optional: Draw white border for contrast
final borderPaint = Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = 2.0
..strokeJoin = StrokeJoin.round;
canvas.drawPath(arrowPath, borderPaint);
} else {
// No heading available - draw a blue circle with white border
final circlePaint = Paint()
..color = const Color(0xFF4285F4)
..style = PaintingStyle.fill;
canvas.drawCircle(center, width * 0.4, circlePaint);
// White border
final borderPaint = Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = 2.5;
canvas.drawCircle(center, width * 0.4, borderPaint);
// Center white dot
final centerDot = Paint()
..color = Colors.white
..style = PaintingStyle.fill;
canvas.drawCircle(center, width * 0.15, centerDot);
}
}
@override
bool shouldRepaint(_NavigationPointerPainter oldDelegate) {
return oldDelegate.color != color ||
oldDelegate.hasValidHeading != hasValidHeading;
}
}