Add Slovenian localization for trail management and device configuration

- Updated `app_localizations_sl.dart` with new strings for trail management, device information, and error messages.
- Enhanced `app_sl.arb` with corresponding translations for the new strings.
- Refactored `device_config_screen.dart` to utilize localized strings for device information and error handling.
- Modified `map_tab.dart` to remove unused trail stats overlay.
- Improved `ble_response_handler.dart` to include additional debug information for parsed contacts and advertisements.
- Updated `trail_controls.dart` to use localized strings for trail management UI, including trail visibility toggle and confirmation dialogs.
This commit is contained in:
Janez T
2025-10-18 21:35:47 +02:00
parent 00b239ab26
commit da03592980
23 changed files with 14448 additions and 99 deletions

View File

@@ -17,7 +17,11 @@
"Bash(flutter analyze lib/main.dart)",
"Bash(flutter analyze lib/providers/app_provider.dart lib/services/meshcore_ble_service.dart)",
"Bash(flutter analyze lib/services/meshcore_ble_service.dart)",
"Bash(flutter analyze lib/services/meshcore_ble_service.dart lib/services/location_tracking_service.dart)"
"Bash(flutter analyze lib/services/meshcore_ble_service.dart lib/services/location_tracking_service.dart)",
"Bash(flutter analyze lib/widgets/map/trail_controls.dart lib/screens/map_tab.dart)",
"Bash(flutter analyze lib/screens/device_config_screen.dart)",
"Bash(flutter analyze lib/services/ble/ble_response_handler.dart)",
"Bash(flutter analyze)"
],
"deny": [],
"ask": []

Binary file not shown.

Binary file not shown.

View File

@@ -5,22 +5,22 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000218">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000178">
</testcase>
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="0.276257">
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="0.251467">
</testcase>
<testcase classname="fastlane.lanes" name="2: build_app" time="134.535005">
<testcase classname="fastlane.lanes" name="2: build_app" time="121.847246">
</testcase>
<testcase classname="fastlane.lanes" name="3: upload_to_testflight" time="9.956839">
<testcase classname="fastlane.lanes" name="3: upload_to_testflight" time="10.210838">
<failure message="/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/actions/actions_helper.rb:67:in &apos;Fastlane::Actions.execute_action&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:255:in &apos;block in Fastlane::Runner#execute_action&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:229:in &apos;Dir.chdir&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:229:in &apos;Fastlane::Runner#execute_action&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:157:in &apos;Fastlane::Runner#trigger_action_by_name&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/fast_file.rb:159:in &apos;Fastlane::FastFile#method_missing&apos;&#10;Fastfile:23:in &apos;block (2 levels) in Fastlane::FastFile#parsing_binding&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/lane.rb:41:in &apos;Fastlane::Lane#call&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:49:in &apos;block in Fastlane::Runner#execute&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:45:in &apos;Dir.chdir&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:45:in &apos;Fastlane::Runner#execute&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/lane_manager.rb:46:in &apos;Fastlane::LaneManager.cruise_lane&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/command_line_handler.rb:34:in &apos;Fastlane::CommandLineHandler.handle&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/commands_generator.rb:110:in &apos;block (2 levels) in Fastlane::CommandsGenerator#run&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/commander-4.6.0/lib/commander/command.rb:187:in &apos;Commander::Command#call&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/commander-4.6.0/lib/commander/command.rb:157:in &apos;Commander::Command#run&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/commander-4.6.0/lib/commander/runner.rb:444:in &apos;Commander::Runner#run_active_command&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb:124:in &apos;Commander::Runner#run!&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/commander-4.6.0/lib/commander/delegates.rb:18:in &apos;Commander::Delegates#run!&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/commands_generator.rb:363:in &apos;Fastlane::CommandsGenerator#run&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/commands_generator.rb:43:in &apos;Fastlane::CommandsGenerator.start&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/cli_tools_distributor.rb:123:in &apos;Fastlane::CLIToolsDistributor.take_off&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/bin/fastlane:23:in &apos;&lt;top (required)&gt;&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/bin/fastlane:25:in &apos;Kernel#load&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/bin/fastlane:25:in &apos;&lt;main&gt;&apos;&#10;&#10;Error uploading ipa file: &#10; [Application Loader Error Output]: The call to the altool completed with a non-zero exit status: 1. This indicates a failure." />

2100
lib/l10n/app_de.arb Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1886,5 +1886,215 @@
"offlineLayers": "Offline Layers",
"@offlineLayers": {
"description": "Section header for offline map layers (MBTiles)"
},
"locationTrail": "Location Trail",
"@locationTrail": {
"description": "Location trail title"
},
"showTrailOnMap": "Show Trail on Map",
"@showTrailOnMap": {
"description": "Toggle to show/hide trail on map"
},
"trailVisible": "Trail is visible on the map",
"@trailVisible": {
"description": "Trail visibility status - visible"
},
"trailHiddenRecording": "Trail is hidden (still recording)",
"@trailHiddenRecording": {
"description": "Trail visibility status - hidden but recording"
},
"distance": "Distance",
"@distance": {
"description": "Distance label"
},
"duration": "Duration",
"@duration": {
"description": "Duration label"
},
"points": "Points",
"@points": {
"description": "Trail points count label"
},
"clearTrail": "Clear Trail",
"@clearTrail": {
"description": "Button to clear location trail"
},
"clearTrailQuestion": "Clear Trail?",
"@clearTrailQuestion": {
"description": "Confirmation dialog title"
},
"clearTrailConfirmation": "Are you sure you want to clear the current location trail? This action cannot be undone.",
"@clearTrailConfirmation": {
"description": "Confirmation dialog message"
},
"noTrailRecorded": "No trail recorded yet",
"@noTrailRecorded": {
"description": "Message when no trail exists"
},
"startTrackingToRecord": "Start location tracking to record your trail",
"@startTrackingToRecord": {
"description": "Instructions to start trail recording"
},
"trailControls": "Trail Controls",
"@trailControls": {
"description": "Trail controls tooltip"
},
"deviceInformation": "Device Information",
"@deviceInformation": {
"description": "Device information section header"
},
"bleName": "BLE Name",
"@bleName": {
"description": "Bluetooth Low Energy device name label"
},
"meshName": "Mesh Name",
"@meshName": {
"description": "Mesh network name label"
},
"notSet": "Not set",
"@notSet": {
"description": "Label when a value is not set"
},
"model": "Model",
"@model": {
"description": "Device model label"
},
"version": "Version",
"@version": {
"description": "Version label"
},
"buildDate": "Build Date",
"@buildDate": {
"description": "Firmware build date label"
},
"firmware": "Firmware",
"@firmware": {
"description": "Firmware label"
},
"maxContacts": "Max Contacts",
"@maxContacts": {
"description": "Maximum contacts capacity label"
},
"maxChannels": "Max Channels",
"@maxChannels": {
"description": "Maximum channels capacity label"
},
"publicInfo": "Public Info",
"@publicInfo": {
"description": "Public information section header"
},
"meshNetworkName": "Mesh Network Name",
"@meshNetworkName": {
"description": "Mesh network name field label"
},
"nameBroadcastInMesh": "Name broadcast in mesh advertisements",
"@nameBroadcastInMesh": {
"description": "Helper text for mesh network name field"
},
"telemetryAndLocationSharing": "Telemetry & Location Sharing",
"@telemetryAndLocationSharing": {
"description": "Telemetry and location sharing toggle label"
},
"lat": "Lat",
"@lat": {
"description": "Latitude field label (short form)"
},
"lon": "Lon",
"@lon": {
"description": "Longitude field label (short form)"
},
"useCurrentLocation": "Use current location",
"@useCurrentLocation": {
"description": "Tooltip for use current location button"
},
"noneUnknown": "None/Unknown",
"@noneUnknown": {
"description": "Device type: none or unknown"
},
"chatNode": "Chat Node",
"@chatNode": {
"description": "Device type: chat node"
},
"repeater": "Repeater",
"@repeater": {
"description": "Device type: repeater"
},
"roomChannel": "Room/Channel",
"@roomChannel": {
"description": "Device type: room or channel"
},
"typeNumber": "Type {number}",
"@typeNumber": {
"description": "Generic device type with number",
"placeholders": {
"number": {
"type": "int"
}
}
},
"copiedToClipboardShort": "Copied {label} to clipboard",
"@copiedToClipboardShort": {
"description": "Short success message when copying to clipboard",
"placeholders": {
"label": {
"type": "String"
}
}
},
"failedToSave": "Failed to save: {error}",
"@failedToSave": {
"description": "Generic error message for save failures",
"placeholders": {
"error": {
"type": "String"
}
}
},
"failedToGetLocation": "Failed to get location: {error}",
"@failedToGetLocation": {
"description": "Error message when getting location fails",
"placeholders": {
"error": {
"type": "String"
}
}
}
}

2095
lib/l10n/app_es.arb Normal file

File diff suppressed because it is too large Load Diff

2100
lib/l10n/app_fr.arb Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -632,5 +632,81 @@
"onlineLayers": "Mrežni slojevi",
"offlineLayers": "Offline slojevi"
"offlineLayers": "Offline slojevi",
"locationTrail": "Putanja lokacije",
"showTrailOnMap": "Prikaži putanju na karti",
"trailVisible": "Putanja je vidljiva na karti",
"trailHiddenRecording": "Putanja je skrivena (još se snima)",
"distance": "Udaljenost",
"duration": "Trajanje",
"points": "Točke",
"clearTrail": "Obriši putanju",
"clearTrailQuestion": "Obrisati putanju?",
"clearTrailConfirmation": "Jeste li sigurni da želite obrisati trenutnu putanju lokacije? Ova radnja se ne može poništiti.",
"noTrailRecorded": "Još nije snimljena putanja",
"startTrackingToRecord": "Pokrenite praćenje lokacije za snimanje putanje",
"trailControls": "Upravljanje putanjom",
"deviceInformation": "Informacije o uređaju",
"bleName": "BLE naziv",
"meshName": "Mesh naziv",
"notSet": "Nije postavljeno",
"model": "Model",
"version": "Verzija",
"buildDate": "Datum izgradnje",
"firmware": "Firmware",
"maxContacts": "Maks. kontakata",
"maxChannels": "Maks. kanala",
"publicInfo": "Javne informacije",
"meshNetworkName": "Naziv mesh mreže",
"nameBroadcastInMesh": "Naziv koji se emitira u mesh oglasima",
"telemetryAndLocationSharing": "Telemetrija i dijeljenje lokacije",
"lat": "Šir",
"lon": "Dug",
"useCurrentLocation": "Koristi trenutnu lokaciju",
"noneUnknown": "Nema/Nepoznato",
"chatNode": "Čvorište za razgovor",
"repeater": "Repetitor",
"roomChannel": "Soba/Kanal",
"typeNumber": "Tip {number}",
"copiedToClipboardShort": "Kopirano {label} u međuspremnik",
"failedToSave": "Neuspjelo spremanje: {error}",
"failedToGetLocation": "Neuspjelo dohvaćanje lokacije: {error}"
}

2100
lib/l10n/app_it.arb Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -5,8 +5,12 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/intl.dart' as intl;
import 'app_localizations_de.dart';
import 'app_localizations_en.dart';
import 'app_localizations_es.dart';
import 'app_localizations_fr.dart';
import 'app_localizations_hr.dart';
import 'app_localizations_it.dart';
import 'app_localizations_sl.dart';
// ignore_for_file: type=lint
@@ -95,8 +99,12 @@ abstract class AppLocalizations {
/// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[
Locale('de'),
Locale('en'),
Locale('es'),
Locale('fr'),
Locale('hr'),
Locale('it'),
Locale('sl'),
];
@@ -2024,6 +2032,228 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Offline Layers'**
String get offlineLayers;
/// Location trail title
///
/// In en, this message translates to:
/// **'Location Trail'**
String get locationTrail;
/// Toggle to show/hide trail on map
///
/// In en, this message translates to:
/// **'Show Trail on Map'**
String get showTrailOnMap;
/// Trail visibility status - visible
///
/// In en, this message translates to:
/// **'Trail is visible on the map'**
String get trailVisible;
/// Trail visibility status - hidden but recording
///
/// In en, this message translates to:
/// **'Trail is hidden (still recording)'**
String get trailHiddenRecording;
/// Duration label
///
/// In en, this message translates to:
/// **'Duration'**
String get duration;
/// Trail points count label
///
/// In en, this message translates to:
/// **'Points'**
String get points;
/// Button to clear location trail
///
/// In en, this message translates to:
/// **'Clear Trail'**
String get clearTrail;
/// Confirmation dialog title
///
/// In en, this message translates to:
/// **'Clear Trail?'**
String get clearTrailQuestion;
/// Confirmation dialog message
///
/// In en, this message translates to:
/// **'Are you sure you want to clear the current location trail? This action cannot be undone.'**
String get clearTrailConfirmation;
/// Message when no trail exists
///
/// In en, this message translates to:
/// **'No trail recorded yet'**
String get noTrailRecorded;
/// Instructions to start trail recording
///
/// In en, this message translates to:
/// **'Start location tracking to record your trail'**
String get startTrackingToRecord;
/// Trail controls tooltip
///
/// In en, this message translates to:
/// **'Trail Controls'**
String get trailControls;
/// Device information section header
///
/// In en, this message translates to:
/// **'Device Information'**
String get deviceInformation;
/// Bluetooth Low Energy device name label
///
/// In en, this message translates to:
/// **'BLE Name'**
String get bleName;
/// Mesh network name label
///
/// In en, this message translates to:
/// **'Mesh Name'**
String get meshName;
/// Label when a value is not set
///
/// In en, this message translates to:
/// **'Not set'**
String get notSet;
/// Device model label
///
/// In en, this message translates to:
/// **'Model'**
String get model;
/// Version label
///
/// In en, this message translates to:
/// **'Version'**
String get version;
/// Firmware build date label
///
/// In en, this message translates to:
/// **'Build Date'**
String get buildDate;
/// Firmware label
///
/// In en, this message translates to:
/// **'Firmware'**
String get firmware;
/// Maximum contacts capacity label
///
/// In en, this message translates to:
/// **'Max Contacts'**
String get maxContacts;
/// Maximum channels capacity label
///
/// In en, this message translates to:
/// **'Max Channels'**
String get maxChannels;
/// Public information section header
///
/// In en, this message translates to:
/// **'Public Info'**
String get publicInfo;
/// Mesh network name field label
///
/// In en, this message translates to:
/// **'Mesh Network Name'**
String get meshNetworkName;
/// Helper text for mesh network name field
///
/// In en, this message translates to:
/// **'Name broadcast in mesh advertisements'**
String get nameBroadcastInMesh;
/// Telemetry and location sharing toggle label
///
/// In en, this message translates to:
/// **'Telemetry & Location Sharing'**
String get telemetryAndLocationSharing;
/// Latitude field label (short form)
///
/// In en, this message translates to:
/// **'Lat'**
String get lat;
/// Longitude field label (short form)
///
/// In en, this message translates to:
/// **'Lon'**
String get lon;
/// Tooltip for use current location button
///
/// In en, this message translates to:
/// **'Use current location'**
String get useCurrentLocation;
/// Device type: none or unknown
///
/// In en, this message translates to:
/// **'None/Unknown'**
String get noneUnknown;
/// Device type: chat node
///
/// In en, this message translates to:
/// **'Chat Node'**
String get chatNode;
/// Device type: repeater
///
/// In en, this message translates to:
/// **'Repeater'**
String get repeater;
/// Device type: room or channel
///
/// In en, this message translates to:
/// **'Room/Channel'**
String get roomChannel;
/// Generic device type with number
///
/// In en, this message translates to:
/// **'Type {number}'**
String typeNumber(int number);
/// Short success message when copying to clipboard
///
/// In en, this message translates to:
/// **'Copied {label} to clipboard'**
String copiedToClipboardShort(String label);
/// Generic error message for save failures
///
/// In en, this message translates to:
/// **'Failed to save: {error}'**
String failedToSave(String error);
/// Error message when getting location fails
///
/// In en, this message translates to:
/// **'Failed to get location: {error}'**
String failedToGetLocation(String error);
}
class _AppLocalizationsDelegate
@@ -2036,8 +2266,15 @@ class _AppLocalizationsDelegate
}
@override
bool isSupported(Locale locale) =>
<String>['en', 'hr', 'sl'].contains(locale.languageCode);
bool isSupported(Locale locale) => <String>[
'de',
'en',
'es',
'fr',
'hr',
'it',
'sl',
].contains(locale.languageCode);
@override
bool shouldReload(_AppLocalizationsDelegate old) => false;
@@ -2046,10 +2283,18 @@ class _AppLocalizationsDelegate
AppLocalizations lookupAppLocalizations(Locale locale) {
// Lookup logic when only language code is specified.
switch (locale.languageCode) {
case 'de':
return AppLocalizationsDe();
case 'en':
return AppLocalizationsEn();
case 'es':
return AppLocalizationsEs();
case 'fr':
return AppLocalizationsFr();
case 'hr':
return AppLocalizationsHr();
case 'it':
return AppLocalizationsIt();
case 'sl':
return AppLocalizationsSl();
}

File diff suppressed because it is too large Load Diff

View File

@@ -1104,4 +1104,125 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get offlineLayers => 'Offline Layers';
@override
String get locationTrail => 'Location Trail';
@override
String get showTrailOnMap => 'Show Trail on Map';
@override
String get trailVisible => 'Trail is visible on the map';
@override
String get trailHiddenRecording => 'Trail is hidden (still recording)';
@override
String get duration => 'Duration';
@override
String get points => 'Points';
@override
String get clearTrail => 'Clear Trail';
@override
String get clearTrailQuestion => 'Clear Trail?';
@override
String get clearTrailConfirmation =>
'Are you sure you want to clear the current location trail? This action cannot be undone.';
@override
String get noTrailRecorded => 'No trail recorded yet';
@override
String get startTrackingToRecord =>
'Start location tracking to record your trail';
@override
String get trailControls => 'Trail Controls';
@override
String get deviceInformation => 'Device Information';
@override
String get bleName => 'BLE Name';
@override
String get meshName => 'Mesh Name';
@override
String get notSet => 'Not set';
@override
String get model => 'Model';
@override
String get version => 'Version';
@override
String get buildDate => 'Build Date';
@override
String get firmware => 'Firmware';
@override
String get maxContacts => 'Max Contacts';
@override
String get maxChannels => 'Max Channels';
@override
String get publicInfo => 'Public Info';
@override
String get meshNetworkName => 'Mesh Network Name';
@override
String get nameBroadcastInMesh => 'Name broadcast in mesh advertisements';
@override
String get telemetryAndLocationSharing => 'Telemetry & Location Sharing';
@override
String get lat => 'Lat';
@override
String get lon => 'Lon';
@override
String get useCurrentLocation => 'Use current location';
@override
String get noneUnknown => 'None/Unknown';
@override
String get chatNode => 'Chat Node';
@override
String get repeater => 'Repeater';
@override
String get roomChannel => 'Room/Channel';
@override
String typeNumber(int number) {
return 'Type $number';
}
@override
String copiedToClipboardShort(String label) {
return 'Copied $label to clipboard';
}
@override
String failedToSave(String error) {
return 'Failed to save: $error';
}
@override
String failedToGetLocation(String error) {
return 'Failed to get location: $error';
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1105,4 +1105,125 @@ class AppLocalizationsHr extends AppLocalizations {
@override
String get offlineLayers => 'Offline slojevi';
@override
String get locationTrail => 'Putanja lokacije';
@override
String get showTrailOnMap => 'Prikaži putanju na karti';
@override
String get trailVisible => 'Putanja je vidljiva na karti';
@override
String get trailHiddenRecording => 'Putanja je skrivena (još se snima)';
@override
String get duration => 'Trajanje';
@override
String get points => 'Točke';
@override
String get clearTrail => 'Obriši putanju';
@override
String get clearTrailQuestion => 'Obrisati putanju?';
@override
String get clearTrailConfirmation =>
'Jeste li sigurni da želite obrisati trenutnu putanju lokacije? Ova radnja se ne može poništiti.';
@override
String get noTrailRecorded => 'Još nije snimljena putanja';
@override
String get startTrackingToRecord =>
'Pokrenite praćenje lokacije za snimanje putanje';
@override
String get trailControls => 'Upravljanje putanjom';
@override
String get deviceInformation => 'Informacije o uređaju';
@override
String get bleName => 'BLE naziv';
@override
String get meshName => 'Mesh naziv';
@override
String get notSet => 'Nije postavljeno';
@override
String get model => 'Model';
@override
String get version => 'Verzija';
@override
String get buildDate => 'Datum izgradnje';
@override
String get firmware => 'Firmware';
@override
String get maxContacts => 'Maks. kontakata';
@override
String get maxChannels => 'Maks. kanala';
@override
String get publicInfo => 'Javne informacije';
@override
String get meshNetworkName => 'Naziv mesh mreže';
@override
String get nameBroadcastInMesh => 'Naziv koji se emitira u mesh oglasima';
@override
String get telemetryAndLocationSharing => 'Telemetrija i dijeljenje lokacije';
@override
String get lat => 'Šir';
@override
String get lon => 'Dug';
@override
String get useCurrentLocation => 'Koristi trenutnu lokaciju';
@override
String get noneUnknown => 'Nema/Nepoznato';
@override
String get chatNode => 'Čvorište za razgovor';
@override
String get repeater => 'Repetitor';
@override
String get roomChannel => 'Soba/Kanal';
@override
String typeNumber(int number) {
return 'Tip $number';
}
@override
String copiedToClipboardShort(String label) {
return 'Kopirano $label u međuspremnik';
}
@override
String failedToSave(String error) {
return 'Neuspjelo spremanje: $error';
}
@override
String failedToGetLocation(String error) {
return 'Neuspjelo dohvaćanje lokacije: $error';
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1108,4 +1108,125 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String get offlineLayers => 'Brezpovezne plasti';
@override
String get locationTrail => 'Sledilna pot';
@override
String get showTrailOnMap => 'Prikaži pot na zemljevidu';
@override
String get trailVisible => 'Pot je vidna na zemljevidu';
@override
String get trailHiddenRecording => 'Pot je skrita (še se snema)';
@override
String get duration => 'Trajanje';
@override
String get points => 'Točke';
@override
String get clearTrail => 'Počisti pot';
@override
String get clearTrailQuestion => 'Počisti pot?';
@override
String get clearTrailConfirmation =>
'Ste prepričani, da želite počistiti trenutno sledilno pot? Tega dejanja ni mogoče razveljaviti.';
@override
String get noTrailRecorded => 'Še ni posnete poti';
@override
String get startTrackingToRecord =>
'Začnite sledenje lokacije za snemanje poti';
@override
String get trailControls => 'Nadzor poti';
@override
String get deviceInformation => 'Informacije o napravi';
@override
String get bleName => 'BLE ime';
@override
String get meshName => 'Mesh ime';
@override
String get notSet => 'Ni nastavljeno';
@override
String get model => 'Model';
@override
String get version => 'Različica';
@override
String get buildDate => 'Datum izdelave';
@override
String get firmware => 'Vdelana programska oprema';
@override
String get maxContacts => 'Maks. stikov';
@override
String get maxChannels => 'Maks. kanalov';
@override
String get publicInfo => 'Javne informacije';
@override
String get meshNetworkName => 'Ime mesh omrežja';
@override
String get nameBroadcastInMesh => 'Ime, ki se oddaja v mesh oglasih';
@override
String get telemetryAndLocationSharing => 'Telemetrija in deljenje lokacije';
@override
String get lat => 'Šir';
@override
String get lon => 'Dol';
@override
String get useCurrentLocation => 'Uporabi trenutno lokacijo';
@override
String get noneUnknown => 'Brez/Neznano';
@override
String get chatNode => 'Vozlišče za klepet';
@override
String get repeater => 'Ojačevalnik';
@override
String get roomChannel => 'Soba/Kanal';
@override
String typeNumber(int number) {
return 'Tip $number';
}
@override
String copiedToClipboardShort(String label) {
return 'Kopirano $label v odložišče';
}
@override
String failedToSave(String error) {
return 'Shranjevanje ni uspelo: $error';
}
@override
String failedToGetLocation(String error) {
return 'Pridobivanje lokacije ni uspelo: $error';
}
}

View File

@@ -632,5 +632,81 @@
"onlineLayers": "Spletne plasti",
"offlineLayers": "Brezpovezne plasti"
"offlineLayers": "Brezpovezne plasti",
"locationTrail": "Sledilna pot",
"showTrailOnMap": "Prikaži pot na zemljevidu",
"trailVisible": "Pot je vidna na zemljevidu",
"trailHiddenRecording": "Pot je skrita (še se snema)",
"distance": "Razdalja",
"duration": "Trajanje",
"points": "Točke",
"clearTrail": "Počisti pot",
"clearTrailQuestion": "Počisti pot?",
"clearTrailConfirmation": "Ste prepričani, da želite počistiti trenutno sledilno pot? Tega dejanja ni mogoče razveljaviti.",
"noTrailRecorded": "Še ni posnete poti",
"startTrackingToRecord": "Začnite sledenje lokacije za snemanje poti",
"trailControls": "Nadzor poti",
"deviceInformation": "Informacije o napravi",
"bleName": "BLE ime",
"meshName": "Mesh ime",
"notSet": "Ni nastavljeno",
"model": "Model",
"version": "Različica",
"buildDate": "Datum izdelave",
"firmware": "Vdelana programska oprema",
"maxContacts": "Maks. stikov",
"maxChannels": "Maks. kanalov",
"publicInfo": "Javne informacije",
"meshNetworkName": "Ime mesh omrežja",
"nameBroadcastInMesh": "Ime, ki se oddaja v mesh oglasih",
"telemetryAndLocationSharing": "Telemetrija in deljenje lokacije",
"lat": "Šir",
"lon": "Dol",
"useCurrentLocation": "Uporabi trenutno lokacijo",
"noneUnknown": "Brez/Neznano",
"chatNode": "Vozlišče za klepet",
"repeater": "Ojačevalnik",
"roomChannel": "Soba/Kanal",
"typeNumber": "Tip {number}",
"copiedToClipboardShort": "Kopirano {label} v odložišče",
"failedToSave": "Shranjevanje ni uspelo: {error}",
"failedToGetLocation": "Pridobivanje lokacije ni uspelo: {error}"
}

View File

@@ -209,7 +209,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to save: $e'),
content: Text(AppLocalizations.of(context)!.failedToSave(e.toString())),
backgroundColor: Colors.red,
),
);
@@ -282,7 +282,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to save: $e'),
content: Text(AppLocalizations.of(context)!.failedToSave(e.toString())),
backgroundColor: Colors.red,
),
);
@@ -367,7 +367,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
if (context.mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Failed to get location: $e')));
).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.failedToGetLocation(e.toString()))));
}
}
}
@@ -446,35 +446,35 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Device Information',
AppLocalizations.of(context)!.deviceInformation,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
_InfoRow('BLE Name', deviceInfo.deviceName ?? 'Unknown'),
_InfoRow('Mesh Name', deviceInfo.selfName ?? 'Not set'),
_InfoRow('Type', _getDeviceTypeString(deviceInfo.deviceType)),
_InfoRow('Model', deviceInfo.manufacturerModel ?? 'Unknown'),
_InfoRow('Version', deviceInfo.semanticVersion ?? 'Unknown'),
_InfoRow(AppLocalizations.of(context)!.bleName, deviceInfo.deviceName ?? AppLocalizations.of(context)!.unknown),
_InfoRow(AppLocalizations.of(context)!.meshName, deviceInfo.selfName ?? AppLocalizations.of(context)!.notSet),
_InfoRow(AppLocalizations.of(context)!.type, _getDeviceTypeString(context, deviceInfo.deviceType)),
_InfoRow(AppLocalizations.of(context)!.model, deviceInfo.manufacturerModel ?? AppLocalizations.of(context)!.unknown),
_InfoRow(AppLocalizations.of(context)!.version, deviceInfo.semanticVersion ?? AppLocalizations.of(context)!.unknown),
_InfoRow(
'Build Date',
deviceInfo.firmwareBuildDate ?? 'Unknown',
AppLocalizations.of(context)!.buildDate,
deviceInfo.firmwareBuildDate ?? AppLocalizations.of(context)!.unknown,
),
_InfoRow(
'Firmware',
AppLocalizations.of(context)!.firmware,
'v${deviceInfo.firmwareVersion?.toString() ?? "?"}',
),
_InfoRow(
'Max Contacts',
deviceInfo.maxContacts?.toString() ?? 'Unknown',
AppLocalizations.of(context)!.maxContacts,
deviceInfo.maxContacts?.toString() ?? AppLocalizations.of(context)!.unknown,
),
_InfoRow(
'Max Channels',
deviceInfo.maxChannels?.toString() ?? 'Unknown',
AppLocalizations.of(context)!.maxChannels,
deviceInfo.maxChannels?.toString() ?? AppLocalizations.of(context)!.unknown,
),
_CopyableInfoRow(
'Public Key',
AppLocalizations.of(context)!.publicKey,
_getPublicKeyHex(deviceInfo.publicKey),
),
],
@@ -495,7 +495,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Public Info',
AppLocalizations.of(context)!.publicInfo,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
@@ -518,10 +518,10 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
// Mesh Network Name
TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Mesh Network Name',
border: OutlineInputBorder(),
helperText: 'Name broadcast in mesh advertisements',
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.meshNetworkName,
border: const OutlineInputBorder(),
helperText: AppLocalizations.of(context)!.nameBroadcastInMesh,
),
),
const SizedBox(height: 8),
@@ -531,7 +531,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
children: [
Expanded(
child: Text(
'Telemetry & Location Sharing',
AppLocalizations.of(context)!.telemetryAndLocationSharing,
style: theme.textTheme.bodyMedium,
),
),
@@ -554,9 +554,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
Expanded(
child: TextField(
controller: _latController,
decoration: const InputDecoration(
labelText: 'Lat',
border: OutlineInputBorder(),
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.lat,
border: const OutlineInputBorder(),
isDense: true,
),
keyboardType: const TextInputType.numberWithOptions(
@@ -569,9 +569,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
Expanded(
child: TextField(
controller: _lonController,
decoration: const InputDecoration(
labelText: 'Lon',
border: OutlineInputBorder(),
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.lon,
border: const OutlineInputBorder(),
isDense: true,
),
keyboardType: const TextInputType.numberWithOptions(
@@ -584,7 +584,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
IconButton.filled(
onPressed: _useCurrentLocation,
icon: const Icon(Icons.my_location, size: 20),
tooltip: 'Use current location',
tooltip: AppLocalizations.of(context)!.useCurrentLocation,
),
],
),
@@ -729,19 +729,19 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
);
}
String _getDeviceTypeString(int? deviceType) {
if (deviceType == null) return 'Unknown';
String _getDeviceTypeString(BuildContext context, int? deviceType) {
if (deviceType == null) return AppLocalizations.of(context)!.unknown;
switch (deviceType) {
case 0:
return 'None/Unknown';
return AppLocalizations.of(context)!.noneUnknown;
case 1:
return 'Chat Node';
return AppLocalizations.of(context)!.chatNode;
case 2:
return 'Repeater';
return AppLocalizations.of(context)!.repeater;
case 3:
return 'Room/Channel';
return AppLocalizations.of(context)!.roomChannel;
default:
return 'Type $deviceType';
return AppLocalizations.of(context)!.typeNumber(deviceType);
}
}
@@ -815,7 +815,7 @@ class _CopyableInfoRow extends StatelessWidget {
Clipboard.setData(ClipboardData(text: value));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Copied $label to clipboard'),
content: Text(AppLocalizations.of(context)!.copiedToClipboardShort(label)),
duration: const Duration(seconds: 2),
backgroundColor: Colors.green,
),

View File

@@ -1353,9 +1353,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
objectCount: messagesProvider.objectMarkers.length,
),
),
// Trail stats overlay (top-left, below legend if shown)
if (!_isFullscreen)
const TrailStatsOverlay(),
// Map controls - right side (hidden in fullscreen mode)
if (!_isFullscreen)
Positioned(

View File

@@ -245,7 +245,8 @@ class BleResponseHandler {
void _handleContact(BufferReader reader) {
try {
final contact = FrameParser.parseContact(reader);
print(' ✅ [Contact] Parsed successfully');
print(' ✅ [Contact] Parsed successfully: ${contact.advName}');
print(' outPathLen: ${contact.outPathLen} (${contact.pathDescription})');
_pendingContacts.add(contact);
onContactReceived?.call(contact);
} catch (e) {
@@ -468,7 +469,8 @@ class BleResponseHandler {
void _handleNewAdvert(BufferReader reader) {
try {
final contact = FrameParser.parseContact(reader);
print(' ✅ [NewAdvert] Parsed successfully');
print(' ✅ [NewAdvert] Parsed successfully: ${contact.advName}');
print(' outPathLen: ${contact.outPathLen} (${contact.pathDescription})');
onContactReceived?.call(contact);
} catch (e) {
print(' ❌ [NewAdvert] Parsing error: $e');

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../providers/map_provider.dart';
import '../../l10n/app_localizations.dart';
/// Trail management controls widget
class TrailControls extends StatelessWidget {
@@ -8,31 +9,52 @@ class TrailControls extends StatelessWidget {
void _showTrailMenu(BuildContext context) {
final mapProvider = Provider.of<MapProvider>(context, listen: false);
final l10n = AppLocalizations.of(context)!;
showModalBottomSheet(
context: context,
builder: (context) => Container(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
const Icon(Icons.timeline, size: 24),
const SizedBox(width: 12),
const Text(
'Location Trail',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
isScrollControlled: true,
builder: (context) => StatefulBuilder(
builder: (context, setModalState) => Container(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
const Icon(Icons.timeline, size: 24),
const SizedBox(width: 12),
Text(
l10n.locationTrail,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 20),
],
),
const SizedBox(height: 20),
// Trail stats
// Trail visibility toggle
SwitchListTile(
secondary: const Icon(Icons.visibility),
title: Text(l10n.showTrailOnMap),
subtitle: Text(
mapProvider.isTrailVisible
? l10n.trailVisible
: l10n.trailHiddenRecording,
),
value: mapProvider.isTrailVisible,
onChanged: (value) {
mapProvider.toggleTrailVisibility();
setModalState(() {}); // Update modal UI
},
),
const Divider(),
const SizedBox(height: 8),
// Trail stats
if (mapProvider.currentTrail != null && mapProvider.currentTrail!.points.isNotEmpty)
Container(
padding: const EdgeInsets.all(12),
@@ -46,19 +68,19 @@ class TrailControls extends StatelessWidget {
children: [
_buildStatRow(
icon: Icons.straighten,
label: 'Distance',
label: l10n.distance,
value: _formatDistance(mapProvider.totalTrailDistance),
),
const SizedBox(height: 8),
_buildStatRow(
icon: Icons.access_time,
label: 'Duration',
label: l10n.duration,
value: _formatDuration(mapProvider.trailDuration),
),
const SizedBox(height: 8),
_buildStatRow(
icon: Icons.place,
label: 'Points',
label: l10n.points,
value: '${mapProvider.currentTrail!.points.length}',
),
],
@@ -71,10 +93,10 @@ class TrailControls extends StatelessWidget {
if (mapProvider.currentTrail != null && mapProvider.currentTrail!.points.isNotEmpty)
ElevatedButton.icon(
onPressed: () {
_showClearConfirmation(context, mapProvider);
_showClearConfirmation(context, mapProvider, l10n);
},
icon: const Icon(Icons.delete_outline),
label: const Text('Clear Trail'),
label: Text(l10n.clearTrail),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
@@ -84,24 +106,24 @@ class TrailControls extends StatelessWidget {
// No trail message
if (mapProvider.currentTrail == null || mapProvider.currentTrail!.points.isEmpty)
const Padding(
padding: EdgeInsets.all(16),
Padding(
padding: const EdgeInsets.all(16),
child: Center(
child: Column(
children: [
Icon(Icons.timeline, size: 48, color: Colors.grey),
SizedBox(height: 8),
const Icon(Icons.timeline, size: 48, color: Colors.grey),
const SizedBox(height: 8),
Text(
'No trail recorded yet',
style: TextStyle(
l10n.noTrailRecorded,
style: const TextStyle(
color: Colors.grey,
fontSize: 16,
),
),
SizedBox(height: 8),
const SizedBox(height: 8),
Text(
'Start location tracking to record your trail',
style: TextStyle(
l10n.startTrackingToRecord,
style: const TextStyle(
color: Colors.grey,
fontSize: 12,
),
@@ -115,28 +137,27 @@ class TrailControls extends StatelessWidget {
const SizedBox(height: 8),
// Close button
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
),
],
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(l10n.close),
),
],
),
),
),
);
}
void _showClearConfirmation(BuildContext context, MapProvider mapProvider) {
void _showClearConfirmation(BuildContext context, MapProvider mapProvider, AppLocalizations l10n) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Clear Trail?'),
content: const Text(
'Are you sure you want to clear the current location trail? This action cannot be undone.',
),
title: Text(l10n.clearTrailQuestion),
content: Text(l10n.clearTrailConfirmation),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
child: Text(l10n.cancel),
),
TextButton(
onPressed: () {
@@ -145,7 +166,7 @@ class TrailControls extends StatelessWidget {
Navigator.pop(context); // Close bottom sheet
},
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('Clear'),
child: Text(l10n.clearTrail),
),
],
),
@@ -204,9 +225,10 @@ class TrailControls extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return FloatingActionButton.small(
heroTag: 'trail_controls',
tooltip: 'Trail Controls',
tooltip: l10n.trailControls,
onPressed: () => _showTrailMenu(context),
child: const Icon(Icons.timeline),
);