mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Add localization support for various UI elements and messages
- Integrated localization for alert dialogs, snack bars, and button labels in PacketLogScreen and SettingsScreen. - Updated location tracking service messages to use localized strings. - Enhanced ContactTile and DirectMessageSheet with localized text for user interactions. - Implemented localization in DrawingLayer and DrawingToolbar for map-related actions. - Updated SAR Update Sheet to utilize localized strings for user prompts and actions. - Ensured consistent use of localization across all relevant widgets and screens.
This commit is contained in:
@@ -8,7 +8,8 @@
|
|||||||
"Bash(dart pub global activate:*)",
|
"Bash(dart pub global activate:*)",
|
||||||
"Bash(dart pub global run intl_utils:generate:*)",
|
"Bash(dart pub global run intl_utils:generate:*)",
|
||||||
"Bash(dart pub global deactivate:*)",
|
"Bash(dart pub global deactivate:*)",
|
||||||
"Bash(flutter precache:*)"
|
"Bash(flutter precache:*)",
|
||||||
|
"Bash(flutter analyze:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
@@ -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": "The default pin for devices without a screen is 123456. Trouble pairing? Forget the bluetooth device in system settings.",
|
||||||
"@defaultPinInfo": {
|
"@defaultPinInfo": {
|
||||||
"description": "Information about default PIN for pairing"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,5 +157,79 @@
|
|||||||
|
|
||||||
"locationBroadcast": "Emitiranje lokacije: {latitude}, {longitude}",
|
"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..."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -578,6 +578,228 @@ abstract class AppLocalizations {
|
|||||||
/// In en, this message translates to:
|
/// 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.'**
|
/// **'The default pin for devices without a screen is 123456. Trouble pairing? Forget the bluetooth device in system settings.'**
|
||||||
String get defaultPinInfo;
|
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
|
class _AppLocalizationsDelegate
|
||||||
|
|||||||
@@ -278,4 +278,130 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get defaultPinInfo =>
|
String get defaultPinInfo =>
|
||||||
'The default pin for devices without a screen is 123456. Trouble pairing? Forget the bluetooth device in system settings.';
|
'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...';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -278,4 +278,130 @@ class AppLocalizationsHr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get defaultPinInfo =>
|
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';
|
||||||
|
|
||||||
|
@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...';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -278,4 +278,130 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get defaultPinInfo =>
|
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';
|
||||||
|
|
||||||
|
@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...';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,5 +157,79 @@
|
|||||||
|
|
||||||
"locationBroadcast": "Oddajanje lokacije: {latitude}, {longitude}",
|
"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..."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:geolocator/geolocator.dart';
|
import 'package:geolocator/geolocator.dart';
|
||||||
import '../providers/connection_provider.dart';
|
import '../providers/connection_provider.dart';
|
||||||
import '../services/validation_service.dart';
|
import '../services/validation_service.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class DeviceConfigScreen extends StatefulWidget {
|
class DeviceConfigScreen extends StatefulWidget {
|
||||||
const DeviceConfigScreen({super.key});
|
const DeviceConfigScreen({super.key});
|
||||||
@@ -174,8 +175,8 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Public info saved'),
|
content: Text(AppLocalizations.of(context)!.save),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -244,8 +245,8 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Radio settings saved'),
|
content: Text(AppLocalizations.of(context)!.save),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -268,7 +269,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
if (!serviceEnabled) {
|
if (!serviceEnabled) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('Location services disabled')),
|
SnackBar(content: Text(AppLocalizations.of(context)!.locationServicesDisabled)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -280,7 +281,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
if (permission == LocationPermission.denied) {
|
if (permission == LocationPermission.denied) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('Location permission denied')),
|
SnackBar(content: Text(AppLocalizations.of(context)!.locationPermissionDenied)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -290,7 +291,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
if (permission == LocationPermission.deniedForever) {
|
if (permission == LocationPermission.deniedForever) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('Location permission permanently denied')),
|
SnackBar(content: Text(AppLocalizations.of(context)!.locationPermissionPermanentlyDenied)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -310,8 +311,11 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Location updated'),
|
content: Text(AppLocalizations.of(context)!.locationBroadcast(
|
||||||
|
position.latitude.toStringAsFixed(6),
|
||||||
|
position.longitude.toStringAsFixed(6),
|
||||||
|
)),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -331,8 +335,8 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
if (!connectionProvider.deviceInfo.isConnected) {
|
if (!connectionProvider.deviceInfo.isConnected) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Not connected to device'),
|
content: Text(AppLocalizations.of(context)!.deviceNotConnected),
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: Colors.orange,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -347,9 +351,12 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
await connectionProvider.sendSelfAdvert(floodMode: true);
|
await connectionProvider.sendSelfAdvert(floodMode: true);
|
||||||
|
|
||||||
if (context.mounted) {
|
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(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Advertisement broadcast to mesh network'),
|
content: Text(AppLocalizations.of(context)!.advertisedAtLocation(lat, lon)),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -358,7 +365,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('Failed to broadcast: $e'),
|
content: Text(AppLocalizations.of(context)!.failedToAdvertise(e.toString())),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -377,7 +384,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Device Settings'),
|
title: Text(AppLocalizations.of(context)!.settings),
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
@@ -441,13 +448,13 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
)
|
)
|
||||||
: const Icon(Icons.sensors),
|
: const Icon(Icons.sensors),
|
||||||
tooltip: 'Broadcast',
|
tooltip: AppLocalizations.of(context)!.settings,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
IconButton.filled(
|
IconButton.filled(
|
||||||
onPressed: _savePublicInfo,
|
onPressed: _savePublicInfo,
|
||||||
icon: const Icon(Icons.save),
|
icon: const Icon(Icons.save),
|
||||||
tooltip: 'Save',
|
tooltip: AppLocalizations.of(context)!.save,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -555,7 +562,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
IconButton.filled(
|
IconButton.filled(
|
||||||
onPressed: _saveRadioSettings,
|
onPressed: _saveRadioSettings,
|
||||||
icon: const Icon(Icons.save),
|
icon: const Icon(Icons.save),
|
||||||
tooltip: 'Save',
|
tooltip: AppLocalizations.of(context)!.save,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import 'settings_screen.dart';
|
|||||||
import 'device_config_screen.dart';
|
import 'device_config_screen.dart';
|
||||||
import 'packet_log_screen.dart';
|
import 'packet_log_screen.dart';
|
||||||
import '../utils/toast_logger.dart';
|
import '../utils/toast_logger.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
class HomeScreen extends StatefulWidget {
|
||||||
final Function(AppThemeMode) onThemeChanged;
|
final Function(AppThemeMode) onThemeChanged;
|
||||||
@@ -77,7 +78,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
|
|
||||||
if (!connectionProvider.deviceInfo.isConnected) {
|
if (!connectionProvider.deviceInfo.isConnected) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ToastLogger.error(context, 'Device not connected');
|
ToastLogger.error(context, AppLocalizations.of(context)!.deviceNotConnected);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -89,7 +90,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ToastLogger.error(
|
ToastLogger.error(
|
||||||
context,
|
context,
|
||||||
'Location services are disabled. Please enable them in Settings.',
|
AppLocalizations.of(context)!.locationServicesDisabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -101,7 +102,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
permission = await Geolocator.requestPermission();
|
permission = await Geolocator.requestPermission();
|
||||||
if (permission == LocationPermission.denied) {
|
if (permission == LocationPermission.denied) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ToastLogger.error(context, 'Location permission denied');
|
ToastLogger.error(context, AppLocalizations.of(context)!.locationPermissionDenied);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -111,7 +112,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ToastLogger.error(
|
ToastLogger.error(
|
||||||
context,
|
context,
|
||||||
'Location permission permanently denied. Please enable in Settings.',
|
AppLocalizations.of(context)!.locationPermissionPermanentlyDenied,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -129,7 +130,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ Failed to get GPS position: $e');
|
print('❌ Failed to get GPS position: $e');
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ToastLogger.error(context, 'Failed to get GPS location');
|
ToastLogger.error(context, AppLocalizations.of(context)!.failedToGetGpsLocation);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -149,13 +150,16 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ToastLogger.success(
|
ToastLogger.success(
|
||||||
context,
|
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) {
|
} catch (e) {
|
||||||
print('❌ Failed to advertise device: $e');
|
print('❌ Failed to advertise device: $e');
|
||||||
if (context.mounted) {
|
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<HomeScreen>
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'MeshCore',
|
AppLocalizations.of(context)!.appTitle,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
@@ -211,7 +215,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Scanning for devices...',
|
AppLocalizations.of(context)!.scanningForDevices,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
context,
|
context,
|
||||||
@@ -253,7 +257,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
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(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
@@ -288,7 +292,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'No devices found',
|
AppLocalizations.of(context)!.noDevicesFound,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
context,
|
context,
|
||||||
@@ -303,7 +307,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
connectionProvider.startScan();
|
connectionProvider.startScan();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.refresh),
|
icon: const Icon(Icons.refresh),
|
||||||
label: const Text('Scan Again'),
|
label: Text(AppLocalizations.of(context)!.scanAgain),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -358,7 +362,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
subtitle: Row(
|
subtitle: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Tap to connect',
|
AppLocalizations.of(context)!.tapToConnect,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
context,
|
context,
|
||||||
@@ -450,7 +454,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
await provider.disconnect();
|
await provider.disconnect();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.power_settings_new),
|
icon: const Icon(Icons.power_settings_new),
|
||||||
tooltip: 'Disconnect',
|
tooltip: AppLocalizations.of(context)!.disconnect,
|
||||||
color: Colors.red.shade700,
|
color: Colors.red.shade700,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -461,11 +465,11 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
icon: const Icon(Icons.more_vert),
|
icon: const Icon(Icons.more_vert),
|
||||||
itemBuilder: (context) => [
|
itemBuilder: (context) => [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: const Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.map),
|
const Icon(Icons.map),
|
||||||
SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text('Map Management'),
|
Text(AppLocalizations.of(context)!.mapManagement),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@@ -483,11 +487,11 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: const Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.settings),
|
const Icon(Icons.settings),
|
||||||
SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text('Settings'),
|
Text(AppLocalizations.of(context)!.settings),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@@ -551,16 +555,16 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
Icons.message,
|
Icons.message,
|
||||||
unreadCount,
|
unreadCount,
|
||||||
),
|
),
|
||||||
text: 'Messages',
|
text: AppLocalizations.of(context)!.messages,
|
||||||
),
|
),
|
||||||
Tab(
|
Tab(
|
||||||
icon: _buildTabIconWithBadge(
|
icon: _buildTabIconWithBadge(
|
||||||
Icons.contacts,
|
Icons.contacts,
|
||||||
newContactsCount,
|
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<HomeScreen>
|
|||||||
// Disconnected state: show connect button
|
// Disconnected state: show connect button
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
const Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'MeshCore',
|
AppLocalizations.of(context)!.appTitle,
|
||||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
@@ -608,7 +612,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
label: Text(
|
label: Text(
|
||||||
provider.isReconnecting
|
provider.isReconnecting
|
||||||
? '${provider.reconnectionAttempt}/${provider.maxReconnectionAttempts}'
|
? '${provider.reconnectionAttempt}/${provider.maxReconnectionAttempts}'
|
||||||
: 'Connect',
|
: AppLocalizations.of(context)!.connect,
|
||||||
),
|
),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
@@ -624,7 +628,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => provider.cancelReconnection(),
|
onPressed: () => provider.cancelReconnection(),
|
||||||
icon: const Icon(Icons.close, size: 20),
|
icon: const Icon(Icons.close, size: 20),
|
||||||
tooltip: 'Cancel reconnection',
|
tooltip: AppLocalizations.of(context)!.cancelReconnection,
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
backgroundColor: Colors.red.shade700,
|
backgroundColor: Colors.red.shade700,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
@@ -650,7 +654,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
deviceInfo.selfName ?? 'MeshCore',
|
deviceInfo.selfName ?? AppLocalizations.of(context)!.appTitle,
|
||||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
@@ -838,8 +842,8 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
isConnected
|
isConnected
|
||||||
? deviceInfo.displayName ?? 'Connected'
|
? deviceInfo.displayName ?? AppLocalizations.of(context)!.connect
|
||||||
: 'Not Connected',
|
: AppLocalizations.of(context)!.deviceNotConnected,
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -881,7 +885,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
? null
|
? null
|
||||||
: () => _showConnectionDialog(context),
|
: () => _showConnectionDialog(context),
|
||||||
icon: const Icon(Icons.bluetooth_searching, size: 18),
|
icon: const Icon(Icons.bluetooth_searching, size: 18),
|
||||||
label: const Text('Connect'),
|
label: Text(AppLocalizations.of(context)!.connect),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
),
|
),
|
||||||
@@ -896,7 +900,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
await provider.disconnect();
|
await provider.disconnect();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.bluetooth_disabled, size: 18),
|
icon: const Icon(Icons.bluetooth_disabled, size: 18),
|
||||||
label: const Text('Disconnect'),
|
label: Text(AppLocalizations.of(context)!.disconnect),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:latlong2/latlong.dart';
|
|||||||
import '../services/tile_cache_service.dart';
|
import '../services/tile_cache_service.dart';
|
||||||
import '../services/validation_service.dart';
|
import '../services/validation_service.dart';
|
||||||
import '../models/map_layer.dart';
|
import '../models/map_layer.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class MapManagementScreen extends StatefulWidget {
|
class MapManagementScreen extends StatefulWidget {
|
||||||
final TileCacheService tileCacheService;
|
final TileCacheService tileCacheService;
|
||||||
@@ -217,8 +218,8 @@ class _MapManagementScreenState extends State<MapManagementScreen> {
|
|||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Download cancelled'),
|
content: Text(AppLocalizations.of(context)!.cancel),
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: Colors.orange,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -237,19 +238,19 @@ class _MapManagementScreenState extends State<MapManagementScreen> {
|
|||||||
final confirmed = await showDialog<bool>(
|
final confirmed = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Clear Cache'),
|
title: Text(AppLocalizations.of(context)!.clear),
|
||||||
content: const Text(
|
content: const Text(
|
||||||
'Are you sure you want to delete all downloaded maps? This action cannot be undone.',
|
'Are you sure you want to delete all downloaded maps? This action cannot be undone.',
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context, false),
|
onPressed: () => Navigator.pop(context, false),
|
||||||
child: const Text('Cancel'),
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context, true),
|
onPressed: () => Navigator.pop(context, true),
|
||||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||||
child: const Text('Clear'),
|
child: Text(AppLocalizations.of(context)!.clear),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -295,7 +296,7 @@ class _MapManagementScreenState extends State<MapManagementScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Map Management'),
|
title: Text(AppLocalizations.of(context)!.mapManagement),
|
||||||
),
|
),
|
||||||
body: _isLoading
|
body: _isLoading
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import '../widgets/map/detailed_compass_dialog.dart';
|
|||||||
import '../widgets/map/drawing_layer.dart';
|
import '../widgets/map/drawing_layer.dart';
|
||||||
import '../widgets/map/drawing_toolbar.dart';
|
import '../widgets/map/drawing_toolbar.dart';
|
||||||
import '../widgets/messages/sar_update_sheet.dart';
|
import '../widgets/messages/sar_update_sheet.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
import 'map_management_screen.dart';
|
import 'map_management_screen.dart';
|
||||||
|
|
||||||
class MapTab extends StatefulWidget {
|
class MapTab extends StatefulWidget {
|
||||||
@@ -643,8 +644,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
_saveSettings();
|
_saveSettings();
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Failed to start background tracking. Check permissions and BLE connection.'),
|
content: Text(AppLocalizations.of(context)!.failedToStartBackgroundTracking),
|
||||||
duration: Duration(seconds: 3),
|
duration: Duration(seconds: 3),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -710,8 +711,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
if (!connectionProvider.deviceInfo.isConnected) {
|
if (!connectionProvider.deviceInfo.isConnected) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Not connected to device'),
|
content: Text(AppLocalizations.of(context)!.deviceNotConnected),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import '../models/sar_marker.dart';
|
|||||||
import '../widgets/messages/sar_update_sheet.dart';
|
import '../widgets/messages/sar_update_sheet.dart';
|
||||||
import '../widgets/contacts/direct_message_sheet.dart';
|
import '../widgets/contacts/direct_message_sheet.dart';
|
||||||
import '../utils/toast_logger.dart';
|
import '../utils/toast_logger.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class MessagesTab extends StatefulWidget {
|
class MessagesTab extends StatefulWidget {
|
||||||
final VoidCallback onNavigateToMap;
|
final VoidCallback onNavigateToMap;
|
||||||
@@ -304,14 +305,14 @@ class _MessagesTabState extends State<MessagesTab> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'No messages yet',
|
AppLocalizations.of(context)!.noMessagesYet,
|
||||||
style: Theme.of(
|
style: Theme.of(
|
||||||
context,
|
context,
|
||||||
).textTheme.titleLarge,
|
).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Pull down to sync messages',
|
AppLocalizations.of(context)!.pullDownToSync,
|
||||||
style: Theme.of(
|
style: Theme.of(
|
||||||
context,
|
context,
|
||||||
).textTheme.bodyMedium,
|
).textTheme.bodyMedium,
|
||||||
@@ -374,7 +375,7 @@ class _MessagesTabState extends State<MessagesTab> {
|
|||||||
// SAR quick action button
|
// SAR quick action button
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.add_location_alt),
|
icon: const Icon(Icons.add_location_alt),
|
||||||
tooltip: 'Send SAR marker',
|
tooltip: AppLocalizations.of(context)!.sendSarMarker,
|
||||||
onPressed: _showSarDialog,
|
onPressed: _showSarDialog,
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
backgroundColor: Theme.of(
|
backgroundColor: Theme.of(
|
||||||
@@ -579,9 +580,9 @@ class _MessageBubble extends StatelessWidget {
|
|||||||
// Delete message option
|
// Delete message option
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.delete, color: Colors.red),
|
leading: const Icon(Icons.delete, color: Colors.red),
|
||||||
title: const Text(
|
title: Text(
|
||||||
'Delete message',
|
AppLocalizations.of(context)!.delete,
|
||||||
style: TextStyle(color: Colors.red),
|
style: const TextStyle(color: Colors.red),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@@ -633,6 +634,7 @@ class _MessageBubble extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showDeleteConfirmation(BuildContext context) {
|
void _showDeleteConfirmation(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context)!;
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
@@ -641,7 +643,7 @@ class _MessageBubble extends StatelessWidget {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text('Cancel'),
|
child: Text(l10n.cancel),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'dart:io';
|
|||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import '../models/ble_packet_log.dart';
|
import '../models/ble_packet_log.dart';
|
||||||
import '../services/meshcore_ble_service.dart';
|
import '../services/meshcore_ble_service.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class PacketLogScreen extends StatefulWidget {
|
class PacketLogScreen extends StatefulWidget {
|
||||||
final MeshCoreBleService bleService;
|
final MeshCoreBleService bleService;
|
||||||
@@ -149,12 +150,12 @@ class _PacketLogScreenState extends State<PacketLogScreen> {
|
|||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
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.'),
|
content: const Text('Are you sure you want to clear all packet logs? This cannot be undone.'),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text('Cancel'),
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@@ -166,7 +167,7 @@ class _PacketLogScreenState extends State<PacketLogScreen> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||||
child: const Text('Clear'),
|
child: Text(AppLocalizations.of(context)!.clear),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import '../services/location_tracking_service.dart';
|
|||||||
import '../services/locale_preferences.dart';
|
import '../services/locale_preferences.dart';
|
||||||
import '../utils/sample_data_generator.dart';
|
import '../utils/sample_data_generator.dart';
|
||||||
import '../theme/app_theme.dart';
|
import '../theme/app_theme.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class SettingsScreen extends StatefulWidget {
|
class SettingsScreen extends StatefulWidget {
|
||||||
final Function(AppThemeMode) onThemeChanged;
|
final Function(AppThemeMode) onThemeChanged;
|
||||||
@@ -97,7 +98,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
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,
|
backgroundColor: Colors.green,
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
@@ -220,7 +224,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
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,
|
backgroundColor: Colors.green,
|
||||||
),
|
),
|
||||||
@@ -229,7 +238,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('Failed to load sample data: $e'),
|
content: Text(AppLocalizations.of(context)!.failedToLoadSampleData(e.toString())),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -247,11 +256,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
|
|
||||||
if (!success && mounted) {
|
if (!success && mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
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<SettingsScreen> {
|
|||||||
final confirmed = await showDialog<bool>(
|
final confirmed = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Clear All Data'),
|
title: Text(AppLocalizations.of(context)!.clearAllDataConfirmTitle),
|
||||||
content: const Text(
|
content: Text(
|
||||||
'This will clear all contacts and SAR markers. Are you sure?',
|
AppLocalizations.of(context)!.clearAllDataConfirmMessage,
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context, false),
|
onPressed: () => Navigator.pop(context, false),
|
||||||
child: const Text('Cancel'),
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context, true),
|
onPressed: () => Navigator.pop(context, true),
|
||||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||||
child: const Text('Clear'),
|
child: Text(AppLocalizations.of(context)!.clear),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -307,8 +316,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('All data cleared'),
|
content: Text(AppLocalizations.of(context)!.allDataCleared),
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: Colors.orange,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -317,22 +326,22 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('Settings')),
|
appBar: AppBar(title: Text(AppLocalizations.of(context)!.settings)),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
// General Settings Section
|
// General Settings Section
|
||||||
_buildSectionHeader('General'),
|
_buildSectionHeader(AppLocalizations.of(context)!.general),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.palette),
|
leading: const Icon(Icons.palette),
|
||||||
title: const Text('Theme'),
|
title: Text(AppLocalizations.of(context)!.theme),
|
||||||
subtitle: Text(AppTheme.getThemeDisplayName(_selectedTheme)),
|
subtitle: Text(AppTheme.getThemeDisplayName(_selectedTheme)),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () => _showThemeDialog(),
|
onTap: () => _showThemeDialog(),
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
secondary: const Icon(Icons.radar),
|
secondary: const Icon(Icons.radar),
|
||||||
title: const Text('Show RX/TX Indicators'),
|
title: Text(AppLocalizations.of(context)!.showRxTxIndicators),
|
||||||
subtitle: const Text('Display packet activity indicators in top bar'),
|
subtitle: Text(AppLocalizations.of(context)!.displayPacketActivity),
|
||||||
value: _showRxTxIndicators,
|
value: _showRxTxIndicators,
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -343,7 +352,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.language),
|
leading: const Icon(Icons.language),
|
||||||
title: const Text('Language'),
|
title: Text(AppLocalizations.of(context)!.language),
|
||||||
subtitle: Text(LocalePreferences.getDisplayName(_selectedLocale)),
|
subtitle: Text(LocalePreferences.getDisplayName(_selectedLocale)),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () => _showLanguageDialog(),
|
onTap: () => _showLanguageDialog(),
|
||||||
@@ -351,13 +360,13 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
const Divider(),
|
const Divider(),
|
||||||
|
|
||||||
// Location Settings Section
|
// Location Settings Section
|
||||||
_buildSectionHeader('Location Broadcasting'),
|
_buildSectionHeader(AppLocalizations.of(context)!.locationBroadcasting),
|
||||||
|
|
||||||
// Automatic tracking settings
|
// Automatic tracking settings
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
secondary: const Icon(Icons.location_on),
|
secondary: const Icon(Icons.location_on),
|
||||||
title: const Text('Auto Location Tracking'),
|
title: Text(AppLocalizations.of(context)!.autoLocationTracking),
|
||||||
subtitle: const Text('Automatically broadcast position updates'),
|
subtitle: Text(AppLocalizations.of(context)!.automaticallyBroadcastPosition),
|
||||||
value: _locationService.isTracking,
|
value: _locationService.isTracking,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
@@ -371,8 +380,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
if (_locationService.isTracking) ...[
|
if (_locationService.isTracking) ...[
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.tune),
|
leading: const Icon(Icons.tune),
|
||||||
title: const Text('Configure Tracking'),
|
title: Text(AppLocalizations.of(context)!.configureTracking),
|
||||||
subtitle: const Text('Distance and time thresholds'),
|
subtitle: Text(AppLocalizations.of(context)!.distanceAndTimeThresholds),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () => _showTrackingConfigDialog(),
|
onTap: () => _showTrackingConfigDialog(),
|
||||||
),
|
),
|
||||||
@@ -381,10 +390,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
const Divider(),
|
const Divider(),
|
||||||
|
|
||||||
// About Section
|
// About Section
|
||||||
_buildSectionHeader('About'),
|
_buildSectionHeader(AppLocalizations.of(context)!.about),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.info),
|
leading: const Icon(Icons.info),
|
||||||
title: const Text('App Version'),
|
title: Text(AppLocalizations.of(context)!.appVersion),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
_packageInfo != null
|
_packageInfo != null
|
||||||
? '${_packageInfo!.version} (${_packageInfo!.buildNumber})'
|
? '${_packageInfo!.version} (${_packageInfo!.buildNumber})'
|
||||||
@@ -393,34 +402,34 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.badge),
|
leading: const Icon(Icons.badge),
|
||||||
title: const Text('App Name'),
|
title: Text(AppLocalizations.of(context)!.appName),
|
||||||
subtitle: Text(_packageInfo?.appName ?? 'MeshCore SAR'),
|
subtitle: Text(_packageInfo?.appName ?? 'MeshCore SAR'),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.description),
|
leading: const Icon(Icons.description),
|
||||||
title: const Text('About MeshCore SAR'),
|
title: Text(AppLocalizations.of(context)!.aboutMeshCoreSar),
|
||||||
subtitle: const Text(
|
subtitle: Text(
|
||||||
'Search & Rescue application with BLE mesh networking and offline maps',
|
AppLocalizations.of(context)!.aboutDescription.split('\n\n')[0],
|
||||||
),
|
),
|
||||||
onTap: () => _showAboutDialog(),
|
onTap: () => _showAboutDialog(),
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
|
||||||
// Developer Section
|
// Developer Section
|
||||||
_buildSectionHeader('Developer'),
|
_buildSectionHeader(AppLocalizations.of(context)!.developer),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.bug_report),
|
leading: const Icon(Icons.bug_report),
|
||||||
title: const Text('Package Name'),
|
title: Text(AppLocalizations.of(context)!.packageName),
|
||||||
subtitle: Text(_packageInfo?.packageName ?? 'com.meshcore.sar'),
|
subtitle: Text(_packageInfo?.packageName ?? 'com.meshcore.sar'),
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
|
||||||
// Sample Data Section
|
// Sample Data Section
|
||||||
_buildSectionHeader('Sample Data'),
|
_buildSectionHeader(AppLocalizations.of(context)!.sampleData),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
child: Text(
|
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(
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||||
),
|
),
|
||||||
@@ -440,7 +449,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
)
|
)
|
||||||
: const Icon(Icons.add_circle_outline),
|
: const Icon(Icons.add_circle_outline),
|
||||||
label: const Text('Load Sample Data'),
|
label: Text(AppLocalizations.of(context)!.loadSampleData),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
),
|
),
|
||||||
@@ -451,7 +460,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
child: OutlinedButton.icon(
|
child: OutlinedButton.icon(
|
||||||
onPressed: _isLoadingSampleData ? null : _clearSampleData,
|
onPressed: _isLoadingSampleData ? null : _clearSampleData,
|
||||||
icon: const Icon(Icons.delete_outline),
|
icon: const Icon(Icons.delete_outline),
|
||||||
label: const Text('Clear All Data'),
|
label: Text(AppLocalizations.of(context)!.clearAllData),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
foregroundColor: Colors.red,
|
foregroundColor: Colors.red,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
@@ -488,7 +497,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (context) => StatefulBuilder(
|
builder: (context) => StatefulBuilder(
|
||||||
builder: (context, setDialogState) => AlertDialog(
|
builder: (context, setDialogState) => AlertDialog(
|
||||||
title: const Text('Location Tracking Configuration'),
|
title: Text(AppLocalizations.of(context)!.locationTrackingConfiguration),
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -496,7 +505,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
children: [
|
children: [
|
||||||
// Description
|
// Description
|
||||||
Text(
|
Text(
|
||||||
'Configure when location broadcasts are sent to the mesh network',
|
AppLocalizations.of(context)!.configureWhenLocationBroadcasts,
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
color: Colors.grey,
|
color: Colors.grey,
|
||||||
),
|
),
|
||||||
@@ -505,14 +514,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
|
|
||||||
// Minimum Distance
|
// Minimum Distance
|
||||||
Text(
|
Text(
|
||||||
'Minimum Distance',
|
AppLocalizations.of(context)!.minimumDistance,
|
||||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Broadcast only after moving ${tempMinDistance.toStringAsFixed(0)} meters',
|
AppLocalizations.of(context)!.broadcastAfterMoving(tempMinDistance.toStringAsFixed(0)),
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@@ -551,14 +560,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
|
|
||||||
// Maximum Distance
|
// Maximum Distance
|
||||||
Text(
|
Text(
|
||||||
'Maximum Distance',
|
AppLocalizations.of(context)!.maximumDistance,
|
||||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Always broadcast after moving ${tempMaxDistance.toStringAsFixed(0)} meters',
|
AppLocalizations.of(context)!.alwaysBroadcastAfterMoving(tempMaxDistance.toStringAsFixed(0)),
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@@ -594,14 +603,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
|
|
||||||
// Minimum Time Interval
|
// Minimum Time Interval
|
||||||
Text(
|
Text(
|
||||||
'Minimum Time Interval',
|
AppLocalizations.of(context)!.minimumTimeInterval,
|
||||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Always broadcast every ${_formatDuration(tempTimeInterval)}',
|
AppLocalizations.of(context)!.alwaysBroadcastEvery(_formatDuration(tempTimeInterval)),
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@@ -638,7 +647,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text('Cancel'),
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@@ -663,7 +672,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: const Text('Save'),
|
child: Text(AppLocalizations.of(context)!.save),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -689,14 +698,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Choose Theme'),
|
title: Text(AppLocalizations.of(context)!.chooseTheme),
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
RadioListTile<AppThemeMode>(
|
RadioListTile<AppThemeMode>(
|
||||||
title: const Text('Light'),
|
title: Text(AppLocalizations.of(context)!.light),
|
||||||
subtitle: const Text('Blue light theme'),
|
subtitle: Text(AppLocalizations.of(context)!.blueLightTheme),
|
||||||
value: AppThemeMode.light,
|
value: AppThemeMode.light,
|
||||||
groupValue: _selectedTheme,
|
groupValue: _selectedTheme,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
@@ -705,8 +714,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
RadioListTile<AppThemeMode>(
|
RadioListTile<AppThemeMode>(
|
||||||
title: const Text('Dark'),
|
title: Text(AppLocalizations.of(context)!.dark),
|
||||||
subtitle: const Text('Blue dark theme'),
|
subtitle: Text(AppLocalizations.of(context)!.blueDarkTheme),
|
||||||
value: AppThemeMode.dark,
|
value: AppThemeMode.dark,
|
||||||
groupValue: _selectedTheme,
|
groupValue: _selectedTheme,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
@@ -718,7 +727,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
RadioListTile<AppThemeMode>(
|
RadioListTile<AppThemeMode>(
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
const Text('SAR Red'),
|
Text(AppLocalizations.of(context)!.sarRed),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Container(
|
Container(
|
||||||
width: 16,
|
width: 16,
|
||||||
@@ -731,7 +740,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
subtitle: const Text('Alert/Emergency mode'),
|
subtitle: Text(AppLocalizations.of(context)!.alertEmergencyMode),
|
||||||
value: AppThemeMode.sarRed,
|
value: AppThemeMode.sarRed,
|
||||||
groupValue: _selectedTheme,
|
groupValue: _selectedTheme,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
@@ -742,7 +751,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
RadioListTile<AppThemeMode>(
|
RadioListTile<AppThemeMode>(
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
const Text('SAR Green'),
|
Text(AppLocalizations.of(context)!.sarGreen),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Container(
|
Container(
|
||||||
width: 16,
|
width: 16,
|
||||||
@@ -755,7 +764,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
subtitle: const Text('Safe/All Clear mode'),
|
subtitle: Text(AppLocalizations.of(context)!.safeAllClearMode),
|
||||||
value: AppThemeMode.sarGreen,
|
value: AppThemeMode.sarGreen,
|
||||||
groupValue: _selectedTheme,
|
groupValue: _selectedTheme,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
@@ -765,8 +774,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
RadioListTile<AppThemeMode>(
|
RadioListTile<AppThemeMode>(
|
||||||
title: const Text('Auto (System)'),
|
title: Text(AppLocalizations.of(context)!.autoSystem),
|
||||||
subtitle: const Text('Follow system theme'),
|
subtitle: Text(AppLocalizations.of(context)!.followSystemTheme),
|
||||||
value: AppThemeMode.system,
|
value: AppThemeMode.system,
|
||||||
groupValue: _selectedTheme,
|
groupValue: _selectedTheme,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
@@ -780,7 +789,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text('Cancel'),
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -791,14 +800,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Choose Language'),
|
title: Text(AppLocalizations.of(context)!.chooseLanguage),
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
RadioListTile<Locale?>(
|
RadioListTile<Locale?>(
|
||||||
title: const Text('System Default'),
|
title: Text(LocalePreferences.getDisplayName(null)),
|
||||||
subtitle: const Text('Follow system language'),
|
subtitle: Text(LocalePreferences.getDisplayName(null)),
|
||||||
value: null,
|
value: null,
|
||||||
groupValue: _selectedLocale,
|
groupValue: _selectedLocale,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
@@ -824,7 +833,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text('Cancel'),
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -835,7 +844,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('About MeshCore SAR'),
|
title: Text(AppLocalizations.of(context)!.aboutMeshCoreSar),
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -853,31 +862,19 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const Text(
|
Text(
|
||||||
'A Search & Rescue application designed for emergency response teams. '
|
AppLocalizations.of(context)!.aboutDescription,
|
||||||
'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',
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'Technologies Used:',
|
AppLocalizations.of(context)!.technologiesUsed,
|
||||||
style: Theme.of(
|
style: Theme.of(
|
||||||
context,
|
context,
|
||||||
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
|
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
const Text(
|
Text(
|
||||||
'• Flutter for cross-platform development\n'
|
AppLocalizations.of(context)!.technologiesList,
|
||||||
'• BLE (Bluetooth Low Energy) for mesh networking\n'
|
|
||||||
'• OpenStreetMap for mapping\n'
|
|
||||||
'• Provider for state management\n'
|
|
||||||
'• SharedPreferences for local storage',
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -885,7 +882,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text('Close'),
|
child: Text(AppLocalizations.of(context)!.close),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ class LocationTrackingService {
|
|||||||
// Singleton Pattern
|
// Singleton Pattern
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
static final LocationTrackingService _instance = LocationTrackingService._internal();
|
static final LocationTrackingService _instance =
|
||||||
|
LocationTrackingService._internal();
|
||||||
|
|
||||||
/// Get the singleton instance
|
/// Get the singleton instance
|
||||||
factory LocationTrackingService() => _instance;
|
factory LocationTrackingService() => _instance;
|
||||||
@@ -52,7 +53,7 @@ class LocationTrackingService {
|
|||||||
double maxDistanceMeters = 100.0;
|
double maxDistanceMeters = 100.0;
|
||||||
|
|
||||||
/// Minimum time interval in seconds between broadcasts
|
/// Minimum time interval in seconds between broadcasts
|
||||||
int minTimeIntervalSeconds = 30;
|
int minTimeIntervalSeconds = 155;
|
||||||
|
|
||||||
/// GPS update distance filter for position stream
|
/// GPS update distance filter for position stream
|
||||||
double gpsUpdateDistance = 10.0;
|
double gpsUpdateDistance = 10.0;
|
||||||
@@ -154,7 +155,9 @@ class LocationTrackingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (permission == LocationPermission.deniedForever) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +213,9 @@ class LocationTrackingService {
|
|||||||
/// Returns true if successful, false otherwise.
|
/// Returns true if successful, false otherwise.
|
||||||
Future<bool> startTracking({double? distanceThreshold}) async {
|
Future<bool> startTracking({double? distanceThreshold}) async {
|
||||||
if (!_isInitialized || _bleService == null) {
|
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');
|
onError?.call('Location tracking service not initialized');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -239,7 +244,8 @@ class LocationTrackingService {
|
|||||||
|
|
||||||
// Start position stream
|
// Start position stream
|
||||||
try {
|
try {
|
||||||
_positionSubscription = getPositionStream(distanceFilter: threshold).listen(
|
_positionSubscription = getPositionStream(distanceFilter: threshold)
|
||||||
|
.listen(
|
||||||
_handlePositionUpdate,
|
_handlePositionUpdate,
|
||||||
onError: (error) {
|
onError: (error) {
|
||||||
debugPrint('❌ [LocationTracking] Position stream error: $error');
|
debugPrint('❌ [LocationTracking] Position stream error: $error');
|
||||||
@@ -250,7 +256,9 @@ class LocationTrackingService {
|
|||||||
isTracking = true;
|
isTracking = true;
|
||||||
onTrackingStateChanged?.call(true);
|
onTrackingStateChanged?.call(true);
|
||||||
|
|
||||||
debugPrint('✅ [LocationTracking] Tracking started with ${threshold}m threshold');
|
debugPrint(
|
||||||
|
'✅ [LocationTracking] Tracking started with ${threshold}m threshold',
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('❌ [LocationTracking] Failed to start tracking: $e');
|
debugPrint('❌ [LocationTracking] Failed to start tracking: $e');
|
||||||
@@ -281,7 +289,9 @@ class LocationTrackingService {
|
|||||||
gpsUpdateDistance = meters;
|
gpsUpdateDistance = meters;
|
||||||
await saveSettings();
|
await saveSettings();
|
||||||
|
|
||||||
debugPrint('📏 [LocationTracking] Distance threshold updated to ${meters}m');
|
debugPrint(
|
||||||
|
'📏 [LocationTracking] Distance threshold updated to ${meters}m',
|
||||||
|
);
|
||||||
|
|
||||||
// Restart tracking if currently active
|
// Restart tracking if currently active
|
||||||
if (isTracking) {
|
if (isTracking) {
|
||||||
@@ -296,7 +306,9 @@ class LocationTrackingService {
|
|||||||
|
|
||||||
/// Handle incoming position updates from GPS stream
|
/// Handle incoming position updates from GPS stream
|
||||||
void _handlePositionUpdate(Position position) {
|
void _handlePositionUpdate(Position position) {
|
||||||
debugPrint('📍 [LocationTracking] New position: ${position.latitude}, ${position.longitude}');
|
debugPrint(
|
||||||
|
'📍 [LocationTracking] New position: ${position.latitude}, ${position.longitude}',
|
||||||
|
);
|
||||||
|
|
||||||
// Update current position
|
// Update current position
|
||||||
currentPosition = position;
|
currentPosition = position;
|
||||||
@@ -317,7 +329,9 @@ class LocationTrackingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate time since last broadcast
|
// Calculate time since last broadcast
|
||||||
final timeSinceLastBroadcast = DateTime.now().difference(_lastBroadcastTime!);
|
final timeSinceLastBroadcast = DateTime.now().difference(
|
||||||
|
_lastBroadcastTime!,
|
||||||
|
);
|
||||||
final secondsSinceLastBroadcast = timeSinceLastBroadcast.inSeconds;
|
final secondsSinceLastBroadcast = timeSinceLastBroadcast.inSeconds;
|
||||||
|
|
||||||
// Calculate distance from last broadcast position
|
// Calculate distance from last broadcast position
|
||||||
@@ -328,12 +342,16 @@ class LocationTrackingService {
|
|||||||
position.longitude,
|
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');
|
debugPrint(' Time since last broadcast: ${secondsSinceLastBroadcast}s');
|
||||||
|
|
||||||
// Broadcast if moved beyond max distance threshold
|
// Broadcast if moved beyond max distance threshold
|
||||||
if (distanceFromLastBroadcast >= maxDistanceMeters) {
|
if (distanceFromLastBroadcast >= maxDistanceMeters) {
|
||||||
debugPrint(' 📤 Triggering broadcast: exceeded max distance (${maxDistanceMeters}m)');
|
debugPrint(
|
||||||
|
' 📤 Triggering broadcast: exceeded max distance (${maxDistanceMeters}m)',
|
||||||
|
);
|
||||||
_broadcastPosition(position);
|
_broadcastPosition(position);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -341,7 +359,9 @@ class LocationTrackingService {
|
|||||||
// Broadcast if minimum time interval passed AND moved beyond min distance
|
// Broadcast if minimum time interval passed AND moved beyond min distance
|
||||||
if (secondsSinceLastBroadcast >= minTimeIntervalSeconds &&
|
if (secondsSinceLastBroadcast >= minTimeIntervalSeconds &&
|
||||||
distanceFromLastBroadcast >= minDistanceMeters) {
|
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);
|
_broadcastPosition(position);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
/// Reusable location display widget with tap-to-show modal
|
/// Reusable location display widget with tap-to-show modal
|
||||||
/// Shows coordinates in a compact format with ability to view all formats
|
/// 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));
|
Clipboard.setData(ClipboardData(text: value));
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('$label copied to clipboard'),
|
content: Text(AppLocalizations.of(context)!.copiedToClipboard(label)),
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import '../../providers/map_provider.dart';
|
|||||||
import 'direct_message_sheet.dart';
|
import 'direct_message_sheet.dart';
|
||||||
import 'room_login_sheet.dart';
|
import 'room_login_sheet.dart';
|
||||||
import '../../utils/toast_logger.dart';
|
import '../../utils/toast_logger.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class ContactTile extends StatelessWidget {
|
class ContactTile extends StatelessWidget {
|
||||||
final Contact contact;
|
final Contact contact;
|
||||||
@@ -359,7 +360,7 @@ class ContactTile extends StatelessWidget {
|
|||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Delete Contact'),
|
title: Text(AppLocalizations.of(context)!.deleteContact),
|
||||||
content: Text(
|
content: Text(
|
||||||
'Are you sure you want to delete "${contact.displayName}"?\n\n'
|
'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.',
|
'This will remove the contact from both the app and the companion radio device.',
|
||||||
@@ -367,7 +368,7 @@ class ContactTile extends StatelessWidget {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text('Cancel'),
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@@ -376,7 +377,7 @@ class ContactTile extends StatelessWidget {
|
|||||||
await _deleteContact(context, contact);
|
await _deleteContact(context, contact);
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
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: () {
|
onTap: () {
|
||||||
Clipboard.setData(ClipboardData(text: contact.publicKeyHex));
|
Clipboard.setData(ClipboardData(text: contact.publicKeyHex));
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Public key copied to clipboard'),
|
content: Text(AppLocalizations.of(context)!.publicKeyCopied),
|
||||||
duration: Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -587,7 +588,7 @@ class ContactTile extends StatelessWidget {
|
|||||||
DefaultTabController.of(context).animateTo(2);
|
DefaultTabController.of(context).animateTo(2);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.map, size: 18),
|
icon: const Icon(Icons.map, size: 18),
|
||||||
label: const Text('View on Map'),
|
label: Text(AppLocalizations.of(context)!.viewOnMap),
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
),
|
),
|
||||||
@@ -645,7 +646,7 @@ class ContactTile extends StatelessWidget {
|
|||||||
ToastLogger.info(context, 'Requesting telemetry from ${contact.displayName}...');
|
ToastLogger.info(context, 'Requesting telemetry from ${contact.displayName}...');
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.refresh, size: 18),
|
icon: const Icon(Icons.refresh, size: 18),
|
||||||
label: const Text('Refresh'),
|
label: Text(AppLocalizations.of(context)!.refresh),
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
),
|
),
|
||||||
@@ -688,7 +689,7 @@ class ContactTile extends StatelessWidget {
|
|||||||
_showDirectMessageDialog(context, contact);
|
_showDirectMessageDialog(context, contact);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.message),
|
icon: const Icon(Icons.message),
|
||||||
label: const Text('Send Direct Message'),
|
label: Text(AppLocalizations.of(context)!.sendDirectMessage),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
backgroundColor: _getTypeColor(contact.type, context),
|
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.');
|
ToastLogger.info(context, 'Path reset for ${contact.displayName}. Next message will find a new route.');
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.route),
|
icon: const Icon(Icons.route),
|
||||||
label: const Text('Reset Path (Re-route)'),
|
label: Text(AppLocalizations.of(context)!.resetPath),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
side: BorderSide(color: _getTypeColor(contact.type, context)),
|
side: BorderSide(color: _getTypeColor(contact.type, context)),
|
||||||
@@ -742,7 +743,7 @@ class ContactTile extends StatelessWidget {
|
|||||||
child: OutlinedButton.icon(
|
child: OutlinedButton.icon(
|
||||||
onPressed: () => _showDeleteConfirmation(context, contact),
|
onPressed: () => _showDeleteConfirmation(context, contact),
|
||||||
icon: const Icon(Icons.delete_outline),
|
icon: const Icon(Icons.delete_outline),
|
||||||
label: const Text('Delete Contact'),
|
label: Text(AppLocalizations.of(context)!.deleteContact),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
side: const BorderSide(color: Colors.red),
|
side: const BorderSide(color: Colors.red),
|
||||||
@@ -803,7 +804,7 @@ class ContactTile extends StatelessWidget {
|
|||||||
Clipboard.setData(ClipboardData(text: value));
|
Clipboard.setData(ClipboardData(text: value));
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('$label copied to clipboard'),
|
content: Text(AppLocalizations.of(context)!.copiedToClipboard(label)),
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import '../../models/message.dart';
|
|||||||
import '../../providers/connection_provider.dart';
|
import '../../providers/connection_provider.dart';
|
||||||
import '../../providers/messages_provider.dart';
|
import '../../providers/messages_provider.dart';
|
||||||
import '../../utils/toast_logger.dart';
|
import '../../utils/toast_logger.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class DirectMessageSheet extends StatefulWidget {
|
class DirectMessageSheet extends StatefulWidget {
|
||||||
final Contact contact;
|
final Contact contact;
|
||||||
@@ -51,7 +52,7 @@ class _DirectMessageSheetState extends State<DirectMessageSheet> {
|
|||||||
|
|
||||||
if (!connectionProvider.deviceInfo.isConnected) {
|
if (!connectionProvider.deviceInfo.isConnected) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ToastLogger.error(context, 'Not connected to device');
|
ToastLogger.error(context, AppLocalizations.of(context)!.notConnectedToDevice);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,10 +101,10 @@ class _DirectMessageSheetState extends State<DirectMessageSheet> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
Navigator.pop(context); // Close the dialog
|
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) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
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<DirectMessageSheet> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Direct Message',
|
AppLocalizations.of(context)!.directMessage,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: colorScheme.onSurface,
|
color: colorScheme.onSurface,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
@@ -173,7 +174,7 @@ class _DirectMessageSheetState extends State<DirectMessageSheet> {
|
|||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
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(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
@@ -210,7 +211,7 @@ class _DirectMessageSheetState extends State<DirectMessageSheet> {
|
|||||||
maxLengthEnforcement: MaxLengthEnforcement.enforced,
|
maxLengthEnforcement: MaxLengthEnforcement.enforced,
|
||||||
style: TextStyle(color: colorScheme.onSurface),
|
style: TextStyle(color: colorScheme.onSurface),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Type your message...',
|
hintText: AppLocalizations.of(context)!.typeYourMessage,
|
||||||
hintStyle: TextStyle(color: colorScheme.onSurfaceVariant),
|
hintStyle: TextStyle(color: colorScheme.onSurfaceVariant),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
@@ -246,7 +247,7 @@ class _DirectMessageSheetState extends State<DirectMessageSheet> {
|
|||||||
? null
|
? null
|
||||||
: _sendDirectMessage,
|
: _sendDirectMessage,
|
||||||
icon: const Icon(Icons.send),
|
icon: const Icon(Icons.send),
|
||||||
label: const Text('Send Direct Message'),
|
label: Text(AppLocalizations.of(context)!.sendDirectMessage),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
backgroundColor: colorScheme.primary,
|
backgroundColor: colorScheme.primary,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
import '../../models/contact.dart';
|
import '../../models/contact.dart';
|
||||||
import '../../providers/connection_provider.dart';
|
import '../../providers/connection_provider.dart';
|
||||||
import '../../providers/contacts_provider.dart';
|
import '../../providers/contacts_provider.dart';
|
||||||
@@ -62,7 +63,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: const Text('Please enter a password'),
|
content: Text(AppLocalizations.of(context)!.pleaseEnterPassword),
|
||||||
backgroundColor: Theme.of(context).colorScheme.error,
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -73,7 +74,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: const Text('Not connected to device'),
|
content: Text(AppLocalizations.of(context)!.deviceNotConnected),
|
||||||
backgroundColor: Theme.of(context).colorScheme.error,
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -183,7 +184,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
|
|||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('Failed to sync contacts: $e'),
|
content: Text(AppLocalizations.of(context)!.failedToSyncContacts(e.toString())),
|
||||||
backgroundColor: Theme.of(context).colorScheme.error,
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -215,7 +216,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
|
|||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: const Text('Logged in successfully! Waiting for room messages...'),
|
content: Text(AppLocalizations.of(context)!.loggedInSuccessfully),
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
),
|
),
|
||||||
@@ -233,7 +234,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
|
|||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: const Text('Login failed - incorrect password'),
|
content: Text(AppLocalizations.of(context)!.loginFailed),
|
||||||
backgroundColor: Theme.of(context).colorScheme.error,
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
),
|
),
|
||||||
@@ -255,7 +256,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
|
|||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('Logging in to ${widget.contact.displayName}...'),
|
content: Text(AppLocalizations.of(context)!.loggingIn(widget.contact.displayName)),
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
),
|
),
|
||||||
@@ -268,7 +269,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('Failed to send login: $e'),
|
content: Text(AppLocalizations.of(context)!.failedToSendLogin(e.toString())),
|
||||||
backgroundColor: Theme.of(context).colorScheme.error,
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import '../../models/map_drawing.dart';
|
import '../../models/map_drawing.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
/// Widget that renders map drawings as polylines
|
/// Widget that renders map drawings as polylines
|
||||||
class DrawingLayer extends StatelessWidget {
|
class DrawingLayer extends StatelessWidget {
|
||||||
@@ -212,14 +213,14 @@ class DrawingMarkersLayer extends StatelessWidget {
|
|||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Delete Drawing'),
|
title: Text(AppLocalizations.of(context)!.deleteDrawing),
|
||||||
content: Text(
|
content: Text(
|
||||||
'Delete this ${drawing.type.name}?',
|
'Delete this ${drawing.type.name}?',
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text('Cancel'),
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@@ -229,7 +230,7 @@ class DrawingMarkersLayer extends StatelessWidget {
|
|||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
foregroundColor: Colors.red,
|
foregroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
child: const Text('Delete'),
|
child: Text(AppLocalizations.of(context)!.delete),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import '../../providers/contacts_provider.dart';
|
|||||||
import '../../providers/messages_provider.dart';
|
import '../../providers/messages_provider.dart';
|
||||||
import '../../models/map_drawing.dart';
|
import '../../models/map_drawing.dart';
|
||||||
import '../../models/contact.dart';
|
import '../../models/contact.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
/// Toolbar for drawing controls on the map
|
/// Toolbar for drawing controls on the map
|
||||||
class DrawingToolbar extends StatelessWidget {
|
class DrawingToolbar extends StatelessWidget {
|
||||||
@@ -53,7 +54,7 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
_getTitleForMode(drawingProvider.drawingMode),
|
_getTitleForMode(drawingProvider.drawingMode, context),
|
||||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
@@ -119,7 +120,7 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.undo),
|
icon: const Icon(Icons.undo),
|
||||||
onPressed: () => drawingProvider.cancelCurrentDrawing(),
|
onPressed: () => drawingProvider.cancelCurrentDrawing(),
|
||||||
tooltip: 'Cancel',
|
tooltip: AppLocalizations.of(context)!.cancel,
|
||||||
iconSize: 20,
|
iconSize: 20,
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
constraints: const BoxConstraints(),
|
constraints: const BoxConstraints(),
|
||||||
@@ -184,8 +185,8 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
const Divider(),
|
const Divider(),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.show_chart),
|
leading: const Icon(Icons.show_chart),
|
||||||
title: const Text('Draw Line'),
|
title: Text(AppLocalizations.of(context)!.drawLine),
|
||||||
subtitle: const Text('Draw a freehand line on the map'),
|
subtitle: Text(AppLocalizations.of(context)!.drawLineDesc),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
drawingProvider.setDrawingMode(DrawingMode.line);
|
drawingProvider.setDrawingMode(DrawingMode.line);
|
||||||
@@ -193,8 +194,8 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.crop_square),
|
leading: const Icon(Icons.crop_square),
|
||||||
title: const Text('Draw Rectangle'),
|
title: Text(AppLocalizations.of(context)!.drawRectangle),
|
||||||
subtitle: const Text('Draw a rectangular area on the map'),
|
subtitle: Text(AppLocalizations.of(context)!.drawRectangleDesc),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
drawingProvider.setDrawingMode(DrawingMode.rectangle);
|
drawingProvider.setDrawingMode(DrawingMode.rectangle);
|
||||||
@@ -204,7 +205,7 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
const Divider(),
|
const Divider(),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.share, color: Colors.blue),
|
leading: const Icon(Icons.share, color: Colors.blue),
|
||||||
title: const Text('Share Drawings'),
|
title: Text(AppLocalizations.of(context)!.shareDrawings),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'Broadcast ${drawingProvider.drawings.length} drawings to team',
|
'Broadcast ${drawingProvider.drawings.length} drawings to team',
|
||||||
),
|
),
|
||||||
@@ -220,7 +221,7 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.delete_sweep, color: Colors.red),
|
leading: const Icon(Icons.delete_sweep, color: Colors.red),
|
||||||
title: const Text('Clear All Drawings'),
|
title: Text(AppLocalizations.of(context)!.clearAllDrawings),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'Remove all ${drawingProvider.drawings.length} drawings',
|
'Remove all ${drawingProvider.drawings.length} drawings',
|
||||||
),
|
),
|
||||||
@@ -244,14 +245,14 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Clear All Drawings'),
|
title: Text(AppLocalizations.of(context)!.clearAllDrawings),
|
||||||
content: Text(
|
content: Text(
|
||||||
'Delete all ${drawingProvider.drawings.length} drawings from the map?',
|
'Delete all ${drawingProvider.drawings.length} drawings from the map?',
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text('Cancel'),
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@@ -259,7 +260,7 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
drawingProvider.clearAllDrawings();
|
drawingProvider.clearAllDrawings();
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
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
|
/// Get title for drawing mode
|
||||||
String _getTitleForMode(DrawingMode mode) {
|
String _getTitleForMode(DrawingMode mode, BuildContext context) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case DrawingMode.line:
|
case DrawingMode.line:
|
||||||
return 'Draw Line';
|
return AppLocalizations.of(context)!.drawLine;
|
||||||
case DrawingMode.rectangle:
|
case DrawingMode.rectangle:
|
||||||
return 'Draw Rectangle';
|
return AppLocalizations.of(context)!.drawRectangle;
|
||||||
case DrawingMode.none:
|
case DrawingMode.none:
|
||||||
return 'Drawing';
|
return 'Drawing';
|
||||||
}
|
}
|
||||||
@@ -321,8 +322,8 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
|
|
||||||
if (!connectionProvider.deviceInfo.isConnected) {
|
if (!connectionProvider.deviceInfo.isConnected) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Not connected to device'),
|
content: Text(AppLocalizations.of(context)!.deviceNotConnected),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -342,8 +343,8 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
|
|
||||||
if (localDrawings.isEmpty) {
|
if (localDrawings.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('No local drawings to share'),
|
content: Text(AppLocalizations.of(context)!.noLocalDrawings),
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: Colors.orange,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -384,8 +385,8 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
// Option: Send to Public Channel
|
// Option: Send to Public Channel
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.public, color: Colors.blue),
|
leading: const Icon(Icons.public, color: Colors.blue),
|
||||||
title: const Text('Public Channel'),
|
title: Text(AppLocalizations.of(context)!.publicChannel),
|
||||||
subtitle: const Text('Broadcast to all nearby nodes (ephemeral)'),
|
subtitle: Text(AppLocalizations.of(context)!.broadcastToAll),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
debugPrint('📤 [DrawingToolbar] Public Channel tapped');
|
debugPrint('📤 [DrawingToolbar] Public Channel tapped');
|
||||||
// Share BEFORE popping the navigator
|
// Share BEFORE popping the navigator
|
||||||
@@ -406,7 +407,7 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
(room) => ListTile(
|
(room) => ListTile(
|
||||||
leading: const Icon(Icons.meeting_room, color: Colors.green),
|
leading: const Icon(Icons.meeting_room, color: Colors.green),
|
||||||
title: Text(room.advName),
|
title: Text(room.advName),
|
||||||
subtitle: const Text('Stored permanently in room'),
|
subtitle: Text(AppLocalizations.of(context)!.storedPermanently),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
debugPrint('📤 [DrawingToolbar] Room ${room.advName} tapped');
|
debugPrint('📤 [DrawingToolbar] Room ${room.advName} tapped');
|
||||||
// Share BEFORE popping the navigator
|
// Share BEFORE popping the navigator
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import '../models/contact.dart';
|
import '../models/contact.dart';
|
||||||
import '../models/sar_marker.dart';
|
import '../models/sar_marker.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class MapMarkers {
|
class MapMarkers {
|
||||||
static List<Marker> createTeamMemberMarkers(
|
static List<Marker> createTeamMemberMarkers(
|
||||||
@@ -238,7 +239,7 @@ class MapMarkers {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text('Close'),
|
child: Text(AppLocalizations.of(context)!.close),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -272,7 +273,7 @@ class MapMarkers {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text('Close'),
|
child: Text(AppLocalizations.of(context)!.close),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import '../../providers/contacts_provider.dart';
|
|||||||
import '../../models/contact.dart';
|
import '../../models/contact.dart';
|
||||||
import '../../models/sar_marker.dart';
|
import '../../models/sar_marker.dart';
|
||||||
import '../../services/validation_service.dart';
|
import '../../services/validation_service.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
/// SAR Update Sheet - Modal bottom sheet for creating and sending SAR markers
|
/// 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
|
/// 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<SarUpdateSheet> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Send SAR Marker',
|
AppLocalizations.of(context)!.sendSarMarker,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: colorScheme.onSurface,
|
color: colorScheme.onSurface,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
@@ -645,7 +646,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
|||||||
final shouldContinue = await showDialog<bool>(
|
final shouldContinue = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Low Location Accuracy'),
|
title: Text(AppLocalizations.of(context)!.lowLocationAccuracy),
|
||||||
content: Text(
|
content: Text(
|
||||||
'Location accuracy is ±${_currentPosition!.accuracy!.round()}m. '
|
'Location accuracy is ±${_currentPosition!.accuracy!.round()}m. '
|
||||||
'This may not be accurate enough for SAR operations.\n\n'
|
'This may not be accurate enough for SAR operations.\n\n'
|
||||||
@@ -654,11 +655,11 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context, false),
|
onPressed: () => Navigator.pop(context, false),
|
||||||
child: const Text('Cancel'),
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context, true),
|
onPressed: () => Navigator.pop(context, true),
|
||||||
child: const Text('Continue'),
|
child: Text(AppLocalizations.of(context)!.continue_),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -688,9 +689,9 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.send, size: 20),
|
icon: const Icon(Icons.send, size: 20),
|
||||||
label: const Text(
|
label: Text(
|
||||||
'Send SAR Marker',
|
AppLocalizations.of(context)!.sendSarMarker,
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user