diff --git a/.claude/settings.local.json b/.claude/settings.local.json index b3d4bbd..7c775e4 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,54 +1,14 @@ { "permissions": { "allow": [ - "Bash(flutter analyze:*)", - "Bash(flutter devices:*)", - "Bash(flutter build:*)", - "Bash(flutter install:*)", - "Bash(open:*)", - "Bash(xcrun devicectl device uninstall app:*)", - "Bash(pod repo update:*)", - "Bash(pod install)", - "Bash(flutter clean:*)", - "Bash(flutter pub get:*)", - "Bash(flutter run:*)", - "Bash(nc:*)", - "Bash(pkill:*)", - "WebFetch(domain:github.com)", - "WebFetch(domain:raw.githubusercontent.com)", - "Bash(dart run:*)", - "Bash(dart test_sar_debug.dart:*)", - "Read(//Users/dz0ny/meshcore-sar/MeshCore/**)", - "Bash(cat:*)", - "Read(//Users/dz0ny/.pub-cache/hosted/pub.dev/**)", - "Bash(find:*)", - "Read(//Users/dz0ny/meshcore-sar/**)", - "Bash(git grep:*)", - "Bash(dart analyze:*)", - "Bash(flutter logs --clear)", - "Bash(for file in /Users/dz0ny/meshcore-sar/meshcore_sar_app/lib/providers/contacts_provider.dart )", - "Bash(/Users/dz0ny/meshcore-sar/meshcore_sar_app/lib/providers/connection_provider.dart )", - "Bash(/Users/dz0ny/meshcore-sar/meshcore_sar_app/lib/providers/messages_provider.dart )", - "Bash(/Users/dz0ny/meshcore-sar/meshcore_sar_app/lib/screens/messages_tab.dart )", - "Bash(/Users/dz0ny/meshcore-sar/meshcore_sar_app/lib/screens/map_management_screen.dart )", - "Bash(/Users/dz0ny/meshcore-sar/meshcore_sar_app/lib/screens/home_screen.dart )", - "Bash(/Users/dz0ny/meshcore-sar/meshcore_sar_app/lib/services/ble/ble_connection_manager.dart )", - "Bash(/Users/dz0ny/meshcore-sar/meshcore_sar_app/lib/services/ble/ble_response_handler.dart )", - "Bash(/Users/dz0ny/meshcore-sar/meshcore_sar_app/lib/services/ble/ble_command_sender.dart )", - "Bash(/Users/dz0ny/meshcore-sar/meshcore_sar_app/lib/services/meshcore_ble_service.dart )", - "Bash(/Users/dz0ny/meshcore-sar/meshcore_sar_app/lib/services/contact_storage_service.dart )", - "Bash(/Users/dz0ny/meshcore-sar/meshcore_sar_app/lib/services/message_storage_service.dart )", - "Bash(/Users/dz0ny/meshcore-sar/meshcore_sar_app/lib/services/cayenne_lpp_parser.dart )", - "Bash(/Users/dz0ny/meshcore-sar/meshcore_sar_app/lib/services/tile_cache_service.dart )", - "Bash(/Users/dz0ny/meshcore-sar/meshcore_sar_app/lib/services/background_location_service.dart )", - "Bash(/Users/dz0ny/meshcore-sar/meshcore_sar_app/lib/widgets/contacts/room_login_sheet.dart)", - "Bash(do)", - "Bash(if grep -q \"debugPrint(\" \"$file\")", - "Bash(! grep -q \"import.*debug_print.dart\" \"$file\")", - "Bash(then)", - "Bash(echo:*)", - "Bash(fi)", - "Bash(done)" + "Bash(flutter gen-l10n:*)", + "Bash(flutter pub run:*)", + "Bash(flutter pub run build_runner build:*)", + "Bash(flutter packages pub run intl_translation:generate_from_arb:*)", + "Bash(dart pub global activate:*)", + "Bash(dart pub global run intl_utils:generate:*)", + "Bash(dart pub global deactivate:*)", + "Bash(flutter precache:*)" ], "deny": [], "ask": [] diff --git a/l10n.yaml b/l10n.yaml new file mode 100644 index 0000000..15338f2 --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,3 @@ +arb-dir: lib/l10n +template-arb-file: app_en.arb +output-localization-file: app_localizations.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb new file mode 100644 index 0000000..36e6f66 --- /dev/null +++ b/lib/l10n/app_en.arb @@ -0,0 +1,461 @@ +{ + "@@locale": "en", + + "appTitle": "MeshCore SAR", + "@appTitle": { + "description": "The application title" + }, + + "messages": "Messages", + "@messages": { + "description": "Messages tab label" + }, + + "contacts": "Contacts", + "@contacts": { + "description": "Contacts tab label" + }, + + "map": "Map", + "@map": { + "description": "Map tab label" + }, + + "settings": "Settings", + "@settings": { + "description": "Settings screen title" + }, + + "connect": "Connect", + "@connect": { + "description": "Connect button label" + }, + + "disconnect": "Disconnect", + "@disconnect": { + "description": "Disconnect button label" + }, + + "scanningForDevices": "Scanning for devices...", + "@scanningForDevices": { + "description": "Text shown when scanning for BLE devices" + }, + + "noDevicesFound": "No devices found", + "@noDevicesFound": { + "description": "Text shown when no BLE devices are found" + }, + + "scanAgain": "Scan Again", + "@scanAgain": { + "description": "Button to restart BLE scanning" + }, + + "tapToConnect": "Tap to connect", + "@tapToConnect": { + "description": "Subtitle text for device in scan list" + }, + + "deviceNotConnected": "Device not connected", + "@deviceNotConnected": { + "description": "Error message when device is not connected" + }, + + "locationPermissionDenied": "Location permission denied", + "@locationPermissionDenied": { + "description": "Error when location permission is denied" + }, + + "locationPermissionPermanentlyDenied": "Location permission permanently denied. Please enable in Settings.", + "@locationPermissionPermanentlyDenied": { + "description": "Error when location permission is permanently denied" + }, + + "locationServicesDisabled": "Location services are disabled. Please enable them in Settings.", + "@locationServicesDisabled": { + "description": "Error when location services are disabled" + }, + + "failedToGetGpsLocation": "Failed to get GPS location", + "@failedToGetGpsLocation": { + "description": "Error when GPS location cannot be obtained" + }, + + "advertisedAtLocation": "Advertised at {latitude}, {longitude}", + "@advertisedAtLocation": { + "description": "Success message showing advertised location", + "placeholders": { + "latitude": { + "type": "String" + }, + "longitude": { + "type": "String" + } + } + }, + + "failedToAdvertise": "Failed to advertise: {error}", + "@failedToAdvertise": { + "description": "Error message for failed advertisement", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "reconnecting": "Reconnecting... ({attempt}/{max})", + "@reconnecting": { + "description": "Text shown during reconnection attempts", + "placeholders": { + "attempt": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + + "cancelReconnection": "Cancel reconnection", + "@cancelReconnection": { + "description": "Tooltip for cancel reconnection button" + }, + + "mapManagement": "Map Management", + "@mapManagement": { + "description": "Menu item for map management" + }, + + "general": "General", + "@general": { + "description": "General settings section header" + }, + + "theme": "Theme", + "@theme": { + "description": "Theme setting label" + }, + + "chooseTheme": "Choose Theme", + "@chooseTheme": { + "description": "Theme selection dialog title" + }, + + "light": "Light", + "@light": { + "description": "Light theme option" + }, + + "dark": "Dark", + "@dark": { + "description": "Dark theme option" + }, + + "blueLightTheme": "Blue light theme", + "@blueLightTheme": { + "description": "Description for blue light theme" + }, + + "blueDarkTheme": "Blue dark theme", + "@blueDarkTheme": { + "description": "Description for blue dark theme" + }, + + "sarRed": "SAR Red", + "@sarRed": { + "description": "SAR Red theme option" + }, + + "alertEmergencyMode": "Alert/Emergency mode", + "@alertEmergencyMode": { + "description": "Description for SAR Red theme" + }, + + "sarGreen": "SAR Green", + "@sarGreen": { + "description": "SAR Green theme option" + }, + + "safeAllClearMode": "Safe/All Clear mode", + "@safeAllClearMode": { + "description": "Description for SAR Green theme" + }, + + "autoSystem": "Auto (System)", + "@autoSystem": { + "description": "Auto/System theme option" + }, + + "followSystemTheme": "Follow system theme", + "@followSystemTheme": { + "description": "Description for system theme" + }, + + "showRxTxIndicators": "Show RX/TX Indicators", + "@showRxTxIndicators": { + "description": "Setting to show RX/TX indicators" + }, + + "displayPacketActivity": "Display packet activity indicators in top bar", + "@displayPacketActivity": { + "description": "Description for RX/TX indicators setting" + }, + + "language": "Language", + "@language": { + "description": "Language setting label" + }, + + "chooseLanguage": "Choose Language", + "@chooseLanguage": { + "description": "Language selection dialog title" + }, + + "english": "English", + "@english": { + "description": "English language option" + }, + + "slovenian": "Slovenian", + "@slovenian": { + "description": "Slovenian language option" + }, + + "croatian": "Croatian", + "@croatian": { + "description": "Croatian language option" + }, + + "locationBroadcasting": "Location Broadcasting", + "@locationBroadcasting": { + "description": "Location settings section header" + }, + + "autoLocationTracking": "Auto Location Tracking", + "@autoLocationTracking": { + "description": "Auto location tracking setting" + }, + + "automaticallyBroadcastPosition": "Automatically broadcast position updates", + "@automaticallyBroadcastPosition": { + "description": "Description for auto location tracking" + }, + + "configureTracking": "Configure Tracking", + "@configureTracking": { + "description": "Configure tracking button label" + }, + + "distanceAndTimeThresholds": "Distance and time thresholds", + "@distanceAndTimeThresholds": { + "description": "Description for tracking configuration" + }, + + "locationTrackingConfiguration": "Location Tracking Configuration", + "@locationTrackingConfiguration": { + "description": "Tracking configuration dialog title" + }, + + "configureWhenLocationBroadcasts": "Configure when location broadcasts are sent to the mesh network", + "@configureWhenLocationBroadcasts": { + "description": "Description for tracking configuration dialog" + }, + + "minimumDistance": "Minimum Distance", + "@minimumDistance": { + "description": "Minimum distance setting label" + }, + + "broadcastAfterMoving": "Broadcast only after moving {distance} meters", + "@broadcastAfterMoving": { + "description": "Description for minimum distance", + "placeholders": { + "distance": { + "type": "String" + } + } + }, + + "maximumDistance": "Maximum Distance", + "@maximumDistance": { + "description": "Maximum distance setting label" + }, + + "alwaysBroadcastAfterMoving": "Always broadcast after moving {distance} meters", + "@alwaysBroadcastAfterMoving": { + "description": "Description for maximum distance", + "placeholders": { + "distance": { + "type": "String" + } + } + }, + + "minimumTimeInterval": "Minimum Time Interval", + "@minimumTimeInterval": { + "description": "Minimum time interval setting label" + }, + + "alwaysBroadcastEvery": "Always broadcast every {duration}", + "@alwaysBroadcastEvery": { + "description": "Description for time interval", + "placeholders": { + "duration": { + "type": "String" + } + } + }, + + "save": "Save", + "@save": { + "description": "Save button label" + }, + + "cancel": "Cancel", + "@cancel": { + "description": "Cancel button label" + }, + + "close": "Close", + "@close": { + "description": "Close button label" + }, + + "about": "About", + "@about": { + "description": "About section header" + }, + + "appVersion": "App Version", + "@appVersion": { + "description": "App version label" + }, + + "appName": "App Name", + "@appName": { + "description": "App name label" + }, + + "aboutMeshCoreSar": "About MeshCore SAR", + "@aboutMeshCoreSar": { + "description": "About dialog title" + }, + + "aboutDescription": "A Search & Rescue application designed for emergency response teams. Features include:\n\n• BLE mesh networking for device-to-device communication\n• Offline maps with multiple layer options\n• Real-time team member tracking\n• SAR tactical markers (found person, fire, staging)\n• Contact management and messaging\n• GPS tracking with compass heading\n• Map tile caching for offline use", + "@aboutDescription": { + "description": "About dialog description" + }, + + "technologiesUsed": "Technologies Used:", + "@technologiesUsed": { + "description": "Technologies used section title" + }, + + "technologiesList": "• Flutter for cross-platform development\n• BLE (Bluetooth Low Energy) for mesh networking\n• OpenStreetMap for mapping\n• Provider for state management\n• SharedPreferences for local storage", + "@technologiesList": { + "description": "List of technologies used" + }, + + "developer": "Developer", + "@developer": { + "description": "Developer section header" + }, + + "packageName": "Package Name", + "@packageName": { + "description": "Package name label" + }, + + "sampleData": "Sample Data", + "@sampleData": { + "description": "Sample data section header" + }, + + "sampleDataDescription": "Load or clear sample contacts, channel messages, and SAR markers for testing", + "@sampleDataDescription": { + "description": "Sample data section description" + }, + + "loadSampleData": "Load Sample Data", + "@loadSampleData": { + "description": "Load sample data button" + }, + + "clearAllData": "Clear All Data", + "@clearAllData": { + "description": "Clear all data button" + }, + + "clearAllDataConfirmTitle": "Clear All Data", + "@clearAllDataConfirmTitle": { + "description": "Clear data confirmation dialog title" + }, + + "clearAllDataConfirmMessage": "This will clear all contacts and SAR markers. Are you sure?", + "@clearAllDataConfirmMessage": { + "description": "Clear data confirmation message" + }, + + "clear": "Clear", + "@clear": { + "description": "Clear button label" + }, + + "loadedSampleData": "Loaded {teamCount} team members, {channelCount} channels, {sarCount} SAR markers, {messageCount} messages", + "@loadedSampleData": { + "description": "Success message after loading sample data", + "placeholders": { + "teamCount": { + "type": "int" + }, + "channelCount": { + "type": "int" + }, + "sarCount": { + "type": "int" + }, + "messageCount": { + "type": "int" + } + } + }, + + "failedToLoadSampleData": "Failed to load sample data: {error}", + "@failedToLoadSampleData": { + "description": "Error message when sample data fails to load", + "placeholders": { + "error": { + "type": "String" + } + } + }, + + "allDataCleared": "All data cleared", + "@allDataCleared": { + "description": "Success message after clearing all data" + }, + + "failedToStartBackgroundTracking": "Failed to start background tracking. Check permissions and BLE connection.", + "@failedToStartBackgroundTracking": { + "description": "Error message when background tracking fails to start" + }, + + "locationBroadcast": "Location broadcast: {latitude}, {longitude}", + "@locationBroadcast": { + "description": "Success message for location broadcast", + "placeholders": { + "latitude": { + "type": "String" + }, + "longitude": { + "type": "String" + } + } + }, + + "defaultPinInfo": "The default pin for devices without a screen is 123456. Trouble pairing? Forget the bluetooth device in system settings.", + "@defaultPinInfo": { + "description": "Information about default PIN for pairing" + } +} diff --git a/lib/l10n/app_hr.arb b/lib/l10n/app_hr.arb new file mode 100644 index 0000000..6a0b8ec --- /dev/null +++ b/lib/l10n/app_hr.arb @@ -0,0 +1,161 @@ +{ + "@@locale": "hr", + + "appTitle": "MeshCore SAR", + + "messages": "Poruke", + + "contacts": "Kontakti", + + "map": "Karta", + + "settings": "Postavke", + + "connect": "Poveži", + + "disconnect": "Prekini", + + "scanningForDevices": "Skeniranje uređaja...", + + "noDevicesFound": "Nisu pronađeni uređaji", + + "scanAgain": "Skeniraj ponovno", + + "tapToConnect": "Dodirnite za povezivanje", + + "deviceNotConnected": "Uređaj nije povezan", + + "locationPermissionDenied": "Dopuštenje za lokaciju odbijeno", + + "locationPermissionPermanentlyDenied": "Dopuštenje za lokaciju trajno odbijeno. Molimo omogućite u Postavkama.", + + "locationServicesDisabled": "Usluge lokacije su onemogućene. Molimo omogućite ih u Postavkama.", + + "failedToGetGpsLocation": "Neuspjelo dobivanje GPS lokacije", + + "advertisedAtLocation": "Objavljeno na {latitude}, {longitude}", + + "failedToAdvertise": "Neuspjela objava: {error}", + + "reconnecting": "Ponovno povezivanje... ({attempt}/{max})", + + "cancelReconnection": "Otkaži ponovno povezivanje", + + "mapManagement": "Upravljanje kartom", + + "general": "Općenito", + + "theme": "Tema", + + "chooseTheme": "Odaberite temu", + + "light": "Svijetla", + + "dark": "Tamna", + + "blueLightTheme": "Plava svijetla tema", + + "blueDarkTheme": "Plava tamna tema", + + "sarRed": "SAR crvena", + + "alertEmergencyMode": "Način upozorenja/hitna situacija", + + "sarGreen": "SAR zelena", + + "safeAllClearMode": "Način sigurno/sve jasno", + + "autoSystem": "Automatski (Sustav)", + + "followSystemTheme": "Slijedi sistemsku temu", + + "showRxTxIndicators": "Prikaži RX/TX indikatore", + + "displayPacketActivity": "Prikaži indikatore aktivnosti paketa u gornjoj traci", + + "language": "Jezik", + + "chooseLanguage": "Odaberite jezik", + + "english": "Engleski", + + "slovenian": "Slovenski", + + "croatian": "Hrvatski", + + "locationBroadcasting": "Emitiranje lokacije", + + "autoLocationTracking": "Automatsko praćenje lokacije", + + "automaticallyBroadcastPosition": "Automatski emitiraj ažuriranja pozicije", + + "configureTracking": "Konfiguriraj praćenje", + + "distanceAndTimeThresholds": "Pragovi udaljenosti i vremena", + + "locationTrackingConfiguration": "Konfiguracija praćenja lokacije", + + "configureWhenLocationBroadcasts": "Konfigurirajte kada se emitiranja lokacije šalju u mesh mrežu", + + "minimumDistance": "Minimalna udaljenost", + + "broadcastAfterMoving": "Emitiraj tek nakon pomicanja {distance} metara", + + "maximumDistance": "Maksimalna udaljenost", + + "alwaysBroadcastAfterMoving": "Uvijek emitiraj nakon pomicanja {distance} metara", + + "minimumTimeInterval": "Minimalni vremenski interval", + + "alwaysBroadcastEvery": "Uvijek emitiraj svakih {duration}", + + "save": "Spremi", + + "cancel": "Otkaži", + + "close": "Zatvori", + + "about": "O aplikaciji", + + "appVersion": "Verzija aplikacije", + + "appName": "Ime aplikacije", + + "aboutMeshCoreSar": "O MeshCore SAR", + + "aboutDescription": "Aplikacija za potragu i spašavanje dizajnirana za timove za hitne slučajeve. Značajke uključuju:\n\n• BLE mesh mrežu za komunikaciju uređaj-uređaj\n• Offline karte s više slojeva\n• Praćenje članova tima u stvarnom vremenu\n• SAR taktički markeri (pronađena osoba, požar, zbirno mjesto)\n• Upravljanje kontaktima i razmjena poruka\n• GPS praćenje s kompasnim smjerom\n• Predmemoriranje karata za offline upotrebu", + + "technologiesUsed": "Korištene tehnologije:", + + "technologiesList": "• Flutter za višeplatformski razvoj\n• BLE (Bluetooth Low Energy) za mesh mrežu\n• OpenStreetMap za kartografiju\n• Provider za upravljanje stanjem\n• SharedPreferences za lokalno pohranu", + + "developer": "Programer", + + "packageName": "Ime paketa", + + "sampleData": "Primjer podataka", + + "sampleDataDescription": "Učitajte ili očistite primjere kontakata, poruka kanala i SAR markera za testiranje", + + "loadSampleData": "Učitaj primjer podataka", + + "clearAllData": "Očisti sve podatke", + + "clearAllDataConfirmTitle": "Očisti sve podatke", + + "clearAllDataConfirmMessage": "Ovo će očistiti sve kontakte i SAR markere. Jeste li sigurni?", + + "clear": "Očisti", + + "loadedSampleData": "Učitano {teamCount} članova tima, {channelCount} kanala, {sarCount} SAR markera, {messageCount} poruka", + + "failedToLoadSampleData": "Neuspjelo učitavanje primjera podataka: {error}", + + "allDataCleared": "Svi podaci očišćeni", + + "failedToStartBackgroundTracking": "Neuspjelo pokretanje praćenja u pozadini. Provjerite dopuštenja i BLE vezu.", + + "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." +} diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart new file mode 100644 index 0000000..ae0d03a --- /dev/null +++ b/lib/l10n/app_localizations.dart @@ -0,0 +1,617 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'app_localizations_en.dart'; +import 'app_localizations_hr.dart'; +import 'app_localizations_sl.dart'; + +// ignore_for_file: type=lint + +/// Callers can lookup localized strings with an instance of AppLocalizations +/// returned by `AppLocalizations.of(context)`. +/// +/// Applications need to include `AppLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'l10n/app_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: AppLocalizations.localizationsDelegates, +/// supportedLocales: AppLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the AppLocalizations.supportedLocales +/// property. +abstract class AppLocalizations { + AppLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static AppLocalizations? of(BuildContext context) { + return Localizations.of(context, AppLocalizations); + } + + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = + >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('en'), + Locale('hr'), + Locale('sl'), + ]; + + /// The application title + /// + /// In en, this message translates to: + /// **'MeshCore SAR'** + String get appTitle; + + /// Messages tab label + /// + /// In en, this message translates to: + /// **'Messages'** + String get messages; + + /// Contacts tab label + /// + /// In en, this message translates to: + /// **'Contacts'** + String get contacts; + + /// Map tab label + /// + /// In en, this message translates to: + /// **'Map'** + String get map; + + /// Settings screen title + /// + /// In en, this message translates to: + /// **'Settings'** + String get settings; + + /// Connect button label + /// + /// In en, this message translates to: + /// **'Connect'** + String get connect; + + /// Disconnect button label + /// + /// In en, this message translates to: + /// **'Disconnect'** + String get disconnect; + + /// Text shown when scanning for BLE devices + /// + /// In en, this message translates to: + /// **'Scanning for devices...'** + String get scanningForDevices; + + /// Text shown when no BLE devices are found + /// + /// In en, this message translates to: + /// **'No devices found'** + String get noDevicesFound; + + /// Button to restart BLE scanning + /// + /// In en, this message translates to: + /// **'Scan Again'** + String get scanAgain; + + /// Subtitle text for device in scan list + /// + /// In en, this message translates to: + /// **'Tap to connect'** + String get tapToConnect; + + /// Error message when device is not connected + /// + /// In en, this message translates to: + /// **'Device not connected'** + String get deviceNotConnected; + + /// Error when location permission is denied + /// + /// In en, this message translates to: + /// **'Location permission denied'** + String get locationPermissionDenied; + + /// Error when location permission is permanently denied + /// + /// In en, this message translates to: + /// **'Location permission permanently denied. Please enable in Settings.'** + String get locationPermissionPermanentlyDenied; + + /// Error when location services are disabled + /// + /// In en, this message translates to: + /// **'Location services are disabled. Please enable them in Settings.'** + String get locationServicesDisabled; + + /// Error when GPS location cannot be obtained + /// + /// In en, this message translates to: + /// **'Failed to get GPS location'** + String get failedToGetGpsLocation; + + /// Success message showing advertised location + /// + /// In en, this message translates to: + /// **'Advertised at {latitude}, {longitude}'** + String advertisedAtLocation(String latitude, String longitude); + + /// Error message for failed advertisement + /// + /// In en, this message translates to: + /// **'Failed to advertise: {error}'** + String failedToAdvertise(String error); + + /// Text shown during reconnection attempts + /// + /// In en, this message translates to: + /// **'Reconnecting... ({attempt}/{max})'** + String reconnecting(int attempt, int max); + + /// Tooltip for cancel reconnection button + /// + /// In en, this message translates to: + /// **'Cancel reconnection'** + String get cancelReconnection; + + /// Menu item for map management + /// + /// In en, this message translates to: + /// **'Map Management'** + String get mapManagement; + + /// General settings section header + /// + /// In en, this message translates to: + /// **'General'** + String get general; + + /// Theme setting label + /// + /// In en, this message translates to: + /// **'Theme'** + String get theme; + + /// Theme selection dialog title + /// + /// In en, this message translates to: + /// **'Choose Theme'** + String get chooseTheme; + + /// Light theme option + /// + /// In en, this message translates to: + /// **'Light'** + String get light; + + /// Dark theme option + /// + /// In en, this message translates to: + /// **'Dark'** + String get dark; + + /// Description for blue light theme + /// + /// In en, this message translates to: + /// **'Blue light theme'** + String get blueLightTheme; + + /// Description for blue dark theme + /// + /// In en, this message translates to: + /// **'Blue dark theme'** + String get blueDarkTheme; + + /// SAR Red theme option + /// + /// In en, this message translates to: + /// **'SAR Red'** + String get sarRed; + + /// Description for SAR Red theme + /// + /// In en, this message translates to: + /// **'Alert/Emergency mode'** + String get alertEmergencyMode; + + /// SAR Green theme option + /// + /// In en, this message translates to: + /// **'SAR Green'** + String get sarGreen; + + /// Description for SAR Green theme + /// + /// In en, this message translates to: + /// **'Safe/All Clear mode'** + String get safeAllClearMode; + + /// Auto/System theme option + /// + /// In en, this message translates to: + /// **'Auto (System)'** + String get autoSystem; + + /// Description for system theme + /// + /// In en, this message translates to: + /// **'Follow system theme'** + String get followSystemTheme; + + /// Setting to show RX/TX indicators + /// + /// In en, this message translates to: + /// **'Show RX/TX Indicators'** + String get showRxTxIndicators; + + /// Description for RX/TX indicators setting + /// + /// In en, this message translates to: + /// **'Display packet activity indicators in top bar'** + String get displayPacketActivity; + + /// Language setting label + /// + /// In en, this message translates to: + /// **'Language'** + String get language; + + /// Language selection dialog title + /// + /// In en, this message translates to: + /// **'Choose Language'** + String get chooseLanguage; + + /// English language option + /// + /// In en, this message translates to: + /// **'English'** + String get english; + + /// Slovenian language option + /// + /// In en, this message translates to: + /// **'Slovenian'** + String get slovenian; + + /// Croatian language option + /// + /// In en, this message translates to: + /// **'Croatian'** + String get croatian; + + /// Location settings section header + /// + /// In en, this message translates to: + /// **'Location Broadcasting'** + String get locationBroadcasting; + + /// Auto location tracking setting + /// + /// In en, this message translates to: + /// **'Auto Location Tracking'** + String get autoLocationTracking; + + /// Description for auto location tracking + /// + /// In en, this message translates to: + /// **'Automatically broadcast position updates'** + String get automaticallyBroadcastPosition; + + /// Configure tracking button label + /// + /// In en, this message translates to: + /// **'Configure Tracking'** + String get configureTracking; + + /// Description for tracking configuration + /// + /// In en, this message translates to: + /// **'Distance and time thresholds'** + String get distanceAndTimeThresholds; + + /// Tracking configuration dialog title + /// + /// In en, this message translates to: + /// **'Location Tracking Configuration'** + String get locationTrackingConfiguration; + + /// Description for tracking configuration dialog + /// + /// In en, this message translates to: + /// **'Configure when location broadcasts are sent to the mesh network'** + String get configureWhenLocationBroadcasts; + + /// Minimum distance setting label + /// + /// In en, this message translates to: + /// **'Minimum Distance'** + String get minimumDistance; + + /// Description for minimum distance + /// + /// In en, this message translates to: + /// **'Broadcast only after moving {distance} meters'** + String broadcastAfterMoving(String distance); + + /// Maximum distance setting label + /// + /// In en, this message translates to: + /// **'Maximum Distance'** + String get maximumDistance; + + /// Description for maximum distance + /// + /// In en, this message translates to: + /// **'Always broadcast after moving {distance} meters'** + String alwaysBroadcastAfterMoving(String distance); + + /// Minimum time interval setting label + /// + /// In en, this message translates to: + /// **'Minimum Time Interval'** + String get minimumTimeInterval; + + /// Description for time interval + /// + /// In en, this message translates to: + /// **'Always broadcast every {duration}'** + String alwaysBroadcastEvery(String duration); + + /// Save button label + /// + /// In en, this message translates to: + /// **'Save'** + String get save; + + /// Cancel button label + /// + /// In en, this message translates to: + /// **'Cancel'** + String get cancel; + + /// Close button label + /// + /// In en, this message translates to: + /// **'Close'** + String get close; + + /// About section header + /// + /// In en, this message translates to: + /// **'About'** + String get about; + + /// App version label + /// + /// In en, this message translates to: + /// **'App Version'** + String get appVersion; + + /// App name label + /// + /// In en, this message translates to: + /// **'App Name'** + String get appName; + + /// About dialog title + /// + /// In en, this message translates to: + /// **'About MeshCore SAR'** + String get aboutMeshCoreSar; + + /// About dialog description + /// + /// In en, this message translates to: + /// **'A Search & Rescue application designed for emergency response teams. Features include:\n\n• BLE mesh networking for device-to-device communication\n• Offline maps with multiple layer options\n• Real-time team member tracking\n• SAR tactical markers (found person, fire, staging)\n• Contact management and messaging\n• GPS tracking with compass heading\n• Map tile caching for offline use'** + String get aboutDescription; + + /// Technologies used section title + /// + /// In en, this message translates to: + /// **'Technologies Used:'** + String get technologiesUsed; + + /// List of technologies used + /// + /// In en, this message translates to: + /// **'• Flutter for cross-platform development\n• BLE (Bluetooth Low Energy) for mesh networking\n• OpenStreetMap for mapping\n• Provider for state management\n• SharedPreferences for local storage'** + String get technologiesList; + + /// Developer section header + /// + /// In en, this message translates to: + /// **'Developer'** + String get developer; + + /// Package name label + /// + /// In en, this message translates to: + /// **'Package Name'** + String get packageName; + + /// Sample data section header + /// + /// In en, this message translates to: + /// **'Sample Data'** + String get sampleData; + + /// Sample data section description + /// + /// In en, this message translates to: + /// **'Load or clear sample contacts, channel messages, and SAR markers for testing'** + String get sampleDataDescription; + + /// Load sample data button + /// + /// In en, this message translates to: + /// **'Load Sample Data'** + String get loadSampleData; + + /// Clear all data button + /// + /// In en, this message translates to: + /// **'Clear All Data'** + String get clearAllData; + + /// Clear data confirmation dialog title + /// + /// In en, this message translates to: + /// **'Clear All Data'** + String get clearAllDataConfirmTitle; + + /// Clear data confirmation message + /// + /// In en, this message translates to: + /// **'This will clear all contacts and SAR markers. Are you sure?'** + String get clearAllDataConfirmMessage; + + /// Clear button label + /// + /// In en, this message translates to: + /// **'Clear'** + String get clear; + + /// Success message after loading sample data + /// + /// In en, this message translates to: + /// **'Loaded {teamCount} team members, {channelCount} channels, {sarCount} SAR markers, {messageCount} messages'** + String loadedSampleData( + int teamCount, + int channelCount, + int sarCount, + int messageCount, + ); + + /// Error message when sample data fails to load + /// + /// In en, this message translates to: + /// **'Failed to load sample data: {error}'** + String failedToLoadSampleData(String error); + + /// Success message after clearing all data + /// + /// In en, this message translates to: + /// **'All data cleared'** + String get allDataCleared; + + /// Error message when background tracking fails to start + /// + /// In en, this message translates to: + /// **'Failed to start background tracking. Check permissions and BLE connection.'** + String get failedToStartBackgroundTracking; + + /// Success message for location broadcast + /// + /// In en, this message translates to: + /// **'Location broadcast: {latitude}, {longitude}'** + String locationBroadcast(String latitude, String longitude); + + /// Information about default PIN for pairing + /// + /// In en, this message translates to: + /// **'The default pin for devices without a screen is 123456. Trouble pairing? Forget the bluetooth device in system settings.'** + String get defaultPinInfo; +} + +class _AppLocalizationsDelegate + extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupAppLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => + ['en', 'hr', 'sl'].contains(locale.languageCode); + + @override + bool shouldReload(_AppLocalizationsDelegate old) => false; +} + +AppLocalizations lookupAppLocalizations(Locale locale) { + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'en': + return AppLocalizationsEn(); + case 'hr': + return AppLocalizationsHr(); + case 'sl': + return AppLocalizationsSl(); + } + + throw FlutterError( + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.', + ); +} diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart new file mode 100644 index 0000000..42e78d0 --- /dev/null +++ b/lib/l10n/app_localizations_en.dart @@ -0,0 +1,281 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for English (`en`). +class AppLocalizationsEn extends AppLocalizations { + AppLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get appTitle => 'MeshCore SAR'; + + @override + String get messages => 'Messages'; + + @override + String get contacts => 'Contacts'; + + @override + String get map => 'Map'; + + @override + String get settings => 'Settings'; + + @override + String get connect => 'Connect'; + + @override + String get disconnect => 'Disconnect'; + + @override + String get scanningForDevices => 'Scanning for devices...'; + + @override + String get noDevicesFound => 'No devices found'; + + @override + String get scanAgain => 'Scan Again'; + + @override + String get tapToConnect => 'Tap to connect'; + + @override + String get deviceNotConnected => 'Device not connected'; + + @override + String get locationPermissionDenied => 'Location permission denied'; + + @override + String get locationPermissionPermanentlyDenied => + 'Location permission permanently denied. Please enable in Settings.'; + + @override + String get locationServicesDisabled => + 'Location services are disabled. Please enable them in Settings.'; + + @override + String get failedToGetGpsLocation => 'Failed to get GPS location'; + + @override + String advertisedAtLocation(String latitude, String longitude) { + return 'Advertised at $latitude, $longitude'; + } + + @override + String failedToAdvertise(String error) { + return 'Failed to advertise: $error'; + } + + @override + String reconnecting(int attempt, int max) { + return 'Reconnecting... ($attempt/$max)'; + } + + @override + String get cancelReconnection => 'Cancel reconnection'; + + @override + String get mapManagement => 'Map Management'; + + @override + String get general => 'General'; + + @override + String get theme => 'Theme'; + + @override + String get chooseTheme => 'Choose Theme'; + + @override + String get light => 'Light'; + + @override + String get dark => 'Dark'; + + @override + String get blueLightTheme => 'Blue light theme'; + + @override + String get blueDarkTheme => 'Blue dark theme'; + + @override + String get sarRed => 'SAR Red'; + + @override + String get alertEmergencyMode => 'Alert/Emergency mode'; + + @override + String get sarGreen => 'SAR Green'; + + @override + String get safeAllClearMode => 'Safe/All Clear mode'; + + @override + String get autoSystem => 'Auto (System)'; + + @override + String get followSystemTheme => 'Follow system theme'; + + @override + String get showRxTxIndicators => 'Show RX/TX Indicators'; + + @override + String get displayPacketActivity => + 'Display packet activity indicators in top bar'; + + @override + String get language => 'Language'; + + @override + String get chooseLanguage => 'Choose Language'; + + @override + String get english => 'English'; + + @override + String get slovenian => 'Slovenian'; + + @override + String get croatian => 'Croatian'; + + @override + String get locationBroadcasting => 'Location Broadcasting'; + + @override + String get autoLocationTracking => 'Auto Location Tracking'; + + @override + String get automaticallyBroadcastPosition => + 'Automatically broadcast position updates'; + + @override + String get configureTracking => 'Configure Tracking'; + + @override + String get distanceAndTimeThresholds => 'Distance and time thresholds'; + + @override + String get locationTrackingConfiguration => 'Location Tracking Configuration'; + + @override + String get configureWhenLocationBroadcasts => + 'Configure when location broadcasts are sent to the mesh network'; + + @override + String get minimumDistance => 'Minimum Distance'; + + @override + String broadcastAfterMoving(String distance) { + return 'Broadcast only after moving $distance meters'; + } + + @override + String get maximumDistance => 'Maximum Distance'; + + @override + String alwaysBroadcastAfterMoving(String distance) { + return 'Always broadcast after moving $distance meters'; + } + + @override + String get minimumTimeInterval => 'Minimum Time Interval'; + + @override + String alwaysBroadcastEvery(String duration) { + return 'Always broadcast every $duration'; + } + + @override + String get save => 'Save'; + + @override + String get cancel => 'Cancel'; + + @override + String get close => 'Close'; + + @override + String get about => 'About'; + + @override + String get appVersion => 'App Version'; + + @override + String get appName => 'App Name'; + + @override + String get aboutMeshCoreSar => 'About MeshCore SAR'; + + @override + String get aboutDescription => + 'A Search & Rescue application designed for emergency response teams. Features include:\n\n• BLE mesh networking for device-to-device communication\n• Offline maps with multiple layer options\n• Real-time team member tracking\n• SAR tactical markers (found person, fire, staging)\n• Contact management and messaging\n• GPS tracking with compass heading\n• Map tile caching for offline use'; + + @override + String get technologiesUsed => 'Technologies Used:'; + + @override + String get technologiesList => + '• Flutter for cross-platform development\n• BLE (Bluetooth Low Energy) for mesh networking\n• OpenStreetMap for mapping\n• Provider for state management\n• SharedPreferences for local storage'; + + @override + String get developer => 'Developer'; + + @override + String get packageName => 'Package Name'; + + @override + String get sampleData => 'Sample Data'; + + @override + String get sampleDataDescription => + 'Load or clear sample contacts, channel messages, and SAR markers for testing'; + + @override + String get loadSampleData => 'Load Sample Data'; + + @override + String get clearAllData => 'Clear All Data'; + + @override + String get clearAllDataConfirmTitle => 'Clear All Data'; + + @override + String get clearAllDataConfirmMessage => + 'This will clear all contacts and SAR markers. Are you sure?'; + + @override + String get clear => 'Clear'; + + @override + String loadedSampleData( + int teamCount, + int channelCount, + int sarCount, + int messageCount, + ) { + return 'Loaded $teamCount team members, $channelCount channels, $sarCount SAR markers, $messageCount messages'; + } + + @override + String failedToLoadSampleData(String error) { + return 'Failed to load sample data: $error'; + } + + @override + String get allDataCleared => 'All data cleared'; + + @override + String get failedToStartBackgroundTracking => + 'Failed to start background tracking. Check permissions and BLE connection.'; + + @override + String locationBroadcast(String latitude, String longitude) { + return 'Location broadcast: $latitude, $longitude'; + } + + @override + String get defaultPinInfo => + 'The default pin for devices without a screen is 123456. Trouble pairing? Forget the bluetooth device in system settings.'; +} diff --git a/lib/l10n/app_localizations_hr.dart b/lib/l10n/app_localizations_hr.dart new file mode 100644 index 0000000..5772905 --- /dev/null +++ b/lib/l10n/app_localizations_hr.dart @@ -0,0 +1,281 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Croatian (`hr`). +class AppLocalizationsHr extends AppLocalizations { + AppLocalizationsHr([String locale = 'hr']) : super(locale); + + @override + String get appTitle => 'MeshCore SAR'; + + @override + String get messages => 'Poruke'; + + @override + String get contacts => 'Kontakti'; + + @override + String get map => 'Karta'; + + @override + String get settings => 'Postavke'; + + @override + String get connect => 'Poveži'; + + @override + String get disconnect => 'Prekini'; + + @override + String get scanningForDevices => 'Skeniranje uređaja...'; + + @override + String get noDevicesFound => 'Nisu pronađeni uređaji'; + + @override + String get scanAgain => 'Skeniraj ponovno'; + + @override + String get tapToConnect => 'Dodirnite za povezivanje'; + + @override + String get deviceNotConnected => 'Uređaj nije povezan'; + + @override + String get locationPermissionDenied => 'Dopuštenje za lokaciju odbijeno'; + + @override + String get locationPermissionPermanentlyDenied => + 'Dopuštenje za lokaciju trajno odbijeno. Molimo omogućite u Postavkama.'; + + @override + String get locationServicesDisabled => + 'Usluge lokacije su onemogućene. Molimo omogućite ih u Postavkama.'; + + @override + String get failedToGetGpsLocation => 'Neuspjelo dobivanje GPS lokacije'; + + @override + String advertisedAtLocation(String latitude, String longitude) { + return 'Objavljeno na $latitude, $longitude'; + } + + @override + String failedToAdvertise(String error) { + return 'Neuspjela objava: $error'; + } + + @override + String reconnecting(int attempt, int max) { + return 'Ponovno povezivanje... ($attempt/$max)'; + } + + @override + String get cancelReconnection => 'Otkaži ponovno povezivanje'; + + @override + String get mapManagement => 'Upravljanje kartom'; + + @override + String get general => 'Općenito'; + + @override + String get theme => 'Tema'; + + @override + String get chooseTheme => 'Odaberite temu'; + + @override + String get light => 'Svijetla'; + + @override + String get dark => 'Tamna'; + + @override + String get blueLightTheme => 'Plava svijetla tema'; + + @override + String get blueDarkTheme => 'Plava tamna tema'; + + @override + String get sarRed => 'SAR crvena'; + + @override + String get alertEmergencyMode => 'Način upozorenja/hitna situacija'; + + @override + String get sarGreen => 'SAR zelena'; + + @override + String get safeAllClearMode => 'Način sigurno/sve jasno'; + + @override + String get autoSystem => 'Automatski (Sustav)'; + + @override + String get followSystemTheme => 'Slijedi sistemsku temu'; + + @override + String get showRxTxIndicators => 'Prikaži RX/TX indikatore'; + + @override + String get displayPacketActivity => + 'Prikaži indikatore aktivnosti paketa u gornjoj traci'; + + @override + String get language => 'Jezik'; + + @override + String get chooseLanguage => 'Odaberite jezik'; + + @override + String get english => 'Engleski'; + + @override + String get slovenian => 'Slovenski'; + + @override + String get croatian => 'Hrvatski'; + + @override + String get locationBroadcasting => 'Emitiranje lokacije'; + + @override + String get autoLocationTracking => 'Automatsko praćenje lokacije'; + + @override + String get automaticallyBroadcastPosition => + 'Automatski emitiraj ažuriranja pozicije'; + + @override + String get configureTracking => 'Konfiguriraj praćenje'; + + @override + String get distanceAndTimeThresholds => 'Pragovi udaljenosti i vremena'; + + @override + String get locationTrackingConfiguration => 'Konfiguracija praćenja lokacije'; + + @override + String get configureWhenLocationBroadcasts => + 'Konfigurirajte kada se emitiranja lokacije šalju u mesh mrežu'; + + @override + String get minimumDistance => 'Minimalna udaljenost'; + + @override + String broadcastAfterMoving(String distance) { + return 'Emitiraj tek nakon pomicanja $distance metara'; + } + + @override + String get maximumDistance => 'Maksimalna udaljenost'; + + @override + String alwaysBroadcastAfterMoving(String distance) { + return 'Uvijek emitiraj nakon pomicanja $distance metara'; + } + + @override + String get minimumTimeInterval => 'Minimalni vremenski interval'; + + @override + String alwaysBroadcastEvery(String duration) { + return 'Uvijek emitiraj svakih $duration'; + } + + @override + String get save => 'Spremi'; + + @override + String get cancel => 'Otkaži'; + + @override + String get close => 'Zatvori'; + + @override + String get about => 'O aplikaciji'; + + @override + String get appVersion => 'Verzija aplikacije'; + + @override + String get appName => 'Ime aplikacije'; + + @override + String get aboutMeshCoreSar => 'O MeshCore SAR'; + + @override + String get aboutDescription => + 'Aplikacija za potragu i spašavanje dizajnirana za timove za hitne slučajeve. Značajke uključuju:\n\n• BLE mesh mrežu za komunikaciju uređaj-uređaj\n• Offline karte s više slojeva\n• Praćenje članova tima u stvarnom vremenu\n• SAR taktički markeri (pronađena osoba, požar, zbirno mjesto)\n• Upravljanje kontaktima i razmjena poruka\n• GPS praćenje s kompasnim smjerom\n• Predmemoriranje karata za offline upotrebu'; + + @override + String get technologiesUsed => 'Korištene tehnologije:'; + + @override + String get technologiesList => + '• Flutter za višeplatformski razvoj\n• BLE (Bluetooth Low Energy) za mesh mrežu\n• OpenStreetMap za kartografiju\n• Provider za upravljanje stanjem\n• SharedPreferences za lokalno pohranu'; + + @override + String get developer => 'Programer'; + + @override + String get packageName => 'Ime paketa'; + + @override + String get sampleData => 'Primjer podataka'; + + @override + String get sampleDataDescription => + 'Učitajte ili očistite primjere kontakata, poruka kanala i SAR markera za testiranje'; + + @override + String get loadSampleData => 'Učitaj primjer podataka'; + + @override + String get clearAllData => 'Očisti sve podatke'; + + @override + String get clearAllDataConfirmTitle => 'Očisti sve podatke'; + + @override + String get clearAllDataConfirmMessage => + 'Ovo će očistiti sve kontakte i SAR markere. Jeste li sigurni?'; + + @override + String get clear => 'Očisti'; + + @override + String loadedSampleData( + int teamCount, + int channelCount, + int sarCount, + int messageCount, + ) { + return 'Učitano $teamCount članova tima, $channelCount kanala, $sarCount SAR markera, $messageCount poruka'; + } + + @override + String failedToLoadSampleData(String error) { + return 'Neuspjelo učitavanje primjera podataka: $error'; + } + + @override + String get allDataCleared => 'Svi podaci očišćeni'; + + @override + String get failedToStartBackgroundTracking => + 'Neuspjelo pokretanje praćenja u pozadini. Provjerite dopuštenja i BLE vezu.'; + + @override + String locationBroadcast(String latitude, String longitude) { + return 'Emitiranje lokacije: $latitude, $longitude'; + } + + @override + String get defaultPinInfo => + 'Zadani PIN za uređaje bez zaslona je 123456. Problemi s uparivanjem? Zaboravite bluetooth uređaj u postavkama sustava.'; +} diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart new file mode 100644 index 0000000..183ac56 --- /dev/null +++ b/lib/l10n/app_localizations_sl.dart @@ -0,0 +1,281 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Slovenian (`sl`). +class AppLocalizationsSl extends AppLocalizations { + AppLocalizationsSl([String locale = 'sl']) : super(locale); + + @override + String get appTitle => 'MeshCore SAR'; + + @override + String get messages => 'Sporočila'; + + @override + String get contacts => 'Stiki'; + + @override + String get map => 'Zemljevid'; + + @override + String get settings => 'Nastavitve'; + + @override + String get connect => 'Poveži'; + + @override + String get disconnect => 'Prekini'; + + @override + String get scanningForDevices => 'Iskanje naprav...'; + + @override + String get noDevicesFound => 'Ni najdenih naprav'; + + @override + String get scanAgain => 'Ponovi iskanje'; + + @override + String get tapToConnect => 'Tapnite za povezavo'; + + @override + String get deviceNotConnected => 'Naprava ni povezana'; + + @override + String get locationPermissionDenied => 'Dovoljenje za lokacijo zavrnjeno'; + + @override + String get locationPermissionPermanentlyDenied => + 'Dovoljenje za lokacijo trajno zavrnjeno. Prosimo, omogočite v Nastavitvah.'; + + @override + String get locationServicesDisabled => + 'Lokacijske storitve so onemogočene. Prosimo, omogočite jih v Nastavitvah.'; + + @override + String get failedToGetGpsLocation => 'Pridobitev GPS lokacije ni uspela'; + + @override + String advertisedAtLocation(String latitude, String longitude) { + return 'Objavljeno na $latitude, $longitude'; + } + + @override + String failedToAdvertise(String error) { + return 'Objava ni uspela: $error'; + } + + @override + String reconnecting(int attempt, int max) { + return 'Ponovno povezovanje... ($attempt/$max)'; + } + + @override + String get cancelReconnection => 'Prekliči ponovno povezovanje'; + + @override + String get mapManagement => 'Upravljanje zemljevida'; + + @override + String get general => 'Splošno'; + + @override + String get theme => 'Tema'; + + @override + String get chooseTheme => 'Izberite temo'; + + @override + String get light => 'Svetla'; + + @override + String get dark => 'Temna'; + + @override + String get blueLightTheme => 'Modra svetla tema'; + + @override + String get blueDarkTheme => 'Modra temna tema'; + + @override + String get sarRed => 'SAR rdeča'; + + @override + String get alertEmergencyMode => 'Način opozorila/nujni primer'; + + @override + String get sarGreen => 'SAR zelena'; + + @override + String get safeAllClearMode => 'Način varno/vse jasno'; + + @override + String get autoSystem => 'Samodejno (Sistem)'; + + @override + String get followSystemTheme => 'Sledi sistemski temi'; + + @override + String get showRxTxIndicators => 'Prikaži RX/TX kazalnike'; + + @override + String get displayPacketActivity => + 'Prikaži kazalnike aktivnosti paketov v zgornji vrstici'; + + @override + String get language => 'Jezik'; + + @override + String get chooseLanguage => 'Izberite jezik'; + + @override + String get english => 'Angleščina'; + + @override + String get slovenian => 'Slovenščina'; + + @override + String get croatian => 'Hrvaščina'; + + @override + String get locationBroadcasting => 'Oddajanje lokacije'; + + @override + String get autoLocationTracking => 'Samodejno sledenje lokaciji'; + + @override + String get automaticallyBroadcastPosition => + 'Samodejno oddajaj posodobitve položaja'; + + @override + String get configureTracking => 'Konfiguriraj sledenje'; + + @override + String get distanceAndTimeThresholds => 'Pragovi razdalje in časa'; + + @override + String get locationTrackingConfiguration => 'Konfiguracija sledenja lokaciji'; + + @override + String get configureWhenLocationBroadcasts => + 'Konfigurirajte, kdaj se oddajanja lokacije pošiljajo v omrežje mesh'; + + @override + String get minimumDistance => 'Minimalna razdalja'; + + @override + String broadcastAfterMoving(String distance) { + return 'Oddajaj šele po premiku $distance metrov'; + } + + @override + String get maximumDistance => 'Maksimalna razdalja'; + + @override + String alwaysBroadcastAfterMoving(String distance) { + return 'Vedno oddajaj po premiku $distance metrov'; + } + + @override + String get minimumTimeInterval => 'Minimalni časovni interval'; + + @override + String alwaysBroadcastEvery(String duration) { + return 'Vedno oddajaj vsakih $duration'; + } + + @override + String get save => 'Shrani'; + + @override + String get cancel => 'Prekliči'; + + @override + String get close => 'Zapri'; + + @override + String get about => 'O aplikaciji'; + + @override + String get appVersion => 'Različica aplikacije'; + + @override + String get appName => 'Ime aplikacije'; + + @override + String get aboutMeshCoreSar => 'O MeshCore SAR'; + + @override + String get aboutDescription => + 'Aplikacija za iskanje in reševanje, zasnovana za ekipe za odzivanje v nujnih primerih. Funkcije vključujejo:\n\n• BLE mesh omrežje za komunikacijo naprava-naprava\n• Brez povezave delujejo zemljevidi z več sloji\n• Sledenje članov ekipe v realnem času\n• SAR taktični označevalci (najdena oseba, ogenj, zbirališče)\n• Upravljanje stikov in sporočanje\n• GPS sledenje s kompasno smerjo\n• Predpomnenje ploščic zemljevida za uporabo brez povezave'; + + @override + String get technologiesUsed => 'Uporabljene tehnologije:'; + + @override + String get technologiesList => + '• Flutter za večplatformni razvoj\n• BLE (Bluetooth Low Energy) za mesh omrežje\n• OpenStreetMap za kartografijo\n• Provider za upravljanje stanja\n• SharedPreferences za lokalno shranjevanje'; + + @override + String get developer => 'Razvijalec'; + + @override + String get packageName => 'Ime paketa'; + + @override + String get sampleData => 'Vzorčni podatki'; + + @override + String get sampleDataDescription => + 'Naložite ali počistite vzorčne stike, sporočila kanalov in SAR označevalce za testiranje'; + + @override + String get loadSampleData => 'Naloži vzorčne podatke'; + + @override + String get clearAllData => 'Počisti vse podatke'; + + @override + String get clearAllDataConfirmTitle => 'Počisti vse podatke'; + + @override + String get clearAllDataConfirmMessage => + 'To bo počistilo vse stike in SAR označevalce. Ste prepričani?'; + + @override + String get clear => 'Počisti'; + + @override + String loadedSampleData( + int teamCount, + int channelCount, + int sarCount, + int messageCount, + ) { + return 'Naloženih $teamCount članov ekipe, $channelCount kanalov, $sarCount SAR označevalcev, $messageCount sporočil'; + } + + @override + String failedToLoadSampleData(String error) { + return 'Nalaganje vzorčnih podatkov ni uspelo: $error'; + } + + @override + String get allDataCleared => 'Vsi podatki počiščeni'; + + @override + String get failedToStartBackgroundTracking => + 'Zagon sledenja v ozadju ni uspel. Preverite dovoljenja in BLE povezavo.'; + + @override + String locationBroadcast(String latitude, String longitude) { + return 'Oddajanje lokacije: $latitude, $longitude'; + } + + @override + String get defaultPinInfo => + 'Privzeta PIN koda za naprave brez zaslona je 123456. Težave s seznanitvijo? Pozabite napravo bluetooth v sistemskih nastavitvah.'; +} diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb new file mode 100644 index 0000000..b39375c --- /dev/null +++ b/lib/l10n/app_sl.arb @@ -0,0 +1,161 @@ +{ + "@@locale": "sl", + + "appTitle": "MeshCore SAR", + + "messages": "Sporočila", + + "contacts": "Stiki", + + "map": "Zemljevid", + + "settings": "Nastavitve", + + "connect": "Poveži", + + "disconnect": "Prekini", + + "scanningForDevices": "Iskanje naprav...", + + "noDevicesFound": "Ni najdenih naprav", + + "scanAgain": "Ponovi iskanje", + + "tapToConnect": "Tapnite za povezavo", + + "deviceNotConnected": "Naprava ni povezana", + + "locationPermissionDenied": "Dovoljenje za lokacijo zavrnjeno", + + "locationPermissionPermanentlyDenied": "Dovoljenje za lokacijo trajno zavrnjeno. Prosimo, omogočite v Nastavitvah.", + + "locationServicesDisabled": "Lokacijske storitve so onemogočene. Prosimo, omogočite jih v Nastavitvah.", + + "failedToGetGpsLocation": "Pridobitev GPS lokacije ni uspela", + + "advertisedAtLocation": "Objavljeno na {latitude}, {longitude}", + + "failedToAdvertise": "Objava ni uspela: {error}", + + "reconnecting": "Ponovno povezovanje... ({attempt}/{max})", + + "cancelReconnection": "Prekliči ponovno povezovanje", + + "mapManagement": "Upravljanje zemljevida", + + "general": "Splošno", + + "theme": "Tema", + + "chooseTheme": "Izberite temo", + + "light": "Svetla", + + "dark": "Temna", + + "blueLightTheme": "Modra svetla tema", + + "blueDarkTheme": "Modra temna tema", + + "sarRed": "SAR rdeča", + + "alertEmergencyMode": "Način opozorila/nujni primer", + + "sarGreen": "SAR zelena", + + "safeAllClearMode": "Način varno/vse jasno", + + "autoSystem": "Samodejno (Sistem)", + + "followSystemTheme": "Sledi sistemski temi", + + "showRxTxIndicators": "Prikaži RX/TX kazalnike", + + "displayPacketActivity": "Prikaži kazalnike aktivnosti paketov v zgornji vrstici", + + "language": "Jezik", + + "chooseLanguage": "Izberite jezik", + + "english": "Angleščina", + + "slovenian": "Slovenščina", + + "croatian": "Hrvaščina", + + "locationBroadcasting": "Oddajanje lokacije", + + "autoLocationTracking": "Samodejno sledenje lokaciji", + + "automaticallyBroadcastPosition": "Samodejno oddajaj posodobitve položaja", + + "configureTracking": "Konfiguriraj sledenje", + + "distanceAndTimeThresholds": "Pragovi razdalje in časa", + + "locationTrackingConfiguration": "Konfiguracija sledenja lokaciji", + + "configureWhenLocationBroadcasts": "Konfigurirajte, kdaj se oddajanja lokacije pošiljajo v omrežje mesh", + + "minimumDistance": "Minimalna razdalja", + + "broadcastAfterMoving": "Oddajaj šele po premiku {distance} metrov", + + "maximumDistance": "Maksimalna razdalja", + + "alwaysBroadcastAfterMoving": "Vedno oddajaj po premiku {distance} metrov", + + "minimumTimeInterval": "Minimalni časovni interval", + + "alwaysBroadcastEvery": "Vedno oddajaj vsakih {duration}", + + "save": "Shrani", + + "cancel": "Prekliči", + + "close": "Zapri", + + "about": "O aplikaciji", + + "appVersion": "Različica aplikacije", + + "appName": "Ime aplikacije", + + "aboutMeshCoreSar": "O MeshCore SAR", + + "aboutDescription": "Aplikacija za iskanje in reševanje, zasnovana za ekipe za odzivanje v nujnih primerih. Funkcije vključujejo:\n\n• BLE mesh omrežje za komunikacijo naprava-naprava\n• Brez povezave delujejo zemljevidi z več sloji\n• Sledenje članov ekipe v realnem času\n• SAR taktični označevalci (najdena oseba, ogenj, zbirališče)\n• Upravljanje stikov in sporočanje\n• GPS sledenje s kompasno smerjo\n• Predpomnenje ploščic zemljevida za uporabo brez povezave", + + "technologiesUsed": "Uporabljene tehnologije:", + + "technologiesList": "• Flutter za večplatformni razvoj\n• BLE (Bluetooth Low Energy) za mesh omrežje\n• OpenStreetMap za kartografijo\n• Provider za upravljanje stanja\n• SharedPreferences za lokalno shranjevanje", + + "developer": "Razvijalec", + + "packageName": "Ime paketa", + + "sampleData": "Vzorčni podatki", + + "sampleDataDescription": "Naložite ali počistite vzorčne stike, sporočila kanalov in SAR označevalce za testiranje", + + "loadSampleData": "Naloži vzorčne podatke", + + "clearAllData": "Počisti vse podatke", + + "clearAllDataConfirmTitle": "Počisti vse podatke", + + "clearAllDataConfirmMessage": "To bo počistilo vse stike in SAR označevalce. Ste prepričani?", + + "clear": "Počisti", + + "loadedSampleData": "Naloženih {teamCount} članov ekipe, {channelCount} kanalov, {sarCount} SAR označevalcev, {messageCount} sporočil", + + "failedToLoadSampleData": "Nalaganje vzorčnih podatkov ni uspelo: {error}", + + "allDataCleared": "Vsi podatki počiščeni", + + "failedToStartBackgroundTracking": "Zagon sledenja v ozadju ni uspel. Preverite dovoljenja in BLE povezavo.", + + "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." +} diff --git a/lib/main.dart b/lib/main.dart index 455593a..1c3ea20 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'providers/connection_provider.dart'; @@ -8,8 +9,10 @@ import 'providers/map_provider.dart'; import 'providers/drawing_provider.dart'; import 'providers/app_provider.dart'; import 'services/tile_cache_service.dart'; +import 'services/locale_preferences.dart'; import 'screens/home_screen.dart'; import 'theme/app_theme.dart'; +import 'l10n/app_localizations.dart'; void main() { runApp(const MeshCoreSarApp()); @@ -24,6 +27,7 @@ class MeshCoreSarApp extends StatefulWidget { class _MeshCoreSarAppState extends State { AppThemeMode _themeMode = AppThemeMode.system; + Locale? _locale; bool _isInitialized = false; @override @@ -34,6 +38,7 @@ class _MeshCoreSarAppState extends State { Future _initializeApp() async { await _loadThemePreference(); + await _loadLocalePreference(); setState(() { _isInitialized = true; }); @@ -47,12 +52,25 @@ class _MeshCoreSarAppState extends State { }); } + Future _loadLocalePreference() async { + final locale = await LocalePreferences.getLocale(); + setState(() { + _locale = locale; + }); + } + void _handleThemeChanged(AppThemeMode mode) { setState(() { _themeMode = mode; }); } + void _handleLocaleChanged(Locale? locale) { + setState(() { + _locale = locale; + }); + } + @override Widget build(BuildContext context) { if (!_isInitialized) { @@ -125,9 +143,19 @@ class _MeshCoreSarAppState extends State { title: 'MeshCore SAR', debugShowCheckedModeBanner: false, theme: AppTheme.getTheme(_themeMode, systemBrightness), + locale: _locale, + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: LocalePreferences.supportedLocales, home: HomeScreen( onThemeChanged: _handleThemeChanged, + onLocaleChanged: _handleLocaleChanged, currentTheme: _themeMode, + currentLocale: _locale, ), ); }, diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 7a8360c..85b2256 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -18,12 +18,16 @@ import '../utils/toast_logger.dart'; class HomeScreen extends StatefulWidget { final Function(AppThemeMode) onThemeChanged; + final Function(Locale?) onLocaleChanged; final AppThemeMode currentTheme; + final Locale? currentLocale; const HomeScreen({ super.key, required this.onThemeChanged, + required this.onLocaleChanged, required this.currentTheme, + required this.currentLocale, }); @override @@ -493,7 +497,9 @@ class _HomeScreenState extends State MaterialPageRoute( builder: (context) => SettingsScreen( onThemeChanged: widget.onThemeChanged, + onLocaleChanged: widget.onLocaleChanged, currentTheme: widget.currentTheme, + currentLocale: widget.currentLocale, ), ), ); @@ -575,72 +581,131 @@ class _HomeScreenState extends State return Row( children: [ + // LEFT SECTION: Name + BT/Battery status + Settings cog Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Text( - 'MeshCore', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - if (isConnected) - Row( + // Name and status group + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - // BLE connection strength indicator with RSSI - Icon( - Icons.bluetooth_connected, - color: deviceInfo.signalRssi != null - ? _getSignalColor(deviceInfo.signalRssi!) - : Colors.grey, - size: 14, + Text( + isConnected && deviceInfo.selfName != null + ? deviceInfo.selfName! + : 'MeshCore', + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + overflow: TextOverflow.ellipsis, ), - const SizedBox(width: 3), - if (deviceInfo.signalRssi != null) - Flexible( - child: Text( - '${deviceInfo.signalRssi}', - style: TextStyle( - fontSize: 12, - color: _getSignalColor(deviceInfo.signalRssi!), - fontWeight: FontWeight.w500, + if (isConnected) + Row( + mainAxisSize: MainAxisSize.min, + children: [ + // BLE connection strength indicator with RSSI + Icon( + Icons.bluetooth_connected, + color: deviceInfo.signalRssi != null + ? _getSignalColor(deviceInfo.signalRssi!) + : Colors.grey, + size: 14, ), - overflow: TextOverflow.ellipsis, - ), - ), - const SizedBox(width: 8), - // Battery indicator - if (deviceInfo.batteryPercent != null) ...[ - Icon( - _getBatteryIcon(deviceInfo.batteryPercent!), - color: _getBatteryColor(deviceInfo.batteryPercent!), - size: 14, - ), - const SizedBox(width: 3), - Flexible( - child: Text( - '${deviceInfo.batteryPercent!.round()}%', - style: TextStyle( - fontSize: 12, - color: _getBatteryColor( - deviceInfo.batteryPercent!, + const SizedBox(width: 3), + if (deviceInfo.signalRssi != null) + Flexible( + child: Text( + '${deviceInfo.signalRssi}', + style: TextStyle( + fontSize: 12, + color: _getSignalColor(deviceInfo.signalRssi!), + fontWeight: FontWeight.w500, + ), + overflow: TextOverflow.ellipsis, + ), ), - ), - overflow: TextOverflow.ellipsis, - ), + const SizedBox(width: 8), + // Battery indicator + if (deviceInfo.batteryPercent != null) ...[ + Icon( + _getBatteryIcon(deviceInfo.batteryPercent!), + color: _getBatteryColor(deviceInfo.batteryPercent!), + size: 14, + ), + const SizedBox(width: 3), + Flexible( + child: Text( + '${deviceInfo.batteryPercent!.round()}%', + style: TextStyle( + fontSize: 12, + color: _getBatteryColor( + deviceInfo.batteryPercent!, + ), + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ], + ) + else if (provider.isReconnecting) + Text( + 'Reconnecting...', + style: TextStyle(fontSize: 12, color: Colors.orange[600]), + overflow: TextOverflow.ellipsis, ), - ], ], - ) - else if (provider.isReconnecting) - Text( - 'Reconnecting... (${provider.reconnectionAttempt}/${provider.maxReconnectionAttempts})', - style: TextStyle(fontSize: 14, color: Colors.orange[600]), ), + ), + // Settings cog - part of left group + if (isConnected) ...[ + const SizedBox(width: 8), + GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const DeviceConfigScreen(), + ), + ); + }, + onLongPress: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + PacketLogScreen(bleService: provider.bleService), + ), + ); + }, + child: Container( + width: 36, + height: 36, + alignment: Alignment.center, + child: const Icon(Icons.settings, size: 20), + ), + ), + ], ], ), ), + + // CENTER SECTION: Broadcast button (when connected) + if (isConnected) ...[ + FilledButton( + onPressed: () => _advertiseDevice(context), + style: FilledButton.styleFrom( + backgroundColor: Colors.blue.shade700, + foregroundColor: Colors.white, + padding: const EdgeInsets.all(10), + minimumSize: const Size(40, 40), + shape: const CircleBorder(), + ), + child: const Icon(Icons.campaign, size: 20), + ), + ], + + // RIGHT SECTION: Connect button OR RX/TX indicators if (!isConnected) Row( mainAxisSize: MainAxisSize.min, @@ -663,7 +728,7 @@ class _HomeScreenState extends State : const Icon(Icons.bluetooth, size: 18), label: Text( provider.isReconnecting - ? 'Reconnecting (${provider.reconnectionAttempt}/${provider.maxReconnectionAttempts})' + ? '${provider.reconnectionAttempt}/${provider.maxReconnectionAttempts}' : 'Connect', ), style: ElevatedButton.styleFrom( @@ -691,123 +756,72 @@ class _HomeScreenState extends State ], ], ) - else - Row( - mainAxisSize: MainAxisSize.min, - children: [ - // Advertise button (broadcast location) - FilledButton( - onPressed: () => _advertiseDevice(context), - style: FilledButton.styleFrom( - backgroundColor: Colors.blue.shade700, - foregroundColor: Colors.white, - padding: const EdgeInsets.all(10), - minimumSize: const Size(40, 40), - shape: const CircleBorder(), + else if (_showRxTxIndicators) + GestureDetector( + onLongPress: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + PacketLogScreen(bleService: provider.bleService), ), - child: const Icon(Icons.campaign, size: 20), - ), - if (_showRxTxIndicators) ...[ - const SizedBox(width: 8), - // RX/TX indicators with long press to open packet log - GestureDetector( - onLongPress: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - PacketLogScreen(bleService: provider.bleService), + ); + }, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // RX indicator + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: provider.rxActivity + ? Colors.green + : Colors.grey.withOpacity(0.3), ), - ); - }, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // RX indicator - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 8, - height: 8, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: provider.rxActivity - ? Colors.green - : Colors.grey.withOpacity(0.3), - ), - ), - const SizedBox(width: 4), - Text( - 'RX:${provider.rxPacketCount}', - style: const TextStyle( - fontSize: 11, - color: Colors.grey, - ), - ), - ], + ), + const SizedBox(width: 4), + Text( + 'RX:${provider.rxPacketCount}', + style: const TextStyle( + fontSize: 11, + color: Colors.grey, ), - const SizedBox(height: 4), - // TX indicator - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 8, - height: 8, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: provider.txActivity - ? Colors.blue - : Colors.grey.withOpacity(0.3), - ), - ), - const SizedBox(width: 4), - Text( - 'TX:${provider.txPacketCount}', - style: const TextStyle( - fontSize: 11, - color: Colors.grey, - ), - ), - ], - ), - ], - ), + ), + ], + ), + const SizedBox(height: 4), + // TX indicator + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: provider.txActivity + ? Colors.blue + : Colors.grey.withOpacity(0.3), + ), + ), + const SizedBox(width: 4), + Text( + 'TX:${provider.txPacketCount}', + style: const TextStyle( + fontSize: 11, + color: Colors.grey, + ), + ), + ], ), - const SizedBox(width: 12), ], - // Settings button (tap for device settings, long press for packet logs) - GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const DeviceConfigScreen(), - ), - ); - }, - onLongPress: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - PacketLogScreen(bleService: provider.bleService), - ), - ); - }, - child: Container( - width: 36, - height: 36, - alignment: Alignment.center, - child: const Icon(Icons.settings, size: 20), - ), - ), - const SizedBox(width: 16), - - // Disconnect button (prominent, icon only) - pushed to far right edge - ], + ), ), ], ); diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 1805f5f..b672338 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -8,17 +8,22 @@ import '../providers/contacts_provider.dart'; import '../providers/messages_provider.dart'; import '../providers/app_provider.dart'; import '../services/location_tracking_service.dart'; +import '../services/locale_preferences.dart'; import '../utils/sample_data_generator.dart'; import '../theme/app_theme.dart'; class SettingsScreen extends StatefulWidget { final Function(AppThemeMode) onThemeChanged; + final Function(Locale?) onLocaleChanged; final AppThemeMode currentTheme; + final Locale? currentLocale; const SettingsScreen({ super.key, required this.onThemeChanged, + required this.onLocaleChanged, required this.currentTheme, + required this.currentLocale, }); @override @@ -27,6 +32,7 @@ class SettingsScreen extends StatefulWidget { class _SettingsScreenState extends State { late AppThemeMode _selectedTheme; + late Locale? _selectedLocale; PackageInfo? _packageInfo; bool _isLoadingSampleData = false; bool _showRxTxIndicators = true; @@ -36,6 +42,7 @@ class _SettingsScreenState extends State { void initState() { super.initState(); _selectedTheme = widget.currentTheme; + _selectedLocale = widget.currentLocale; _loadPackageInfo(); _initializeLocationService(); _loadRxTxPreference(); @@ -135,6 +142,18 @@ class _SettingsScreenState extends State { } } + Future _saveLocalePreference(Locale? locale) async { + await LocalePreferences.setLocale(locale); + } + + void _handleLocaleChange(Locale? locale) { + setState(() { + _selectedLocale = locale; + }); + _saveLocalePreference(locale); + widget.onLocaleChanged(locale); + } + Future _loadSampleData() async { setState(() => _isLoadingSampleData = true); @@ -322,6 +341,13 @@ class _SettingsScreenState extends State { await _saveRxTxPreference(value); }, ), + ListTile( + leading: const Icon(Icons.language), + title: const Text('Language'), + subtitle: Text(LocalePreferences.getDisplayName(_selectedLocale)), + trailing: const Icon(Icons.chevron_right), + onTap: () => _showLanguageDialog(), + ), const Divider(), // Location Settings Section @@ -761,6 +787,50 @@ class _SettingsScreenState extends State { ); } + void _showLanguageDialog() { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Choose Language'), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + RadioListTile( + title: const Text('System Default'), + subtitle: const Text('Follow system language'), + value: null, + groupValue: _selectedLocale, + onChanged: (value) { + _handleLocaleChange(value); + Navigator.pop(context); + }, + ), + const Divider(), + ...LocalePreferences.supportedLocales.map((locale) { + return RadioListTile( + title: Text(LocalePreferences.getNativeDisplayName(locale)), + value: locale, + groupValue: _selectedLocale, + onChanged: (value) { + _handleLocaleChange(value); + Navigator.pop(context); + }, + ); + }), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + ], + ), + ); + } + void _showAboutDialog() { showDialog( context: context, diff --git a/lib/services/locale_preferences.dart b/lib/services/locale_preferences.dart new file mode 100644 index 0000000..600715d --- /dev/null +++ b/lib/services/locale_preferences.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +/// Service for managing locale preferences +class LocalePreferences { + static const String _localeKey = 'app_locale'; + + /// Supported locales + static const List supportedLocales = [ + Locale('en'), // English + Locale('sl'), // Slovenian + Locale('hr'), // Croatian + ]; + + /// Get the saved locale or return null to use system locale + static Future getLocale() async { + final prefs = await SharedPreferences.getInstance(); + final localeCode = prefs.getString(_localeKey); + + if (localeCode == null) { + return null; // Use system locale + } + + return Locale(localeCode); + } + + /// Save the selected locale + static Future setLocale(Locale? locale) async { + final prefs = await SharedPreferences.getInstance(); + + if (locale == null) { + // Remove preference to use system locale + await prefs.remove(_localeKey); + } else { + await prefs.setString(_localeKey, locale.languageCode); + } + } + + /// Get display name for a locale + static String getDisplayName(Locale? locale) { + if (locale == null) { + return 'System Default'; + } + + switch (locale.languageCode) { + case 'en': + return 'English'; + case 'sl': + return 'Slovenščina'; + case 'hr': + return 'Hrvatski'; + default: + return locale.languageCode; + } + } + + /// Get native display name for a locale (shown in selection dialog) + static String getNativeDisplayName(Locale locale) { + switch (locale.languageCode) { + case 'en': + return 'English'; + case 'sl': + return 'Slovenščina'; + case 'hr': + return 'Hrvatski'; + default: + return locale.languageCode; + } + } +} diff --git a/pubspec.lock b/pubspec.lock index 36107a6..cf5418d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -246,6 +246,11 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" flutter_map: dependency: "direct main" description: @@ -361,7 +366,7 @@ packages: source: hosted version: "4.1.2" intl: - dependency: transitive + dependency: "direct main" description: name: intl sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" diff --git a/pubspec.yaml b/pubspec.yaml index a26d8a6..41b7d94 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,6 +30,11 @@ environment: dependencies: flutter: sdk: flutter + flutter_localizations: + sdk: flutter + + # Internationalization + intl: any # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. @@ -90,6 +95,9 @@ flutter: # the material Icons class. uses-material-design: true + # Enable generation of localization files + generate: true + # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg