diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 7c775e4..36869a1 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -8,7 +8,8 @@ "Bash(dart pub global activate:*)", "Bash(dart pub global run intl_utils:generate:*)", "Bash(dart pub global deactivate:*)", - "Bash(flutter precache:*)" + "Bash(flutter precache:*)", + "Bash(flutter analyze:*)" ], "deny": [], "ask": [] diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 36e6f66..1ec2433 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -457,5 +457,225 @@ "defaultPinInfo": "The default pin for devices without a screen is 123456. Trouble pairing? Forget the bluetooth device in system settings.", "@defaultPinInfo": { "description": "Information about default PIN for pairing" + }, + + "noMessagesYet": "No messages yet", + "@noMessagesYet": { + "description": "Empty state message when there are no messages" + }, + + "pullDownToSync": "Pull down to sync messages", + "@pullDownToSync": { + "description": "Instruction to pull down to refresh messages" + }, + + "deleteContact": "Delete Contact", + "@deleteContact": { + "description": "Delete contact action label" + }, + + "delete": "Delete", + "@delete": { + "description": "Delete button label" + }, + + "viewOnMap": "View on Map", + "@viewOnMap": { + "description": "Action to view contact location on map" + }, + + "refresh": "Refresh", + "@refresh": { + "description": "Refresh button label" + }, + + "sendDirectMessage": "Send Direct Message", + "@sendDirectMessage": { + "description": "Action to send direct message to contact" + }, + + "resetPath": "Reset Path (Re-route)", + "@resetPath": { + "description": "Action to reset contact path for re-routing" + }, + + "publicKeyCopied": "Public key copied to clipboard", + "@publicKeyCopied": { + "description": "Success message when public key is copied" + }, + + "copiedToClipboard": "{label} copied to clipboard", + "@copiedToClipboard": { + "description": "Success message when a value is copied to clipboard", + "placeholders": { + "label": { + "type": "String" + } + } + }, + + "pleaseEnterPassword": "Please enter a password", + "@pleaseEnterPassword": { + "description": "Validation message for empty password field" + }, + + "failedToSyncContacts": "Failed to sync contacts: {error}", + "@failedToSyncContacts": { + "description": "Error message when contact sync fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "loggedInSuccessfully": "Logged in successfully! Waiting for room messages...", + "@loggedInSuccessfully": { + "description": "Success message after successful room login" + }, + + "loginFailed": "Login failed - incorrect password", + "@loginFailed": { + "description": "Error message when room login fails" + }, + + "loggingIn": "Logging in to {roomName}...", + "@loggingIn": { + "description": "Status message during room login process", + "placeholders": { + "roomName": { + "type": "String" + } + } + }, + + "failedToSendLogin": "Failed to send login: {error}", + "@failedToSendLogin": { + "description": "Error message when login command fails to send", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "lowLocationAccuracy": "Low Location Accuracy", + "@lowLocationAccuracy": { + "description": "Warning title for low GPS accuracy" + }, + + "continue_": "Continue", + "@continue_": { + "description": "Continue button label" + }, + + "sendSarMarker": "Send SAR marker", + "@sendSarMarker": { + "description": "Action to send SAR marker" + }, + + "deleteDrawing": "Delete Drawing", + "@deleteDrawing": { + "description": "Action to delete a map drawing" + }, + + "drawLine": "Draw Line", + "@drawLine": { + "description": "Map drawing mode: line" + }, + + "drawLineDesc": "Draw a freehand line on the map", + "@drawLineDesc": { + "description": "Description for line drawing mode" + }, + + "drawRectangle": "Draw Rectangle", + "@drawRectangle": { + "description": "Map drawing mode: rectangle" + }, + + "drawRectangleDesc": "Draw a rectangular area on the map", + "@drawRectangleDesc": { + "description": "Description for rectangle drawing mode" + }, + + "shareDrawings": "Share Drawings", + "@shareDrawings": { + "description": "Action to share drawings to network" + }, + + "clearAllDrawings": "Clear All Drawings", + "@clearAllDrawings": { + "description": "Action to clear all local drawings" + }, + + "clearAll": "Clear All", + "@clearAll": { + "description": "Clear all button label" + }, + + "noLocalDrawings": "No local drawings to share", + "@noLocalDrawings": { + "description": "Message when there are no drawings to share" + }, + + "publicChannel": "Public Channel", + "@publicChannel": { + "description": "Public channel option for sharing" + }, + + "broadcastToAll": "Broadcast to all nearby nodes (ephemeral)", + "@broadcastToAll": { + "description": "Description for public channel broadcast" + }, + + "storedPermanently": "Stored permanently in room", + "@storedPermanently": { + "description": "Description for room storage permanence" + }, + + "notConnectedToDevice": "Not connected to device", + "@notConnectedToDevice": { + "description": "Error message when device is not connected for direct messaging" + }, + + "directMessage": "Direct Message", + "@directMessage": { + "description": "Title for direct message sheet" + }, + + "directMessageSentTo": "Direct message sent to {contactName}", + "@directMessageSentTo": { + "description": "Success message after sending direct message", + "placeholders": { + "contactName": { + "type": "String" + } + } + }, + + "failedToSend": "Failed to send: {error}", + "@failedToSend": { + "description": "Error message when sending direct message fails", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "directMessageInfo": "This message will be sent directly to {contactName}. It will also appear in the main messages feed.", + "@directMessageInfo": { + "description": "Information about direct messaging behavior", + "placeholders": { + "contactName": { + "type": "String" + } + } + }, + + "typeYourMessage": "Type your message...", + "@typeYourMessage": { + "description": "Placeholder text for message input field" } } diff --git a/lib/l10n/app_hr.arb b/lib/l10n/app_hr.arb index 6a0b8ec..d822238 100644 --- a/lib/l10n/app_hr.arb +++ b/lib/l10n/app_hr.arb @@ -157,5 +157,79 @@ "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", + + "pullDownToSync": "Povucite prema dolje za sinkronizaciju", + + "deleteContact": "Izbriši kontakt", + + "delete": "Izbriši", + + "viewOnMap": "Prikaži na karti", + + "refresh": "Osvježi", + + "sendDirectMessage": "Pošalji izravnu poruku", + + "resetPath": "Resetiraj put (preusmjeri)", + + "publicKeyCopied": "Javni ključ kopiran u međuspremnik", + + "copiedToClipboard": "{label} kopirano u međuspremnik", + + "pleaseEnterPassword": "Molimo unesite lozinku", + + "failedToSyncContacts": "Neuspjela sinkronizacija kontakata: {error}", + + "loggedInSuccessfully": "Uspješno prijavljen! Čekanje na poruke sobe...", + + "loginFailed": "Prijava neuspjela - netočna lozinka", + + "loggingIn": "Prijavljivanje u {roomName}...", + + "failedToSendLogin": "Neuspjelo slanje prijave: {error}", + + "lowLocationAccuracy": "Niska točnost lokacije", + + "continue_": "Nastavi", + + "sendSarMarker": "Pošalji SAR marker", + + "deleteDrawing": "Izbriši crtež", + + "drawLine": "Nacrtaj liniju", + + "drawLineDesc": "Nacrtaj slobodnu liniju na karti", + + "drawRectangle": "Nacrtaj pravokutnik", + + "drawRectangleDesc": "Nacrtaj pravokutno područje na karti", + + "shareDrawings": "Podijeli crteže", + + "clearAllDrawings": "Očisti sve crteže", + + "clearAll": "Očisti sve", + + "noLocalDrawings": "Nema lokalnih crteža za dijeljenje", + + "publicChannel": "Javni kanal", + + "broadcastToAll": "Emitiraj svim obližnjim čvorovima (privremeno)", + + "storedPermanently": "Trajno pohranjeno u sobi", + + "notConnectedToDevice": "Nije povezano s uređajem", + + "directMessage": "Izravna poruka", + + "directMessageSentTo": "Izravna poruka poslana {contactName}", + + "failedToSend": "Neuspjelo slanje: {error}", + + "directMessageInfo": "Ova poruka će biti poslana izravno {contactName}. Također će se prikazati u glavnom feedu poruka.", + + "typeYourMessage": "Upišite svoju poruku..." } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index ae0d03a..350b7b5 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -578,6 +578,228 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'The default pin for devices without a screen is 123456. Trouble pairing? Forget the bluetooth device in system settings.'** String get defaultPinInfo; + + /// Empty state message when there are no messages + /// + /// In en, this message translates to: + /// **'No messages yet'** + String get noMessagesYet; + + /// Instruction to pull down to refresh messages + /// + /// In en, this message translates to: + /// **'Pull down to sync messages'** + String get pullDownToSync; + + /// Delete contact action label + /// + /// In en, this message translates to: + /// **'Delete Contact'** + String get deleteContact; + + /// Delete button label + /// + /// In en, this message translates to: + /// **'Delete'** + String get delete; + + /// Action to view contact location on map + /// + /// In en, this message translates to: + /// **'View on Map'** + String get viewOnMap; + + /// Refresh button label + /// + /// In en, this message translates to: + /// **'Refresh'** + String get refresh; + + /// Action to send direct message to contact + /// + /// In en, this message translates to: + /// **'Send Direct Message'** + String get sendDirectMessage; + + /// Action to reset contact path for re-routing + /// + /// In en, this message translates to: + /// **'Reset Path (Re-route)'** + String get resetPath; + + /// Success message when public key is copied + /// + /// In en, this message translates to: + /// **'Public key copied to clipboard'** + String get publicKeyCopied; + + /// Success message when a value is copied to clipboard + /// + /// In en, this message translates to: + /// **'{label} copied to clipboard'** + String copiedToClipboard(String label); + + /// Validation message for empty password field + /// + /// In en, this message translates to: + /// **'Please enter a password'** + String get pleaseEnterPassword; + + /// Error message when contact sync fails + /// + /// In en, this message translates to: + /// **'Failed to sync contacts: {error}'** + String failedToSyncContacts(String error); + + /// Success message after successful room login + /// + /// In en, this message translates to: + /// **'Logged in successfully! Waiting for room messages...'** + String get loggedInSuccessfully; + + /// Error message when room login fails + /// + /// In en, this message translates to: + /// **'Login failed - incorrect password'** + String get loginFailed; + + /// Status message during room login process + /// + /// In en, this message translates to: + /// **'Logging in to {roomName}...'** + String loggingIn(String roomName); + + /// Error message when login command fails to send + /// + /// In en, this message translates to: + /// **'Failed to send login: {error}'** + String failedToSendLogin(String error); + + /// Warning title for low GPS accuracy + /// + /// In en, this message translates to: + /// **'Low Location Accuracy'** + String get lowLocationAccuracy; + + /// Continue button label + /// + /// In en, this message translates to: + /// **'Continue'** + String get continue_; + + /// Action to send SAR marker + /// + /// In en, this message translates to: + /// **'Send SAR marker'** + String get sendSarMarker; + + /// Action to delete a map drawing + /// + /// In en, this message translates to: + /// **'Delete Drawing'** + String get deleteDrawing; + + /// Map drawing mode: line + /// + /// In en, this message translates to: + /// **'Draw Line'** + String get drawLine; + + /// Description for line drawing mode + /// + /// In en, this message translates to: + /// **'Draw a freehand line on the map'** + String get drawLineDesc; + + /// Map drawing mode: rectangle + /// + /// In en, this message translates to: + /// **'Draw Rectangle'** + String get drawRectangle; + + /// Description for rectangle drawing mode + /// + /// In en, this message translates to: + /// **'Draw a rectangular area on the map'** + String get drawRectangleDesc; + + /// Action to share drawings to network + /// + /// In en, this message translates to: + /// **'Share Drawings'** + String get shareDrawings; + + /// Action to clear all local drawings + /// + /// In en, this message translates to: + /// **'Clear All Drawings'** + String get clearAllDrawings; + + /// Clear all button label + /// + /// In en, this message translates to: + /// **'Clear All'** + String get clearAll; + + /// Message when there are no drawings to share + /// + /// In en, this message translates to: + /// **'No local drawings to share'** + String get noLocalDrawings; + + /// Public channel option for sharing + /// + /// In en, this message translates to: + /// **'Public Channel'** + String get publicChannel; + + /// Description for public channel broadcast + /// + /// In en, this message translates to: + /// **'Broadcast to all nearby nodes (ephemeral)'** + String get broadcastToAll; + + /// Description for room storage permanence + /// + /// In en, this message translates to: + /// **'Stored permanently in room'** + String get storedPermanently; + + /// Error message when device is not connected for direct messaging + /// + /// In en, this message translates to: + /// **'Not connected to device'** + String get notConnectedToDevice; + + /// Title for direct message sheet + /// + /// In en, this message translates to: + /// **'Direct Message'** + String get directMessage; + + /// Success message after sending direct message + /// + /// In en, this message translates to: + /// **'Direct message sent to {contactName}'** + String directMessageSentTo(String contactName); + + /// Error message when sending direct message fails + /// + /// In en, this message translates to: + /// **'Failed to send: {error}'** + String failedToSend(String error); + + /// Information about direct messaging behavior + /// + /// In en, this message translates to: + /// **'This message will be sent directly to {contactName}. It will also appear in the main messages feed.'** + String directMessageInfo(String contactName); + + /// Placeholder text for message input field + /// + /// In en, this message translates to: + /// **'Type your message...'** + String get typeYourMessage; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 42e78d0..9fd8396 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -278,4 +278,130 @@ class AppLocalizationsEn extends AppLocalizations { @override String get defaultPinInfo => 'The default pin for devices without a screen is 123456. Trouble pairing? Forget the bluetooth device in system settings.'; + + @override + String get noMessagesYet => 'No messages yet'; + + @override + String get pullDownToSync => 'Pull down to sync messages'; + + @override + String get deleteContact => 'Delete Contact'; + + @override + String get delete => 'Delete'; + + @override + String get viewOnMap => 'View on Map'; + + @override + String get refresh => 'Refresh'; + + @override + String get sendDirectMessage => 'Send Direct Message'; + + @override + String get resetPath => 'Reset Path (Re-route)'; + + @override + String get publicKeyCopied => 'Public key copied to clipboard'; + + @override + String copiedToClipboard(String label) { + return '$label copied to clipboard'; + } + + @override + String get pleaseEnterPassword => 'Please enter a password'; + + @override + String failedToSyncContacts(String error) { + return 'Failed to sync contacts: $error'; + } + + @override + String get loggedInSuccessfully => + 'Logged in successfully! Waiting for room messages...'; + + @override + String get loginFailed => 'Login failed - incorrect password'; + + @override + String loggingIn(String roomName) { + return 'Logging in to $roomName...'; + } + + @override + String failedToSendLogin(String error) { + return 'Failed to send login: $error'; + } + + @override + String get lowLocationAccuracy => 'Low Location Accuracy'; + + @override + String get continue_ => 'Continue'; + + @override + String get sendSarMarker => 'Send SAR marker'; + + @override + String get deleteDrawing => 'Delete Drawing'; + + @override + String get drawLine => 'Draw Line'; + + @override + String get drawLineDesc => 'Draw a freehand line on the map'; + + @override + String get drawRectangle => 'Draw Rectangle'; + + @override + String get drawRectangleDesc => 'Draw a rectangular area on the map'; + + @override + String get shareDrawings => 'Share Drawings'; + + @override + String get clearAllDrawings => 'Clear All Drawings'; + + @override + String get clearAll => 'Clear All'; + + @override + String get noLocalDrawings => 'No local drawings to share'; + + @override + String get publicChannel => 'Public Channel'; + + @override + String get broadcastToAll => 'Broadcast to all nearby nodes (ephemeral)'; + + @override + String get storedPermanently => 'Stored permanently in room'; + + @override + String get notConnectedToDevice => 'Not connected to device'; + + @override + String get directMessage => 'Direct Message'; + + @override + String directMessageSentTo(String contactName) { + return 'Direct message sent to $contactName'; + } + + @override + String failedToSend(String error) { + return 'Failed to send: $error'; + } + + @override + String directMessageInfo(String contactName) { + return 'This message will be sent directly to $contactName. It will also appear in the main messages feed.'; + } + + @override + String get typeYourMessage => 'Type your message...'; } diff --git a/lib/l10n/app_localizations_hr.dart b/lib/l10n/app_localizations_hr.dart index 5772905..3746a15 100644 --- a/lib/l10n/app_localizations_hr.dart +++ b/lib/l10n/app_localizations_hr.dart @@ -278,4 +278,130 @@ 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.'; + + @override + String get noMessagesYet => 'Još nema poruka'; + + @override + String get pullDownToSync => 'Povucite prema dolje za sinkronizaciju'; + + @override + String get deleteContact => 'Izbriši kontakt'; + + @override + String get delete => 'Izbriši'; + + @override + String get viewOnMap => 'Prikaži na karti'; + + @override + String get refresh => 'Osvježi'; + + @override + String get sendDirectMessage => 'Pošalji izravnu poruku'; + + @override + String get resetPath => 'Resetiraj put (preusmjeri)'; + + @override + String get publicKeyCopied => 'Javni ključ kopiran u međuspremnik'; + + @override + String copiedToClipboard(String label) { + return '$label kopirano u međuspremnik'; + } + + @override + String get pleaseEnterPassword => 'Molimo unesite lozinku'; + + @override + String failedToSyncContacts(String error) { + return 'Neuspjela sinkronizacija kontakata: $error'; + } + + @override + String get loggedInSuccessfully => + 'Uspješno prijavljen! Čekanje na poruke sobe...'; + + @override + String get loginFailed => 'Prijava neuspjela - netočna lozinka'; + + @override + String loggingIn(String roomName) { + return 'Prijavljivanje u $roomName...'; + } + + @override + String failedToSendLogin(String error) { + return 'Neuspjelo slanje prijave: $error'; + } + + @override + String get lowLocationAccuracy => 'Niska točnost lokacije'; + + @override + String get continue_ => 'Nastavi'; + + @override + String get sendSarMarker => 'Pošalji SAR marker'; + + @override + String get deleteDrawing => 'Izbriši crtež'; + + @override + String get drawLine => 'Nacrtaj liniju'; + + @override + String get drawLineDesc => 'Nacrtaj slobodnu liniju na karti'; + + @override + String get drawRectangle => 'Nacrtaj pravokutnik'; + + @override + String get drawRectangleDesc => 'Nacrtaj pravokutno područje na karti'; + + @override + String get shareDrawings => 'Podijeli crteže'; + + @override + String get clearAllDrawings => 'Očisti sve crteže'; + + @override + String get clearAll => 'Očisti sve'; + + @override + String get noLocalDrawings => 'Nema lokalnih crteža za dijeljenje'; + + @override + String get publicChannel => 'Javni kanal'; + + @override + String get broadcastToAll => 'Emitiraj svim obližnjim čvorovima (privremeno)'; + + @override + String get storedPermanently => 'Trajno pohranjeno u sobi'; + + @override + String get notConnectedToDevice => 'Nije povezano s uređajem'; + + @override + String get directMessage => 'Izravna poruka'; + + @override + String directMessageSentTo(String contactName) { + return 'Izravna poruka poslana $contactName'; + } + + @override + String failedToSend(String error) { + return 'Neuspjelo slanje: $error'; + } + + @override + String directMessageInfo(String contactName) { + return 'Ova poruka će biti poslana izravno $contactName. Također će se prikazati u glavnom feedu poruka.'; + } + + @override + String get typeYourMessage => 'Upišite svoju poruku...'; } diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 183ac56..844fd3f 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -278,4 +278,130 @@ 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.'; + + @override + String get noMessagesYet => 'Še ni sporočil'; + + @override + String get pullDownToSync => 'Potegnite navzdol za sinhronizacijo'; + + @override + String get deleteContact => 'Izbriši stik'; + + @override + String get delete => 'Izbriši'; + + @override + String get viewOnMap => 'Poglej na zemljevidu'; + + @override + String get refresh => 'Osveži'; + + @override + String get sendDirectMessage => 'Pošlji neposredno sporočilo'; + + @override + String get resetPath => 'Ponastavi pot (preusmeri)'; + + @override + String get publicKeyCopied => 'Javni ključ kopiran v odložišče'; + + @override + String copiedToClipboard(String label) { + return '$label kopirano v odložišče'; + } + + @override + String get pleaseEnterPassword => 'Prosimo, vnesite geslo'; + + @override + String failedToSyncContacts(String error) { + return 'Sinhronizacija stikov ni uspela: $error'; + } + + @override + String get loggedInSuccessfully => + 'Uspešno prijavljen! Čakanje na sporočila sobe...'; + + @override + String get loginFailed => 'Prijava ni uspela - nepravilno geslo'; + + @override + String loggingIn(String roomName) { + return 'Prijavljanje v $roomName...'; + } + + @override + String failedToSendLogin(String error) { + return 'Pošiljanje prijave ni uspelo: $error'; + } + + @override + String get lowLocationAccuracy => 'Nizka natančnost lokacije'; + + @override + String get continue_ => 'Nadaljuj'; + + @override + String get sendSarMarker => 'Pošlji SAR označevalec'; + + @override + String get deleteDrawing => 'Izbriši risbo'; + + @override + String get drawLine => 'Nariši črto'; + + @override + String get drawLineDesc => 'Nariši prosto črto na zemljevidu'; + + @override + String get drawRectangle => 'Nariši pravokotnik'; + + @override + String get drawRectangleDesc => 'Nariši pravokotno področje na zemljevidu'; + + @override + String get shareDrawings => 'Deli risbe'; + + @override + String get clearAllDrawings => 'Počisti vse risbe'; + + @override + String get clearAll => 'Počisti vse'; + + @override + String get noLocalDrawings => 'Ni lokalnih risb za deljenje'; + + @override + String get publicChannel => 'Javni kanal'; + + @override + String get broadcastToAll => 'Oddajaj vsem bližnjim vozliščem (začasno)'; + + @override + String get storedPermanently => 'Trajno shranjeno v sobi'; + + @override + String get notConnectedToDevice => 'Ni povezano z napravo'; + + @override + String get directMessage => 'Neposredno sporočilo'; + + @override + String directMessageSentTo(String contactName) { + return 'Neposredno sporočilo poslano $contactName'; + } + + @override + String failedToSend(String error) { + return 'Pošiljanje ni uspelo: $error'; + } + + @override + String directMessageInfo(String contactName) { + return 'To sporočilo bo poslano neposredno $contactName. Prikazalo se bo tudi v glavnem viru sporočil.'; + } + + @override + String get typeYourMessage => 'Vnesite svoje sporočilo...'; } diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index b39375c..533ea12 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -157,5 +157,79 @@ "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", + + "pullDownToSync": "Potegnite navzdol za sinhronizacijo", + + "deleteContact": "Izbriši stik", + + "delete": "Izbriši", + + "viewOnMap": "Poglej na zemljevidu", + + "refresh": "Osveži", + + "sendDirectMessage": "Pošlji neposredno sporočilo", + + "resetPath": "Ponastavi pot (preusmeri)", + + "publicKeyCopied": "Javni ključ kopiran v odložišče", + + "copiedToClipboard": "{label} kopirano v odložišče", + + "pleaseEnterPassword": "Prosimo, vnesite geslo", + + "failedToSyncContacts": "Sinhronizacija stikov ni uspela: {error}", + + "loggedInSuccessfully": "Uspešno prijavljen! Čakanje na sporočila sobe...", + + "loginFailed": "Prijava ni uspela - nepravilno geslo", + + "loggingIn": "Prijavljanje v {roomName}...", + + "failedToSendLogin": "Pošiljanje prijave ni uspelo: {error}", + + "lowLocationAccuracy": "Nizka natančnost lokacije", + + "continue_": "Nadaljuj", + + "sendSarMarker": "Pošlji SAR označevalec", + + "deleteDrawing": "Izbriši risbo", + + "drawLine": "Nariši črto", + + "drawLineDesc": "Nariši prosto črto na zemljevidu", + + "drawRectangle": "Nariši pravokotnik", + + "drawRectangleDesc": "Nariši pravokotno področje na zemljevidu", + + "shareDrawings": "Deli risbe", + + "clearAllDrawings": "Počisti vse risbe", + + "clearAll": "Počisti vse", + + "noLocalDrawings": "Ni lokalnih risb za deljenje", + + "publicChannel": "Javni kanal", + + "broadcastToAll": "Oddajaj vsem bližnjim vozliščem (začasno)", + + "storedPermanently": "Trajno shranjeno v sobi", + + "notConnectedToDevice": "Ni povezano z napravo", + + "directMessage": "Neposredno sporočilo", + + "directMessageSentTo": "Neposredno sporočilo poslano {contactName}", + + "failedToSend": "Pošiljanje ni uspelo: {error}", + + "directMessageInfo": "To sporočilo bo poslano neposredno {contactName}. Prikazalo se bo tudi v glavnem viru sporočil.", + + "typeYourMessage": "Vnesite svoje sporočilo..." } diff --git a/lib/screens/device_config_screen.dart b/lib/screens/device_config_screen.dart index 8086946..b074c43 100644 --- a/lib/screens/device_config_screen.dart +++ b/lib/screens/device_config_screen.dart @@ -4,6 +4,7 @@ import 'package:provider/provider.dart'; import 'package:geolocator/geolocator.dart'; import '../providers/connection_provider.dart'; import '../services/validation_service.dart'; +import '../l10n/app_localizations.dart'; class DeviceConfigScreen extends StatefulWidget { const DeviceConfigScreen({super.key}); @@ -174,8 +175,8 @@ class _DeviceConfigScreenState extends State { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Public info saved'), + SnackBar( + content: Text(AppLocalizations.of(context)!.save), backgroundColor: Colors.green, ), ); @@ -244,8 +245,8 @@ class _DeviceConfigScreenState extends State { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Radio settings saved'), + SnackBar( + content: Text(AppLocalizations.of(context)!.save), backgroundColor: Colors.green, ), ); @@ -268,7 +269,7 @@ class _DeviceConfigScreenState extends State { if (!serviceEnabled) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Location services disabled')), + SnackBar(content: Text(AppLocalizations.of(context)!.locationServicesDisabled)), ); } return; @@ -280,7 +281,7 @@ class _DeviceConfigScreenState extends State { if (permission == LocationPermission.denied) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Location permission denied')), + SnackBar(content: Text(AppLocalizations.of(context)!.locationPermissionDenied)), ); } return; @@ -290,7 +291,7 @@ class _DeviceConfigScreenState extends State { if (permission == LocationPermission.deniedForever) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Location permission permanently denied')), + SnackBar(content: Text(AppLocalizations.of(context)!.locationPermissionPermanentlyDenied)), ); } return; @@ -310,8 +311,11 @@ class _DeviceConfigScreenState extends State { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Location updated'), + SnackBar( + content: Text(AppLocalizations.of(context)!.locationBroadcast( + position.latitude.toStringAsFixed(6), + position.longitude.toStringAsFixed(6), + )), backgroundColor: Colors.green, ), ); @@ -331,8 +335,8 @@ class _DeviceConfigScreenState extends State { if (!connectionProvider.deviceInfo.isConnected) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Not connected to device'), + SnackBar( + content: Text(AppLocalizations.of(context)!.deviceNotConnected), backgroundColor: Colors.orange, ), ); @@ -347,9 +351,12 @@ class _DeviceConfigScreenState extends State { await connectionProvider.sendSelfAdvert(floodMode: true); if (context.mounted) { + final deviceInfo = connectionProvider.deviceInfo; + final lat = deviceInfo.advLat != null ? (deviceInfo.advLat! / 1000000).toStringAsFixed(6) : '0.0'; + final lon = deviceInfo.advLon != null ? (deviceInfo.advLon! / 1000000).toStringAsFixed(6) : '0.0'; ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Advertisement broadcast to mesh network'), + SnackBar( + content: Text(AppLocalizations.of(context)!.advertisedAtLocation(lat, lon)), backgroundColor: Colors.green, ), ); @@ -358,7 +365,7 @@ class _DeviceConfigScreenState extends State { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Failed to broadcast: $e'), + content: Text(AppLocalizations.of(context)!.failedToAdvertise(e.toString())), backgroundColor: Colors.red, ), ); @@ -377,7 +384,7 @@ class _DeviceConfigScreenState extends State { return Scaffold( appBar: AppBar( - title: const Text('Device Settings'), + title: Text(AppLocalizations.of(context)!.settings), ), body: ListView( padding: const EdgeInsets.all(16), @@ -441,13 +448,13 @@ class _DeviceConfigScreenState extends State { child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.sensors), - tooltip: 'Broadcast', + tooltip: AppLocalizations.of(context)!.settings, ), const SizedBox(width: 8), IconButton.filled( onPressed: _savePublicInfo, icon: const Icon(Icons.save), - tooltip: 'Save', + tooltip: AppLocalizations.of(context)!.save, ), ], ), @@ -555,7 +562,7 @@ class _DeviceConfigScreenState extends State { IconButton.filled( onPressed: _saveRadioSettings, icon: const Icon(Icons.save), - tooltip: 'Save', + tooltip: AppLocalizations.of(context)!.save, ), ], ), diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 4de87ff..9dea31f 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -15,6 +15,7 @@ import 'settings_screen.dart'; import 'device_config_screen.dart'; import 'packet_log_screen.dart'; import '../utils/toast_logger.dart'; +import '../l10n/app_localizations.dart'; class HomeScreen extends StatefulWidget { final Function(AppThemeMode) onThemeChanged; @@ -77,7 +78,7 @@ class _HomeScreenState extends State if (!connectionProvider.deviceInfo.isConnected) { if (context.mounted) { - ToastLogger.error(context, 'Device not connected'); + ToastLogger.error(context, AppLocalizations.of(context)!.deviceNotConnected); } return; } @@ -89,7 +90,7 @@ class _HomeScreenState extends State if (context.mounted) { ToastLogger.error( context, - 'Location services are disabled. Please enable them in Settings.', + AppLocalizations.of(context)!.locationServicesDisabled, ); } return; @@ -101,7 +102,7 @@ class _HomeScreenState extends State permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { if (context.mounted) { - ToastLogger.error(context, 'Location permission denied'); + ToastLogger.error(context, AppLocalizations.of(context)!.locationPermissionDenied); } return; } @@ -111,7 +112,7 @@ class _HomeScreenState extends State if (context.mounted) { ToastLogger.error( context, - 'Location permission permanently denied. Please enable in Settings.', + AppLocalizations.of(context)!.locationPermissionPermanentlyDenied, ); } return; @@ -129,7 +130,7 @@ class _HomeScreenState extends State } catch (e) { print('❌ Failed to get GPS position: $e'); if (context.mounted) { - ToastLogger.error(context, 'Failed to get GPS location'); + ToastLogger.error(context, AppLocalizations.of(context)!.failedToGetGpsLocation); } return; } @@ -149,13 +150,16 @@ class _HomeScreenState extends State if (context.mounted) { ToastLogger.success( context, - 'Advertised at ${position.latitude.toStringAsFixed(6)}, ${position.longitude.toStringAsFixed(6)}', + AppLocalizations.of(context)!.advertisedAtLocation( + position.latitude.toStringAsFixed(6), + position.longitude.toStringAsFixed(6), + ), ); } } catch (e) { print('❌ Failed to advertise device: $e'); if (context.mounted) { - ToastLogger.error(context, 'Failed to advertise: $e'); + ToastLogger.error(context, AppLocalizations.of(context)!.failedToAdvertise(e.toString())); } } } @@ -203,7 +207,7 @@ class _HomeScreenState extends State child: Column( children: [ Text( - 'MeshCore', + AppLocalizations.of(context)!.appTitle, style: TextStyle( color: Theme.of(context).colorScheme.onSurface, fontSize: 18, @@ -211,7 +215,7 @@ class _HomeScreenState extends State ), ), Text( - 'Scanning for devices...', + AppLocalizations.of(context)!.scanningForDevices, style: TextStyle( color: Theme.of( context, @@ -253,7 +257,7 @@ class _HomeScreenState extends State const SizedBox(width: 12), Expanded( child: Text( - 'The default pin for devices without a screen is 123456. Trouble pairing? Forget the bluetooth device in system settings.', + AppLocalizations.of(context)!.defaultPinInfo, style: TextStyle( color: Theme.of(context).colorScheme.onPrimaryContainer, fontSize: 13, @@ -288,7 +292,7 @@ class _HomeScreenState extends State ), const SizedBox(height: 16), Text( - 'No devices found', + AppLocalizations.of(context)!.noDevicesFound, style: TextStyle( color: Theme.of( context, @@ -303,7 +307,7 @@ class _HomeScreenState extends State connectionProvider.startScan(); }, icon: const Icon(Icons.refresh), - label: const Text('Scan Again'), + label: Text(AppLocalizations.of(context)!.scanAgain), ), ], ), @@ -358,7 +362,7 @@ class _HomeScreenState extends State subtitle: Row( children: [ Text( - 'Tap to connect', + AppLocalizations.of(context)!.tapToConnect, style: TextStyle( color: Theme.of( context, @@ -450,7 +454,7 @@ class _HomeScreenState extends State await provider.disconnect(); }, icon: const Icon(Icons.power_settings_new), - tooltip: 'Disconnect', + tooltip: AppLocalizations.of(context)!.disconnect, color: Colors.red.shade700, ); } @@ -461,11 +465,11 @@ class _HomeScreenState extends State icon: const Icon(Icons.more_vert), itemBuilder: (context) => [ PopupMenuItem( - child: const Row( + child: Row( children: [ - Icon(Icons.map), - SizedBox(width: 8), - Text('Map Management'), + const Icon(Icons.map), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.mapManagement), ], ), onTap: () { @@ -483,11 +487,11 @@ class _HomeScreenState extends State }, ), PopupMenuItem( - child: const Row( + child: Row( children: [ - Icon(Icons.settings), - SizedBox(width: 8), - Text('Settings'), + const Icon(Icons.settings), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.settings), ], ), onTap: () { @@ -551,16 +555,16 @@ class _HomeScreenState extends State Icons.message, unreadCount, ), - text: 'Messages', + text: AppLocalizations.of(context)!.messages, ), Tab( icon: _buildTabIconWithBadge( Icons.contacts, newContactsCount, ), - text: 'Contacts', + text: AppLocalizations.of(context)!.contacts, ), - const Tab(icon: Icon(Icons.map), text: 'Map'), + Tab(icon: const Icon(Icons.map), text: AppLocalizations.of(context)!.map), ], ), ); @@ -583,10 +587,10 @@ class _HomeScreenState extends State // Disconnected state: show connect button return Row( children: [ - const Expanded( + Expanded( child: Text( - 'MeshCore', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + AppLocalizations.of(context)!.appTitle, + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), ElevatedButton.icon( @@ -608,7 +612,7 @@ class _HomeScreenState extends State label: Text( provider.isReconnecting ? '${provider.reconnectionAttempt}/${provider.maxReconnectionAttempts}' - : 'Connect', + : AppLocalizations.of(context)!.connect, ), style: ElevatedButton.styleFrom( backgroundColor: Colors.white, @@ -624,7 +628,7 @@ class _HomeScreenState extends State IconButton( onPressed: () => provider.cancelReconnection(), icon: const Icon(Icons.close, size: 20), - tooltip: 'Cancel reconnection', + tooltip: AppLocalizations.of(context)!.cancelReconnection, style: IconButton.styleFrom( backgroundColor: Colors.red.shade700, foregroundColor: Colors.white, @@ -650,7 +654,7 @@ class _HomeScreenState extends State mainAxisSize: MainAxisSize.min, children: [ Text( - deviceInfo.selfName ?? 'MeshCore', + deviceInfo.selfName ?? AppLocalizations.of(context)!.appTitle, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), overflow: TextOverflow.ellipsis, ), @@ -838,8 +842,8 @@ class _HomeScreenState extends State Expanded( child: Text( isConnected - ? deviceInfo.displayName ?? 'Connected' - : 'Not Connected', + ? deviceInfo.displayName ?? AppLocalizations.of(context)!.connect + : AppLocalizations.of(context)!.deviceNotConnected, style: Theme.of(context).textTheme.bodyMedium, ), ), @@ -881,7 +885,7 @@ class _HomeScreenState extends State ? null : () => _showConnectionDialog(context), icon: const Icon(Icons.bluetooth_searching, size: 18), - label: const Text('Connect'), + label: Text(AppLocalizations.of(context)!.connect), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 8), ), @@ -896,7 +900,7 @@ class _HomeScreenState extends State await provider.disconnect(); }, icon: const Icon(Icons.bluetooth_disabled, size: 18), - label: const Text('Disconnect'), + label: Text(AppLocalizations.of(context)!.disconnect), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 8), ), diff --git a/lib/screens/map_management_screen.dart b/lib/screens/map_management_screen.dart index 41276ea..d181a94 100644 --- a/lib/screens/map_management_screen.dart +++ b/lib/screens/map_management_screen.dart @@ -4,6 +4,7 @@ import 'package:latlong2/latlong.dart'; import '../services/tile_cache_service.dart'; import '../services/validation_service.dart'; import '../models/map_layer.dart'; +import '../l10n/app_localizations.dart'; class MapManagementScreen extends StatefulWidget { final TileCacheService tileCacheService; @@ -217,8 +218,8 @@ class _MapManagementScreenState extends State { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Download cancelled'), + SnackBar( + content: Text(AppLocalizations.of(context)!.cancel), backgroundColor: Colors.orange, ), ); @@ -237,19 +238,19 @@ class _MapManagementScreenState extends State { final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( - title: const Text('Clear Cache'), + title: Text(AppLocalizations.of(context)!.clear), content: const Text( 'Are you sure you want to delete all downloaded maps? This action cannot be undone.', ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), - child: const Text('Cancel'), + child: Text(AppLocalizations.of(context)!.cancel), ), TextButton( onPressed: () => Navigator.pop(context, true), style: TextButton.styleFrom(foregroundColor: Colors.red), - child: const Text('Clear'), + child: Text(AppLocalizations.of(context)!.clear), ), ], ), @@ -295,7 +296,7 @@ class _MapManagementScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Map Management'), + title: Text(AppLocalizations.of(context)!.mapManagement), ), body: _isLoading ? const Center(child: CircularProgressIndicator()) diff --git a/lib/screens/map_tab.dart b/lib/screens/map_tab.dart index b7cdd65..a7e0ea7 100644 --- a/lib/screens/map_tab.dart +++ b/lib/screens/map_tab.dart @@ -28,6 +28,7 @@ import '../widgets/map/detailed_compass_dialog.dart'; import '../widgets/map/drawing_layer.dart'; import '../widgets/map/drawing_toolbar.dart'; import '../widgets/messages/sar_update_sheet.dart'; +import '../l10n/app_localizations.dart'; import 'map_management_screen.dart'; class MapTab extends StatefulWidget { @@ -643,8 +644,8 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { _saveSettings(); ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Failed to start background tracking. Check permissions and BLE connection.'), + SnackBar( + content: Text(AppLocalizations.of(context)!.failedToStartBackgroundTracking), duration: Duration(seconds: 3), ), ); @@ -710,8 +711,8 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { if (!connectionProvider.deviceInfo.isConnected) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Not connected to device'), + SnackBar( + content: Text(AppLocalizations.of(context)!.deviceNotConnected), backgroundColor: Colors.red, ), ); diff --git a/lib/screens/messages_tab.dart b/lib/screens/messages_tab.dart index 3bc78be..d54c505 100644 --- a/lib/screens/messages_tab.dart +++ b/lib/screens/messages_tab.dart @@ -12,6 +12,7 @@ import '../models/sar_marker.dart'; import '../widgets/messages/sar_update_sheet.dart'; import '../widgets/contacts/direct_message_sheet.dart'; import '../utils/toast_logger.dart'; +import '../l10n/app_localizations.dart'; class MessagesTab extends StatefulWidget { final VoidCallback onNavigateToMap; @@ -304,14 +305,14 @@ class _MessagesTabState extends State { ), const SizedBox(height: 16), Text( - 'No messages yet', + AppLocalizations.of(context)!.noMessagesYet, style: Theme.of( context, ).textTheme.titleLarge, ), const SizedBox(height: 8), Text( - 'Pull down to sync messages', + AppLocalizations.of(context)!.pullDownToSync, style: Theme.of( context, ).textTheme.bodyMedium, @@ -374,7 +375,7 @@ class _MessagesTabState extends State { // SAR quick action button IconButton( icon: const Icon(Icons.add_location_alt), - tooltip: 'Send SAR marker', + tooltip: AppLocalizations.of(context)!.sendSarMarker, onPressed: _showSarDialog, style: IconButton.styleFrom( backgroundColor: Theme.of( @@ -579,9 +580,9 @@ class _MessageBubble extends StatelessWidget { // Delete message option ListTile( leading: const Icon(Icons.delete, color: Colors.red), - title: const Text( - 'Delete message', - style: TextStyle(color: Colors.red), + title: Text( + AppLocalizations.of(context)!.delete, + style: const TextStyle(color: Colors.red), ), onTap: () { Navigator.pop(context); @@ -633,6 +634,7 @@ class _MessageBubble extends StatelessWidget { } void _showDeleteConfirmation(BuildContext context) { + final l10n = AppLocalizations.of(context)!; showDialog( context: context, builder: (context) => AlertDialog( @@ -641,7 +643,7 @@ class _MessageBubble extends StatelessWidget { actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('Cancel'), + child: Text(l10n.cancel), ), TextButton( onPressed: () { diff --git a/lib/screens/packet_log_screen.dart b/lib/screens/packet_log_screen.dart index 14b3b30..bd3a02e 100644 --- a/lib/screens/packet_log_screen.dart +++ b/lib/screens/packet_log_screen.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'package:path_provider/path_provider.dart'; import '../models/ble_packet_log.dart'; import '../services/meshcore_ble_service.dart'; +import '../l10n/app_localizations.dart'; class PacketLogScreen extends StatefulWidget { final MeshCoreBleService bleService; @@ -149,12 +150,12 @@ class _PacketLogScreenState extends State { showDialog( context: context, builder: (context) => AlertDialog( - title: const Text('Clear Packet Logs'), + title: Text(AppLocalizations.of(context)!.clearAllData), content: const Text('Are you sure you want to clear all packet logs? This cannot be undone.'), actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('Cancel'), + child: Text(AppLocalizations.of(context)!.cancel), ), TextButton( onPressed: () { @@ -166,7 +167,7 @@ class _PacketLogScreenState extends State { ); }, style: TextButton.styleFrom(foregroundColor: Colors.red), - child: const Text('Clear'), + child: Text(AppLocalizations.of(context)!.clear), ), ], ), diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index b672338..4d24918 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -11,6 +11,7 @@ import '../services/location_tracking_service.dart'; import '../services/locale_preferences.dart'; import '../utils/sample_data_generator.dart'; import '../theme/app_theme.dart'; +import '../l10n/app_localizations.dart'; class SettingsScreen extends StatefulWidget { final Function(AppThemeMode) onThemeChanged; @@ -97,7 +98,10 @@ class _SettingsScreenState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( - 'Location broadcast: ${position.latitude.toStringAsFixed(5)}, ${position.longitude.toStringAsFixed(5)}', + AppLocalizations.of(context)!.locationBroadcast( + position.latitude.toStringAsFixed(5), + position.longitude.toStringAsFixed(5), + ), ), backgroundColor: Colors.green, duration: const Duration(seconds: 2), @@ -220,7 +224,12 @@ class _SettingsScreenState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( - 'Loaded $teamCount team members, $channelCount channels, ${sarMessages.length} SAR markers, ${channelMessages.length} messages', + AppLocalizations.of(context)!.loadedSampleData( + teamCount, + channelCount, + sarMessages.length, + channelMessages.length, + ), ), backgroundColor: Colors.green, ), @@ -229,7 +238,7 @@ class _SettingsScreenState extends State { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Failed to load sample data: $e'), + content: Text(AppLocalizations.of(context)!.failedToLoadSampleData(e.toString())), backgroundColor: Colors.red, ), ); @@ -247,11 +256,11 @@ class _SettingsScreenState extends State { if (!success && mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( + SnackBar( content: Text( - 'Failed to start background tracking. Check permissions and BLE connection.', + AppLocalizations.of(context)!.failedToStartBackgroundTracking, ), - duration: Duration(seconds: 3), + duration: const Duration(seconds: 3), ), ); } @@ -272,19 +281,19 @@ class _SettingsScreenState extends State { final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( - title: const Text('Clear All Data'), - content: const Text( - 'This will clear all contacts and SAR markers. Are you sure?', + title: Text(AppLocalizations.of(context)!.clearAllDataConfirmTitle), + content: Text( + AppLocalizations.of(context)!.clearAllDataConfirmMessage, ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), - child: const Text('Cancel'), + child: Text(AppLocalizations.of(context)!.cancel), ), TextButton( onPressed: () => Navigator.pop(context, true), style: TextButton.styleFrom(foregroundColor: Colors.red), - child: const Text('Clear'), + child: Text(AppLocalizations.of(context)!.clear), ), ], ), @@ -307,8 +316,8 @@ class _SettingsScreenState extends State { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('All data cleared'), + SnackBar( + content: Text(AppLocalizations.of(context)!.allDataCleared), backgroundColor: Colors.orange, ), ); @@ -317,22 +326,22 @@ class _SettingsScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Settings')), + appBar: AppBar(title: Text(AppLocalizations.of(context)!.settings)), body: ListView( children: [ // General Settings Section - _buildSectionHeader('General'), + _buildSectionHeader(AppLocalizations.of(context)!.general), ListTile( leading: const Icon(Icons.palette), - title: const Text('Theme'), + title: Text(AppLocalizations.of(context)!.theme), subtitle: Text(AppTheme.getThemeDisplayName(_selectedTheme)), trailing: const Icon(Icons.chevron_right), onTap: () => _showThemeDialog(), ), SwitchListTile( secondary: const Icon(Icons.radar), - title: const Text('Show RX/TX Indicators'), - subtitle: const Text('Display packet activity indicators in top bar'), + title: Text(AppLocalizations.of(context)!.showRxTxIndicators), + subtitle: Text(AppLocalizations.of(context)!.displayPacketActivity), value: _showRxTxIndicators, onChanged: (value) async { setState(() { @@ -343,7 +352,7 @@ class _SettingsScreenState extends State { ), ListTile( leading: const Icon(Icons.language), - title: const Text('Language'), + title: Text(AppLocalizations.of(context)!.language), subtitle: Text(LocalePreferences.getDisplayName(_selectedLocale)), trailing: const Icon(Icons.chevron_right), onTap: () => _showLanguageDialog(), @@ -351,13 +360,13 @@ class _SettingsScreenState extends State { const Divider(), // Location Settings Section - _buildSectionHeader('Location Broadcasting'), + _buildSectionHeader(AppLocalizations.of(context)!.locationBroadcasting), // Automatic tracking settings SwitchListTile( secondary: const Icon(Icons.location_on), - title: const Text('Auto Location Tracking'), - subtitle: const Text('Automatically broadcast position updates'), + title: Text(AppLocalizations.of(context)!.autoLocationTracking), + subtitle: Text(AppLocalizations.of(context)!.automaticallyBroadcastPosition), value: _locationService.isTracking, onChanged: (value) { if (value) { @@ -371,8 +380,8 @@ class _SettingsScreenState extends State { if (_locationService.isTracking) ...[ ListTile( leading: const Icon(Icons.tune), - title: const Text('Configure Tracking'), - subtitle: const Text('Distance and time thresholds'), + title: Text(AppLocalizations.of(context)!.configureTracking), + subtitle: Text(AppLocalizations.of(context)!.distanceAndTimeThresholds), trailing: const Icon(Icons.chevron_right), onTap: () => _showTrackingConfigDialog(), ), @@ -381,10 +390,10 @@ class _SettingsScreenState extends State { const Divider(), // About Section - _buildSectionHeader('About'), + _buildSectionHeader(AppLocalizations.of(context)!.about), ListTile( leading: const Icon(Icons.info), - title: const Text('App Version'), + title: Text(AppLocalizations.of(context)!.appVersion), subtitle: Text( _packageInfo != null ? '${_packageInfo!.version} (${_packageInfo!.buildNumber})' @@ -393,34 +402,34 @@ class _SettingsScreenState extends State { ), ListTile( leading: const Icon(Icons.badge), - title: const Text('App Name'), + title: Text(AppLocalizations.of(context)!.appName), subtitle: Text(_packageInfo?.appName ?? 'MeshCore SAR'), ), ListTile( leading: const Icon(Icons.description), - title: const Text('About MeshCore SAR'), - subtitle: const Text( - 'Search & Rescue application with BLE mesh networking and offline maps', + title: Text(AppLocalizations.of(context)!.aboutMeshCoreSar), + subtitle: Text( + AppLocalizations.of(context)!.aboutDescription.split('\n\n')[0], ), onTap: () => _showAboutDialog(), ), const Divider(), // Developer Section - _buildSectionHeader('Developer'), + _buildSectionHeader(AppLocalizations.of(context)!.developer), ListTile( leading: const Icon(Icons.bug_report), - title: const Text('Package Name'), + title: Text(AppLocalizations.of(context)!.packageName), subtitle: Text(_packageInfo?.packageName ?? 'com.meshcore.sar'), ), const Divider(), // Sample Data Section - _buildSectionHeader('Sample Data'), + _buildSectionHeader(AppLocalizations.of(context)!.sampleData), Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text( - 'Load or clear sample contacts, channel messages, and SAR markers for testing', + AppLocalizations.of(context)!.sampleDataDescription, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), ), @@ -440,7 +449,7 @@ class _SettingsScreenState extends State { child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.add_circle_outline), - label: const Text('Load Sample Data'), + label: Text(AppLocalizations.of(context)!.loadSampleData), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 12), ), @@ -451,7 +460,7 @@ class _SettingsScreenState extends State { child: OutlinedButton.icon( onPressed: _isLoadingSampleData ? null : _clearSampleData, icon: const Icon(Icons.delete_outline), - label: const Text('Clear All Data'), + label: Text(AppLocalizations.of(context)!.clearAllData), style: OutlinedButton.styleFrom( foregroundColor: Colors.red, padding: const EdgeInsets.symmetric(vertical: 12), @@ -488,7 +497,7 @@ class _SettingsScreenState extends State { context: context, builder: (context) => StatefulBuilder( builder: (context, setDialogState) => AlertDialog( - title: const Text('Location Tracking Configuration'), + title: Text(AppLocalizations.of(context)!.locationTrackingConfiguration), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, @@ -496,7 +505,7 @@ class _SettingsScreenState extends State { children: [ // Description Text( - 'Configure when location broadcasts are sent to the mesh network', + AppLocalizations.of(context)!.configureWhenLocationBroadcasts, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Colors.grey, ), @@ -505,14 +514,14 @@ class _SettingsScreenState extends State { // Minimum Distance Text( - 'Minimum Distance', + AppLocalizations.of(context)!.minimumDistance, style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), Text( - 'Broadcast only after moving ${tempMinDistance.toStringAsFixed(0)} meters', + AppLocalizations.of(context)!.broadcastAfterMoving(tempMinDistance.toStringAsFixed(0)), style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 8), @@ -551,14 +560,14 @@ class _SettingsScreenState extends State { // Maximum Distance Text( - 'Maximum Distance', + AppLocalizations.of(context)!.maximumDistance, style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), Text( - 'Always broadcast after moving ${tempMaxDistance.toStringAsFixed(0)} meters', + AppLocalizations.of(context)!.alwaysBroadcastAfterMoving(tempMaxDistance.toStringAsFixed(0)), style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 8), @@ -594,14 +603,14 @@ class _SettingsScreenState extends State { // Minimum Time Interval Text( - 'Minimum Time Interval', + AppLocalizations.of(context)!.minimumTimeInterval, style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), Text( - 'Always broadcast every ${_formatDuration(tempTimeInterval)}', + AppLocalizations.of(context)!.alwaysBroadcastEvery(_formatDuration(tempTimeInterval)), style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 8), @@ -638,7 +647,7 @@ class _SettingsScreenState extends State { actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('Cancel'), + child: Text(AppLocalizations.of(context)!.cancel), ), TextButton( onPressed: () async { @@ -663,7 +672,7 @@ class _SettingsScreenState extends State { setState(() {}); } }, - child: const Text('Save'), + child: Text(AppLocalizations.of(context)!.save), ), ], ), @@ -689,14 +698,14 @@ class _SettingsScreenState extends State { showDialog( context: context, builder: (context) => AlertDialog( - title: const Text('Choose Theme'), + title: Text(AppLocalizations.of(context)!.chooseTheme), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ RadioListTile( - title: const Text('Light'), - subtitle: const Text('Blue light theme'), + title: Text(AppLocalizations.of(context)!.light), + subtitle: Text(AppLocalizations.of(context)!.blueLightTheme), value: AppThemeMode.light, groupValue: _selectedTheme, onChanged: (value) { @@ -705,8 +714,8 @@ class _SettingsScreenState extends State { }, ), RadioListTile( - title: const Text('Dark'), - subtitle: const Text('Blue dark theme'), + title: Text(AppLocalizations.of(context)!.dark), + subtitle: Text(AppLocalizations.of(context)!.blueDarkTheme), value: AppThemeMode.dark, groupValue: _selectedTheme, onChanged: (value) { @@ -718,7 +727,7 @@ class _SettingsScreenState extends State { RadioListTile( title: Row( children: [ - const Text('SAR Red'), + Text(AppLocalizations.of(context)!.sarRed), const SizedBox(width: 8), Container( width: 16, @@ -731,7 +740,7 @@ class _SettingsScreenState extends State { ), ], ), - subtitle: const Text('Alert/Emergency mode'), + subtitle: Text(AppLocalizations.of(context)!.alertEmergencyMode), value: AppThemeMode.sarRed, groupValue: _selectedTheme, onChanged: (value) { @@ -742,7 +751,7 @@ class _SettingsScreenState extends State { RadioListTile( title: Row( children: [ - const Text('SAR Green'), + Text(AppLocalizations.of(context)!.sarGreen), const SizedBox(width: 8), Container( width: 16, @@ -755,7 +764,7 @@ class _SettingsScreenState extends State { ), ], ), - subtitle: const Text('Safe/All Clear mode'), + subtitle: Text(AppLocalizations.of(context)!.safeAllClearMode), value: AppThemeMode.sarGreen, groupValue: _selectedTheme, onChanged: (value) { @@ -765,8 +774,8 @@ class _SettingsScreenState extends State { ), const Divider(), RadioListTile( - title: const Text('Auto (System)'), - subtitle: const Text('Follow system theme'), + title: Text(AppLocalizations.of(context)!.autoSystem), + subtitle: Text(AppLocalizations.of(context)!.followSystemTheme), value: AppThemeMode.system, groupValue: _selectedTheme, onChanged: (value) { @@ -780,7 +789,7 @@ class _SettingsScreenState extends State { actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('Cancel'), + child: Text(AppLocalizations.of(context)!.cancel), ), ], ), @@ -791,14 +800,14 @@ class _SettingsScreenState extends State { showDialog( context: context, builder: (context) => AlertDialog( - title: const Text('Choose Language'), + title: Text(AppLocalizations.of(context)!.chooseLanguage), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ RadioListTile( - title: const Text('System Default'), - subtitle: const Text('Follow system language'), + title: Text(LocalePreferences.getDisplayName(null)), + subtitle: Text(LocalePreferences.getDisplayName(null)), value: null, groupValue: _selectedLocale, onChanged: (value) { @@ -824,7 +833,7 @@ class _SettingsScreenState extends State { actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('Cancel'), + child: Text(AppLocalizations.of(context)!.cancel), ), ], ), @@ -835,7 +844,7 @@ class _SettingsScreenState extends State { showDialog( context: context, builder: (context) => AlertDialog( - title: const Text('About MeshCore SAR'), + title: Text(AppLocalizations.of(context)!.aboutMeshCoreSar), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, @@ -853,31 +862,19 @@ class _SettingsScreenState extends State { style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 16), - const Text( - 'A Search & Rescue application designed for emergency response teams. ' - 'Features include:\n\n' - '• BLE mesh networking for device-to-device communication\n' - '• Offline maps with multiple layer options\n' - '• Real-time team member tracking\n' - '• SAR tactical markers (found person, fire, staging)\n' - '• Contact management and messaging\n' - '• GPS tracking with compass heading\n' - '• Map tile caching for offline use', + Text( + AppLocalizations.of(context)!.aboutDescription, ), const SizedBox(height: 16), Text( - 'Technologies Used:', + AppLocalizations.of(context)!.technologiesUsed, style: Theme.of( context, ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), ), const SizedBox(height: 8), - const Text( - '• Flutter for cross-platform development\n' - '• BLE (Bluetooth Low Energy) for mesh networking\n' - '• OpenStreetMap for mapping\n' - '• Provider for state management\n' - '• SharedPreferences for local storage', + Text( + AppLocalizations.of(context)!.technologiesList, ), ], ), @@ -885,7 +882,7 @@ class _SettingsScreenState extends State { actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('Close'), + child: Text(AppLocalizations.of(context)!.close), ), ], ), diff --git a/lib/services/location_tracking_service.dart b/lib/services/location_tracking_service.dart index 3a9e317..a9b175b 100644 --- a/lib/services/location_tracking_service.dart +++ b/lib/services/location_tracking_service.dart @@ -22,7 +22,8 @@ class LocationTrackingService { // Singleton Pattern // ============================================================================ - static final LocationTrackingService _instance = LocationTrackingService._internal(); + static final LocationTrackingService _instance = + LocationTrackingService._internal(); /// Get the singleton instance factory LocationTrackingService() => _instance; @@ -52,7 +53,7 @@ class LocationTrackingService { double maxDistanceMeters = 100.0; /// Minimum time interval in seconds between broadcasts - int minTimeIntervalSeconds = 30; + int minTimeIntervalSeconds = 155; /// GPS update distance filter for position stream double gpsUpdateDistance = 10.0; @@ -128,7 +129,7 @@ class LocationTrackingService { Future checkPermissions() async { final permission = await Geolocator.checkPermission(); return permission == LocationPermission.always || - permission == LocationPermission.whileInUse; + permission == LocationPermission.whileInUse; } /// Request location permissions from user @@ -154,7 +155,9 @@ class LocationTrackingService { } if (permission == LocationPermission.deniedForever) { - onError?.call('Location permission permanently denied. Please enable in settings.'); + onError?.call( + 'Location permission permanently denied. Please enable in settings.', + ); return false; } @@ -210,7 +213,9 @@ class LocationTrackingService { /// Returns true if successful, false otherwise. Future startTracking({double? distanceThreshold}) async { if (!_isInitialized || _bleService == null) { - debugPrint('⚠️ [LocationTracking] Service not initialized or BLE service null'); + debugPrint( + '⚠️ [LocationTracking] Service not initialized or BLE service null', + ); onError?.call('Location tracking service not initialized'); return false; } @@ -239,18 +244,21 @@ class LocationTrackingService { // Start position stream try { - _positionSubscription = getPositionStream(distanceFilter: threshold).listen( - _handlePositionUpdate, - onError: (error) { - debugPrint('❌ [LocationTracking] Position stream error: $error'); - onError?.call('Position stream error: $error'); - }, - ); + _positionSubscription = getPositionStream(distanceFilter: threshold) + .listen( + _handlePositionUpdate, + onError: (error) { + debugPrint('❌ [LocationTracking] Position stream error: $error'); + onError?.call('Position stream error: $error'); + }, + ); isTracking = true; onTrackingStateChanged?.call(true); - debugPrint('✅ [LocationTracking] Tracking started with ${threshold}m threshold'); + debugPrint( + '✅ [LocationTracking] Tracking started with ${threshold}m threshold', + ); return true; } catch (e) { debugPrint('❌ [LocationTracking] Failed to start tracking: $e'); @@ -281,7 +289,9 @@ class LocationTrackingService { gpsUpdateDistance = meters; await saveSettings(); - debugPrint('📏 [LocationTracking] Distance threshold updated to ${meters}m'); + debugPrint( + '📏 [LocationTracking] Distance threshold updated to ${meters}m', + ); // Restart tracking if currently active if (isTracking) { @@ -296,7 +306,9 @@ class LocationTrackingService { /// Handle incoming position updates from GPS stream void _handlePositionUpdate(Position position) { - debugPrint('📍 [LocationTracking] New position: ${position.latitude}, ${position.longitude}'); + debugPrint( + '📍 [LocationTracking] New position: ${position.latitude}, ${position.longitude}', + ); // Update current position currentPosition = position; @@ -317,7 +329,9 @@ class LocationTrackingService { } // Calculate time since last broadcast - final timeSinceLastBroadcast = DateTime.now().difference(_lastBroadcastTime!); + final timeSinceLastBroadcast = DateTime.now().difference( + _lastBroadcastTime!, + ); final secondsSinceLastBroadcast = timeSinceLastBroadcast.inSeconds; // Calculate distance from last broadcast position @@ -328,12 +342,16 @@ class LocationTrackingService { position.longitude, ); - debugPrint(' Distance from last broadcast: ${distanceFromLastBroadcast.toStringAsFixed(1)}m'); + debugPrint( + ' Distance from last broadcast: ${distanceFromLastBroadcast.toStringAsFixed(1)}m', + ); debugPrint(' Time since last broadcast: ${secondsSinceLastBroadcast}s'); // Broadcast if moved beyond max distance threshold if (distanceFromLastBroadcast >= maxDistanceMeters) { - debugPrint(' 📤 Triggering broadcast: exceeded max distance (${maxDistanceMeters}m)'); + debugPrint( + ' 📤 Triggering broadcast: exceeded max distance (${maxDistanceMeters}m)', + ); _broadcastPosition(position); return; } @@ -341,7 +359,9 @@ class LocationTrackingService { // Broadcast if minimum time interval passed AND moved beyond min distance if (secondsSinceLastBroadcast >= minTimeIntervalSeconds && distanceFromLastBroadcast >= minDistanceMeters) { - debugPrint(' 📤 Triggering broadcast: exceeded min time (${minTimeIntervalSeconds}s) and min distance (${minDistanceMeters}m)'); + debugPrint( + ' 📤 Triggering broadcast: exceeded min time (${minTimeIntervalSeconds}s) and min distance (${minDistanceMeters}m)', + ); _broadcastPosition(position); return; } diff --git a/lib/widgets/common/location_display.dart b/lib/widgets/common/location_display.dart index d9a00f1..0488209 100644 --- a/lib/widgets/common/location_display.dart +++ b/lib/widgets/common/location_display.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:latlong2/latlong.dart'; +import '../../l10n/app_localizations.dart'; /// Reusable location display widget with tap-to-show modal /// Shows coordinates in a compact format with ability to view all formats @@ -149,7 +150,7 @@ class LocationDisplay extends StatelessWidget { Clipboard.setData(ClipboardData(text: value)); ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('$label copied to clipboard'), + content: Text(AppLocalizations.of(context)!.copiedToClipboard(label)), duration: const Duration(seconds: 2), ), ); diff --git a/lib/widgets/contacts/contact_tile.dart b/lib/widgets/contacts/contact_tile.dart index 88d9e54..d41aa3a 100644 --- a/lib/widgets/contacts/contact_tile.dart +++ b/lib/widgets/contacts/contact_tile.dart @@ -11,6 +11,7 @@ import '../../providers/map_provider.dart'; import 'direct_message_sheet.dart'; import 'room_login_sheet.dart'; import '../../utils/toast_logger.dart'; +import '../../l10n/app_localizations.dart'; class ContactTile extends StatelessWidget { final Contact contact; @@ -359,7 +360,7 @@ class ContactTile extends StatelessWidget { showDialog( context: context, builder: (context) => AlertDialog( - title: const Text('Delete Contact'), + title: Text(AppLocalizations.of(context)!.deleteContact), content: Text( 'Are you sure you want to delete "${contact.displayName}"?\n\n' 'This will remove the contact from both the app and the companion radio device.', @@ -367,7 +368,7 @@ class ContactTile extends StatelessWidget { actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('Cancel'), + child: Text(AppLocalizations.of(context)!.cancel), ), TextButton( onPressed: () async { @@ -376,7 +377,7 @@ class ContactTile extends StatelessWidget { await _deleteContact(context, contact); }, style: TextButton.styleFrom(foregroundColor: Colors.red), - child: const Text('Delete'), + child: Text(AppLocalizations.of(context)!.delete), ), ], ), @@ -504,9 +505,9 @@ class ContactTile extends StatelessWidget { onTap: () { Clipboard.setData(ClipboardData(text: contact.publicKeyHex)); ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Public key copied to clipboard'), - duration: Duration(seconds: 2), + SnackBar( + content: Text(AppLocalizations.of(context)!.publicKeyCopied), + duration: const Duration(seconds: 2), ), ); }, @@ -587,7 +588,7 @@ class ContactTile extends StatelessWidget { DefaultTabController.of(context).animateTo(2); }, icon: const Icon(Icons.map, size: 18), - label: const Text('View on Map'), + label: Text(AppLocalizations.of(context)!.viewOnMap), style: TextButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), ), @@ -645,7 +646,7 @@ class ContactTile extends StatelessWidget { ToastLogger.info(context, 'Requesting telemetry from ${contact.displayName}...'); }, icon: const Icon(Icons.refresh, size: 18), - label: const Text('Refresh'), + label: Text(AppLocalizations.of(context)!.refresh), style: TextButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), ), @@ -688,7 +689,7 @@ class ContactTile extends StatelessWidget { _showDirectMessageDialog(context, contact); }, icon: const Icon(Icons.message), - label: const Text('Send Direct Message'), + label: Text(AppLocalizations.of(context)!.sendDirectMessage), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 14), backgroundColor: _getTypeColor(contact.type, context), @@ -705,7 +706,7 @@ class ContactTile extends StatelessWidget { ToastLogger.info(context, 'Path reset for ${contact.displayName}. Next message will find a new route.'); }, icon: const Icon(Icons.route), - label: const Text('Reset Path (Re-route)'), + label: Text(AppLocalizations.of(context)!.resetPath), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 14), side: BorderSide(color: _getTypeColor(contact.type, context)), @@ -742,7 +743,7 @@ class ContactTile extends StatelessWidget { child: OutlinedButton.icon( onPressed: () => _showDeleteConfirmation(context, contact), icon: const Icon(Icons.delete_outline), - label: const Text('Delete Contact'), + label: Text(AppLocalizations.of(context)!.deleteContact), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 14), side: const BorderSide(color: Colors.red), @@ -803,7 +804,7 @@ class ContactTile extends StatelessWidget { Clipboard.setData(ClipboardData(text: value)); ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('$label copied to clipboard'), + content: Text(AppLocalizations.of(context)!.copiedToClipboard(label)), duration: const Duration(seconds: 2), ), ); diff --git a/lib/widgets/contacts/direct_message_sheet.dart b/lib/widgets/contacts/direct_message_sheet.dart index fa88dfd..81bdcf2 100644 --- a/lib/widgets/contacts/direct_message_sheet.dart +++ b/lib/widgets/contacts/direct_message_sheet.dart @@ -7,6 +7,7 @@ import '../../models/message.dart'; import '../../providers/connection_provider.dart'; import '../../providers/messages_provider.dart'; import '../../utils/toast_logger.dart'; +import '../../l10n/app_localizations.dart'; class DirectMessageSheet extends StatefulWidget { final Contact contact; @@ -51,7 +52,7 @@ class _DirectMessageSheetState extends State { if (!connectionProvider.deviceInfo.isConnected) { if (!mounted) return; - ToastLogger.error(context, 'Not connected to device'); + ToastLogger.error(context, AppLocalizations.of(context)!.notConnectedToDevice); return; } @@ -100,10 +101,10 @@ class _DirectMessageSheetState extends State { if (!mounted) return; Navigator.pop(context); // Close the dialog - ToastLogger.success(context, 'Direct message sent to ${widget.contact.displayName}'); + ToastLogger.success(context, AppLocalizations.of(context)!.directMessageSentTo(widget.contact.displayName)); } catch (e) { if (!mounted) return; - ToastLogger.error(context, 'Failed to send: $e'); + ToastLogger.error(context, AppLocalizations.of(context)!.failedToSend(e.toString())); } } @@ -137,7 +138,7 @@ class _DirectMessageSheetState extends State { child: Column( children: [ Text( - 'Direct Message', + AppLocalizations.of(context)!.directMessage, style: TextStyle( color: colorScheme.onSurface, fontSize: 18, @@ -173,7 +174,7 @@ class _DirectMessageSheetState extends State { const SizedBox(width: 12), Expanded( child: Text( - 'This message will be sent directly to ${widget.contact.displayName}. It will also appear in the main messages feed.', + AppLocalizations.of(context)!.directMessageInfo(widget.contact.displayName), style: TextStyle( color: Theme.of(context).colorScheme.onPrimaryContainer, fontSize: 13, @@ -210,7 +211,7 @@ class _DirectMessageSheetState extends State { maxLengthEnforcement: MaxLengthEnforcement.enforced, style: TextStyle(color: colorScheme.onSurface), decoration: InputDecoration( - hintText: 'Type your message...', + hintText: AppLocalizations.of(context)!.typeYourMessage, hintStyle: TextStyle(color: colorScheme.onSurfaceVariant), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), @@ -246,7 +247,7 @@ class _DirectMessageSheetState extends State { ? null : _sendDirectMessage, icon: const Icon(Icons.send), - label: const Text('Send Direct Message'), + label: Text(AppLocalizations.of(context)!.sendDirectMessage), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 14), backgroundColor: colorScheme.primary, diff --git a/lib/widgets/contacts/room_login_sheet.dart b/lib/widgets/contacts/room_login_sheet.dart index d98b7db..255a4a8 100644 --- a/lib/widgets/contacts/room_login_sheet.dart +++ b/lib/widgets/contacts/room_login_sheet.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import '../../l10n/app_localizations.dart'; import '../../models/contact.dart'; import '../../providers/connection_provider.dart'; import '../../providers/contacts_provider.dart'; @@ -62,7 +63,7 @@ class _RoomLoginSheetState extends State { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: const Text('Please enter a password'), + content: Text(AppLocalizations.of(context)!.pleaseEnterPassword), backgroundColor: Theme.of(context).colorScheme.error, ), ); @@ -73,7 +74,7 @@ class _RoomLoginSheetState extends State { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: const Text('Not connected to device'), + content: Text(AppLocalizations.of(context)!.deviceNotConnected), backgroundColor: Theme.of(context).colorScheme.error, ), ); @@ -183,7 +184,7 @@ class _RoomLoginSheetState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Failed to sync contacts: $e'), + content: Text(AppLocalizations.of(context)!.failedToSyncContacts(e.toString())), backgroundColor: Theme.of(context).colorScheme.error, ), ); @@ -215,7 +216,7 @@ class _RoomLoginSheetState extends State { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: const Text('Logged in successfully! Waiting for room messages...'), + content: Text(AppLocalizations.of(context)!.loggedInSuccessfully), backgroundColor: Theme.of(context).colorScheme.primary, duration: const Duration(seconds: 3), ), @@ -233,7 +234,7 @@ class _RoomLoginSheetState extends State { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: const Text('Login failed - incorrect password'), + content: Text(AppLocalizations.of(context)!.loginFailed), backgroundColor: Theme.of(context).colorScheme.error, duration: const Duration(seconds: 3), ), @@ -255,7 +256,7 @@ class _RoomLoginSheetState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Logging in to ${widget.contact.displayName}...'), + content: Text(AppLocalizations.of(context)!.loggingIn(widget.contact.displayName)), backgroundColor: Theme.of(context).colorScheme.primary, duration: const Duration(seconds: 2), ), @@ -268,7 +269,7 @@ class _RoomLoginSheetState extends State { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Failed to send login: $e'), + content: Text(AppLocalizations.of(context)!.failedToSendLogin(e.toString())), backgroundColor: Theme.of(context).colorScheme.error, ), ); diff --git a/lib/widgets/map/drawing_layer.dart b/lib/widgets/map/drawing_layer.dart index 51db53d..e74c5c0 100644 --- a/lib/widgets/map/drawing_layer.dart +++ b/lib/widgets/map/drawing_layer.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import '../../models/map_drawing.dart'; +import '../../l10n/app_localizations.dart'; /// Widget that renders map drawings as polylines class DrawingLayer extends StatelessWidget { @@ -212,14 +213,14 @@ class DrawingMarkersLayer extends StatelessWidget { showDialog( context: context, builder: (context) => AlertDialog( - title: const Text('Delete Drawing'), + title: Text(AppLocalizations.of(context)!.deleteDrawing), content: Text( 'Delete this ${drawing.type.name}?', ), actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('Cancel'), + child: Text(AppLocalizations.of(context)!.cancel), ), TextButton( onPressed: () { @@ -229,7 +230,7 @@ class DrawingMarkersLayer extends StatelessWidget { style: TextButton.styleFrom( foregroundColor: Colors.red, ), - child: const Text('Delete'), + child: Text(AppLocalizations.of(context)!.delete), ), ], ), diff --git a/lib/widgets/map/drawing_toolbar.dart b/lib/widgets/map/drawing_toolbar.dart index e65654e..0175ef3 100644 --- a/lib/widgets/map/drawing_toolbar.dart +++ b/lib/widgets/map/drawing_toolbar.dart @@ -7,6 +7,7 @@ import '../../providers/contacts_provider.dart'; import '../../providers/messages_provider.dart'; import '../../models/map_drawing.dart'; import '../../models/contact.dart'; +import '../../l10n/app_localizations.dart'; /// Toolbar for drawing controls on the map class DrawingToolbar extends StatelessWidget { @@ -53,7 +54,7 @@ class DrawingToolbar extends StatelessWidget { ), const SizedBox(width: 8), Text( - _getTitleForMode(drawingProvider.drawingMode), + _getTitleForMode(drawingProvider.drawingMode, context), style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.bold, ), @@ -119,7 +120,7 @@ class DrawingToolbar extends StatelessWidget { IconButton( icon: const Icon(Icons.undo), onPressed: () => drawingProvider.cancelCurrentDrawing(), - tooltip: 'Cancel', + tooltip: AppLocalizations.of(context)!.cancel, iconSize: 20, padding: const EdgeInsets.all(4), constraints: const BoxConstraints(), @@ -184,8 +185,8 @@ class DrawingToolbar extends StatelessWidget { const Divider(), ListTile( leading: const Icon(Icons.show_chart), - title: const Text('Draw Line'), - subtitle: const Text('Draw a freehand line on the map'), + title: Text(AppLocalizations.of(context)!.drawLine), + subtitle: Text(AppLocalizations.of(context)!.drawLineDesc), onTap: () { Navigator.pop(context); drawingProvider.setDrawingMode(DrawingMode.line); @@ -193,8 +194,8 @@ class DrawingToolbar extends StatelessWidget { ), ListTile( leading: const Icon(Icons.crop_square), - title: const Text('Draw Rectangle'), - subtitle: const Text('Draw a rectangular area on the map'), + title: Text(AppLocalizations.of(context)!.drawRectangle), + subtitle: Text(AppLocalizations.of(context)!.drawRectangleDesc), onTap: () { Navigator.pop(context); drawingProvider.setDrawingMode(DrawingMode.rectangle); @@ -204,7 +205,7 @@ class DrawingToolbar extends StatelessWidget { const Divider(), ListTile( leading: const Icon(Icons.share, color: Colors.blue), - title: const Text('Share Drawings'), + title: Text(AppLocalizations.of(context)!.shareDrawings), subtitle: Text( 'Broadcast ${drawingProvider.drawings.length} drawings to team', ), @@ -220,7 +221,7 @@ class DrawingToolbar extends StatelessWidget { ), ListTile( leading: const Icon(Icons.delete_sweep, color: Colors.red), - title: const Text('Clear All Drawings'), + title: Text(AppLocalizations.of(context)!.clearAllDrawings), subtitle: Text( 'Remove all ${drawingProvider.drawings.length} drawings', ), @@ -244,14 +245,14 @@ class DrawingToolbar extends StatelessWidget { showDialog( context: context, builder: (context) => AlertDialog( - title: const Text('Clear All Drawings'), + title: Text(AppLocalizations.of(context)!.clearAllDrawings), content: Text( 'Delete all ${drawingProvider.drawings.length} drawings from the map?', ), actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('Cancel'), + child: Text(AppLocalizations.of(context)!.cancel), ), TextButton( onPressed: () { @@ -259,7 +260,7 @@ class DrawingToolbar extends StatelessWidget { drawingProvider.clearAllDrawings(); }, style: TextButton.styleFrom(foregroundColor: Colors.red), - child: const Text('Clear All'), + child: Text(AppLocalizations.of(context)!.clearAll), ), ], ), @@ -279,12 +280,12 @@ class DrawingToolbar extends StatelessWidget { } /// Get title for drawing mode - String _getTitleForMode(DrawingMode mode) { + String _getTitleForMode(DrawingMode mode, BuildContext context) { switch (mode) { case DrawingMode.line: - return 'Draw Line'; + return AppLocalizations.of(context)!.drawLine; case DrawingMode.rectangle: - return 'Draw Rectangle'; + return AppLocalizations.of(context)!.drawRectangle; case DrawingMode.none: return 'Drawing'; } @@ -321,8 +322,8 @@ class DrawingToolbar extends StatelessWidget { if (!connectionProvider.deviceInfo.isConnected) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Not connected to device'), + SnackBar( + content: Text(AppLocalizations.of(context)!.deviceNotConnected), backgroundColor: Colors.red, ), ); @@ -342,8 +343,8 @@ class DrawingToolbar extends StatelessWidget { if (localDrawings.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('No local drawings to share'), + SnackBar( + content: Text(AppLocalizations.of(context)!.noLocalDrawings), backgroundColor: Colors.orange, ), ); @@ -384,8 +385,8 @@ class DrawingToolbar extends StatelessWidget { // Option: Send to Public Channel ListTile( leading: const Icon(Icons.public, color: Colors.blue), - title: const Text('Public Channel'), - subtitle: const Text('Broadcast to all nearby nodes (ephemeral)'), + title: Text(AppLocalizations.of(context)!.publicChannel), + subtitle: Text(AppLocalizations.of(context)!.broadcastToAll), onTap: () async { debugPrint('📤 [DrawingToolbar] Public Channel tapped'); // Share BEFORE popping the navigator @@ -406,7 +407,7 @@ class DrawingToolbar extends StatelessWidget { (room) => ListTile( leading: const Icon(Icons.meeting_room, color: Colors.green), title: Text(room.advName), - subtitle: const Text('Stored permanently in room'), + subtitle: Text(AppLocalizations.of(context)!.storedPermanently), onTap: () async { debugPrint('📤 [DrawingToolbar] Room ${room.advName} tapped'); // Share BEFORE popping the navigator diff --git a/lib/widgets/map_markers.dart b/lib/widgets/map_markers.dart index 9a0d7d4..efe1b56 100644 --- a/lib/widgets/map_markers.dart +++ b/lib/widgets/map_markers.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import '../models/contact.dart'; import '../models/sar_marker.dart'; +import '../l10n/app_localizations.dart'; class MapMarkers { static List createTeamMemberMarkers( @@ -238,7 +239,7 @@ class MapMarkers { actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('Close'), + child: Text(AppLocalizations.of(context)!.close), ), ], ), @@ -272,7 +273,7 @@ class MapMarkers { actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('Close'), + child: Text(AppLocalizations.of(context)!.close), ), ], ), diff --git a/lib/widgets/messages/sar_update_sheet.dart b/lib/widgets/messages/sar_update_sheet.dart index 406e702..59c39ac 100644 --- a/lib/widgets/messages/sar_update_sheet.dart +++ b/lib/widgets/messages/sar_update_sheet.dart @@ -6,6 +6,7 @@ import '../../providers/contacts_provider.dart'; import '../../models/contact.dart'; import '../../models/sar_marker.dart'; import '../../services/validation_service.dart'; +import '../../l10n/app_localizations.dart'; /// SAR Update Sheet - Modal bottom sheet for creating and sending SAR markers /// This widget is public so it can be used from both messages_tab.dart and map_tab.dart @@ -167,7 +168,7 @@ class _SarUpdateSheetState extends State { child: Column( children: [ Text( - 'Send SAR Marker', + AppLocalizations.of(context)!.sendSarMarker, style: TextStyle( color: colorScheme.onSurface, fontSize: 18, @@ -645,7 +646,7 @@ class _SarUpdateSheetState extends State { final shouldContinue = await showDialog( context: context, builder: (context) => AlertDialog( - title: const Text('Low Location Accuracy'), + title: Text(AppLocalizations.of(context)!.lowLocationAccuracy), content: Text( 'Location accuracy is ±${_currentPosition!.accuracy!.round()}m. ' 'This may not be accurate enough for SAR operations.\n\n' @@ -654,11 +655,11 @@ class _SarUpdateSheetState extends State { actions: [ TextButton( onPressed: () => Navigator.pop(context, false), - child: const Text('Cancel'), + child: Text(AppLocalizations.of(context)!.cancel), ), TextButton( onPressed: () => Navigator.pop(context, true), - child: const Text('Continue'), + child: Text(AppLocalizations.of(context)!.continue_), ), ], ), @@ -688,9 +689,9 @@ class _SarUpdateSheetState extends State { ), ), icon: const Icon(Icons.send, size: 20), - label: const Text( - 'Send SAR Marker', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + label: Text( + AppLocalizations.of(context)!.sendSarMarker, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), ), ), ),