Link between front-end and back-end
This commit is contained in:
@ -8,7 +8,8 @@ plugins {
|
||||
android {
|
||||
namespace = "fr.chaboissier.rtime"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
//ndkVersion = flutter.ndkVersion
|
||||
ndkVersion = "27.0.12077973"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
|
||||
@ -49,4 +49,8 @@
|
||||
<!-- GPS Location permission to be able to locate flights. -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
|
||||
<!-- Camera/storage permission to be able to take picture of the drone/battery -->
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
</manifest>
|
||||
|
||||
@ -47,5 +47,11 @@
|
||||
<true/>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>To locate the flights.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>This app needs access to your camera to take photos for your drones/batteries.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>This app needs access to your photo gallery to select images for your drones/batteries.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>This app does not use the microphone.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -50,7 +50,7 @@ class DbHelper {
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
volatege REAL NOT NULL,
|
||||
voltage REAL NOT NULL,
|
||||
image_uuid TEXT
|
||||
)
|
||||
''');
|
||||
@ -84,6 +84,16 @@ class DbHelper {
|
||||
return maps.map((e) => Drone.fromMap(e)).toList();
|
||||
}
|
||||
|
||||
Future<int> updateDrone(Drone drone) async {
|
||||
final db = await database;
|
||||
return await db.update(
|
||||
"drones",
|
||||
drone.toMap(),
|
||||
where: "id = ?",
|
||||
whereArgs: [drone.id],
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> deleteDrone(int droneId) async {
|
||||
// TODO: Delete image
|
||||
final db = await database;
|
||||
@ -103,6 +113,16 @@ class DbHelper {
|
||||
return maps.map((e) => Battery.fromMap(e)).toList();
|
||||
}
|
||||
|
||||
Future<int> updateBattery(Battery battery) async {
|
||||
final db = await database;
|
||||
return await db.update(
|
||||
"batteries",
|
||||
battery.toMap(),
|
||||
where: "id = ?",
|
||||
whereArgs: [battery.id],
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> deleteBattery(int batteryId) async {
|
||||
// TODO: Delete image
|
||||
final db = await database;
|
||||
|
||||
@ -8,7 +8,6 @@ import 'package:path/path.dart' as path;
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:image_cropper/image_cropper.dart' as img_cropper;
|
||||
import 'package:uuid/v5.dart';
|
||||
|
||||
class ImagesManager {
|
||||
static final ImagesManager instance = ImagesManager._internal();
|
||||
@ -32,10 +31,10 @@ class ImagesManager {
|
||||
final directory = Directory.fromUri(directoryUri);
|
||||
|
||||
if (!await directory.exists()) {
|
||||
log.info("Image directory does not yet extists. Creating it.");
|
||||
log.info("Image directory does not yet exist. Creating it.");
|
||||
}
|
||||
|
||||
directory.create(recursive: false);
|
||||
await directory.create(recursive: true);
|
||||
|
||||
log.info("Image directory set up at '$directory'");
|
||||
|
||||
@ -43,11 +42,9 @@ class ImagesManager {
|
||||
}
|
||||
|
||||
Future<String?> createImage(ImageSource source) async {
|
||||
// Get image from camera or not
|
||||
final XFile? ximage = await ImagePicker().pickImage(source: source);
|
||||
if (ximage == null) return null;
|
||||
|
||||
// Crop image
|
||||
final uuid = Uuid().v6();
|
||||
final imageDir = await imageDirectory;
|
||||
final finalPath = path.join(
|
||||
@ -60,10 +57,9 @@ class ImagesManager {
|
||||
);
|
||||
await ximage.saveTo(tempPath);
|
||||
|
||||
img_cropper.CroppedFile? cropped = await img_cropper.ImageCropper()
|
||||
.cropImage(
|
||||
img_cropper.CroppedFile? cropped = await img_cropper.ImageCropper().cropImage(
|
||||
sourcePath: tempPath,
|
||||
aspectRatio: img_cropper.CropAspectRatio(ratioX: 1.0, ratioY: 1.0),
|
||||
aspectRatio: const img_cropper.CropAspectRatio(ratioX: 1.0, ratioY: 1.0),
|
||||
uiSettings: [
|
||||
img_cropper.AndroidUiSettings(
|
||||
toolbarTitle: "Crop image",
|
||||
@ -74,7 +70,10 @@ class ImagesManager {
|
||||
],
|
||||
);
|
||||
|
||||
if (cropped == null) return null;
|
||||
if (cropped == null) {
|
||||
await File(tempPath).delete();
|
||||
return null;
|
||||
}
|
||||
|
||||
await File(finalPath).writeAsBytes(await cropped.readAsBytes());
|
||||
await File(tempPath).delete();
|
||||
@ -94,10 +93,37 @@ class ImagesManager {
|
||||
final imagePath = path.join(imageDir.path, "$imageUuid.jpg");
|
||||
final file = File(imagePath);
|
||||
if (!await file.exists()) {
|
||||
log.warning("Tried to load an image that does not extist: '$imagePath'.");
|
||||
log.warning("Tried to load an image that does not exist: '$imagePath'.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return Image.file(file);
|
||||
}
|
||||
|
||||
Future<bool> deleteImage(String imageUuid) async {
|
||||
final imageDir = await imageDirectory;
|
||||
if (!Uuid.isValidUUID(fromString: imageUuid)) {
|
||||
log.warning(
|
||||
"Tried to delete an image with an invalid UUID : '$imageUuid'.",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
final imagePath = path.join(imageDir.path, "$imageUuid.jpg");
|
||||
final file = File(imagePath);
|
||||
|
||||
if (await file.exists()) {
|
||||
try {
|
||||
await file.delete();
|
||||
log.info("Image with UUID '$imageUuid' deleted successfully.");
|
||||
return true;
|
||||
} catch (e) {
|
||||
log.severe("Error deleting image with UUID '$imageUuid': $e");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
log.warning("Tried to delete an image that does not exist: '$imagePath'.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
210
lib/l10n/app_en.arb
Normal file
210
lib/l10n/app_en.arb
Normal file
@ -0,0 +1,210 @@
|
||||
{
|
||||
"@@locale": "en",
|
||||
"appTitle": "Rtime",
|
||||
"@appTitle": {
|
||||
"description": "The title of the application"
|
||||
},
|
||||
"yourDrones": "Your Drones",
|
||||
"addDrone": "Add Drone",
|
||||
"yourBatteries": "Your Batteries",
|
||||
"addBattery": "Add Battery",
|
||||
"latestFlights": "Latest Flights",
|
||||
"newFlight": "New Flight",
|
||||
"detailsOfDrone": "Details of drone: {droneName}",
|
||||
"@detailsOfDrone": {
|
||||
"placeholders": {
|
||||
"droneName": {
|
||||
"type": "String",
|
||||
"example": "Chimera7"
|
||||
}
|
||||
}
|
||||
},
|
||||
"detailsOfBattery": "Details of battery: {batteryName}",
|
||||
"@detailsOfBattery": {
|
||||
"placeholders": {
|
||||
"batteryName": {
|
||||
"type": "String",
|
||||
"example": "GNB 1300mAh"
|
||||
}
|
||||
}
|
||||
},
|
||||
"detailsOfFlight": "Details of flight: {flightName}",
|
||||
"@detailsOfFlight": {
|
||||
"placeholders": {
|
||||
"flightName": {
|
||||
"type": "String",
|
||||
"example": "Flight at the beach - 01/07/2025"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settingsTitle": "Settings",
|
||||
"languageSetting": "Language",
|
||||
"english": "English",
|
||||
"french": "French",
|
||||
"languageChangedTo": "Language changed to {languageName}",
|
||||
"@languageChangedTo": {
|
||||
"placeholders": {
|
||||
"languageName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chooseDrone": "Choose your Drone",
|
||||
"selectDroneHint": "Select a drone",
|
||||
"chooseBattery": "Choose your Battery",
|
||||
"selectBatteryHint": "Select a battery",
|
||||
"startFlight": "Start Flight",
|
||||
"stopFlight": "Stop Flight",
|
||||
"selectDroneBattery": "Please select a drone and a battery.",
|
||||
"flightStarted": "Flight started!",
|
||||
"flightStopped": "Flight stopped. Duration: {flightDuration}",
|
||||
"@flightStopped": {
|
||||
"placeholders": {
|
||||
"flightDuration": {
|
||||
"type": "String",
|
||||
"example": "00:05:30"
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordFlightLocation": "Record flight location (GPS)",
|
||||
"locationServicesDisabled": "Location services are disabled.",
|
||||
"locationPermissionsDenied": "Location permissions are denied.",
|
||||
"locationPermissionsDeniedForever": "Location permissions are permanently denied, we cannot request permissions.",
|
||||
"locationObtained": "Location obtained: Lat {latitude}, Lng {longitude}",
|
||||
"@locationObtained": {
|
||||
"placeholders": {
|
||||
"latitude": {
|
||||
"type": "double"
|
||||
},
|
||||
"longitude": {
|
||||
"type": "double"
|
||||
}
|
||||
}
|
||||
},
|
||||
"locationRequiredForFlight": "GPS location is required but could not be obtained. Please check permissions and try again.",
|
||||
"obtainingLocation": "Obtaining location...",
|
||||
"failedToGetLocation": "Failed to get location: {error}",
|
||||
"@failedToGetLocation": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"currentCoordinates": "Current coordinates: Lat {latitude}, Lng {longitude}",
|
||||
"@currentCoordinates": {
|
||||
"placeholders": {
|
||||
"latitude": {
|
||||
"type": "double"
|
||||
},
|
||||
"longitude": {
|
||||
"type": "double"
|
||||
}
|
||||
}
|
||||
},
|
||||
"errorLoadingDrones": "Error loading drones",
|
||||
"errorLoadingBatteries": "Error loading batteries",
|
||||
"noDronesYet": "No drones added yet. Tap the '+' card to add one!",
|
||||
"noBatteriesYet": "No batteries added yet. Tap the '+' card to add one!",
|
||||
"flightSavedSuccessfully": "Flight saved successfully!",
|
||||
"failedToSaveFlight": "Failed to save flight: {error}",
|
||||
"@failedToSaveFlight": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"errorLoadingFlights": "Error loading flights",
|
||||
"noFlightsYet": "No flights recorded yet.",
|
||||
"droneName": "Drone Name",
|
||||
"pleaseEnterDroneName": "Please enter a drone name",
|
||||
"imageUuidOptional": "Image UUID (Optional)",
|
||||
"saveDrone": "Save Drone",
|
||||
"droneAddedSuccessfully": "Drone added successfully!",
|
||||
"failedToAddDrone": "Failed to add drone",
|
||||
"batteryName": "Battery Name",
|
||||
"batteryType": "Battery Type",
|
||||
"batteryVoltage": "Voltage (V)",
|
||||
"pleaseEnterBatteryName": "Please enter a battery name",
|
||||
"pleaseEnterBatteryType": "Please enter a battery type",
|
||||
"pleaseEnterBatteryVoltage": "Please enter battery voltage",
|
||||
"pleaseEnterValidNumber": "Please enter a valid number",
|
||||
"saveBattery": "Save Battery",
|
||||
"batteryAddedSuccessfully": "Battery added successfully!",
|
||||
"failedToAddBattery": "Failed to add battery",
|
||||
"droneImage": "Drone Image",
|
||||
"batteryImage": "Battery Image",
|
||||
"takePhoto": "Take Photo",
|
||||
"chooseFromGallery": "Choose from Gallery",
|
||||
"imageSelected": "Image selected!",
|
||||
"imageSelectionCancelled": "Image selection cancelled.",
|
||||
"removeImage": "Remove Image",
|
||||
"imageDeletedSuccessfully": "Image deleted successfully!",
|
||||
"droneDetails": "Drone Details",
|
||||
"editDrone": "Edit Drone",
|
||||
"saveChanges": "Save Changes",
|
||||
"droneUpdatedSuccessfully": "Drone updated successfully!",
|
||||
"failedToUpdateDrone": "Failed to update drone",
|
||||
"deleteDroneConfirmationTitle": "Delete Drone?",
|
||||
"deleteDroneConfirmationMessage": "Are you sure you want to delete {droneName}? This action cannot be undone.",
|
||||
"@deleteDroneConfirmationMessage": {
|
||||
"placeholders": {
|
||||
"droneName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"droneDeletedSuccessfully": "Drone deleted successfully!",
|
||||
"failedToDeleteDrone": "Failed to delete drone",
|
||||
"batteryDetails": "Battery Details",
|
||||
"editBattery": "Edit Battery",
|
||||
"batteryUpdatedSuccessfully": "Battery updated successfully!",
|
||||
"failedToUpdateBattery": "Failed to update battery",
|
||||
"deleteBatteryConfirmationTitle": "Delete Battery?",
|
||||
"deleteBatteryConfirmationMessage": "Are you sure you want to delete {batteryName}? This action cannot be undone.",
|
||||
"@deleteBatteryConfirmationMessage": {
|
||||
"placeholders": {
|
||||
"batteryName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"batteryDeletedSuccessfully": "Battery deleted successfully!",
|
||||
"failedToDeleteBattery": "Failed to delete battery",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"flightDetails": "Flight Details",
|
||||
"startTime": "Start Time",
|
||||
"endTime": "End Time",
|
||||
"duration": "Duration",
|
||||
"flightLocation": "Flight Location",
|
||||
"noLocationData": "No location data for this flight.",
|
||||
"unknown": "Unknown",
|
||||
"errorLoadingData": "Error loading data",
|
||||
"drone": "Drone",
|
||||
"battery": "Battery",
|
||||
"themeSetting": "Theme",
|
||||
"themeLight": "Light Theme",
|
||||
"themeDark": "Dark Theme",
|
||||
"themeSystem": "System Default",
|
||||
"themeChangedTo": "Theme changed to {themeName}",
|
||||
"@themeChangedTo": {
|
||||
"placeholders": {
|
||||
"themeName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deleteFlightConfirmationTitle": "Delete Flight?",
|
||||
"deleteFlightConfirmationMessage": "Are you sure you want to delete {flightName}? This action cannot be undone.",
|
||||
"@deleteFlightConfirmationMessage": {
|
||||
"placeholders": {
|
||||
"flightName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"flightDeletedSuccessfully": "Flight deleted successfully!",
|
||||
"failedToDeleteFlight": "Failed to delete flight"
|
||||
}
|
||||
210
lib/l10n/app_fr.arb
Normal file
210
lib/l10n/app_fr.arb
Normal file
@ -0,0 +1,210 @@
|
||||
{
|
||||
"@@locale": "fr",
|
||||
"appTitle": "Rtime",
|
||||
"@appTitle": {
|
||||
"description": "Le titre de l'application"
|
||||
},
|
||||
"yourDrones": "Vos Drones",
|
||||
"addDrone": "Ajouter un drone",
|
||||
"yourBatteries": "Vos Batteries",
|
||||
"addBattery": "Ajouter une batterie",
|
||||
"latestFlights": "Derniers Vols",
|
||||
"newFlight": "Nouveau Vol",
|
||||
"detailsOfDrone": "Détails du drone : {droneName}",
|
||||
"@detailsOfDrone": {
|
||||
"placeholders": {
|
||||
"droneName": {
|
||||
"type": "String",
|
||||
"example": "Chimera7"
|
||||
}
|
||||
}
|
||||
},
|
||||
"detailsOfBattery": "Détails de la batterie : {batteryName}",
|
||||
"@detailsOfBattery": {
|
||||
"placeholders": {
|
||||
"batteryName": {
|
||||
"type": "String",
|
||||
"example": "GNB 1300mAh"
|
||||
}
|
||||
}
|
||||
},
|
||||
"detailsOfFlight": "Détails du vol : {flightName}",
|
||||
"@detailsOfFlight": {
|
||||
"placeholders": {
|
||||
"flightName": {
|
||||
"type": "String",
|
||||
"example": "Vol à la plage - 01/07/2025"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settingsTitle": "Paramètres",
|
||||
"languageSetting": "Langue",
|
||||
"english": "Anglais",
|
||||
"french": "Français",
|
||||
"languageChangedTo": "Langue changée en {languageName}",
|
||||
"@languageChangedTo": {
|
||||
"placeholders": {
|
||||
"languageName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chooseDrone": "Choisissez votre Drone",
|
||||
"selectDroneHint": "Sélectionnez un drone",
|
||||
"chooseBattery": "Choisissez votre Batterie",
|
||||
"selectBatteryHint": "Sélectionnez une batterie",
|
||||
"startFlight": "Lancer le vol",
|
||||
"stopFlight": "Arrêter le vol",
|
||||
"selectDroneBattery": "Veuillez sélectionner un drone et une batterie.",
|
||||
"flightStarted": "Vol démarré !",
|
||||
"flightStopped": "Vol arrêté. Durée : {flightDuration}",
|
||||
"@flightStopped": {
|
||||
"placeholders": {
|
||||
"flightDuration": {
|
||||
"type": "String",
|
||||
"example": "00:05:30"
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordFlightLocation": "Enregistrer la localisation du vol (GPS)",
|
||||
"locationServicesDisabled": "Les services de localisation sont désactivés.",
|
||||
"locationPermissionsDenied": "Les permissions de localisation sont refusées.",
|
||||
"locationPermissionsDeniedForever": "Les permissions de localisation sont refusées de manière permanente, nous ne pouvons pas les demander.",
|
||||
"locationObtained": "Localisation obtenue : Lat {latitude}, Lon {longitude}",
|
||||
"@locationObtained": {
|
||||
"placeholders": {
|
||||
"latitude": {
|
||||
"type": "double"
|
||||
},
|
||||
"longitude": {
|
||||
"type": "double"
|
||||
}
|
||||
}
|
||||
},
|
||||
"locationRequiredForFlight": "La localisation GPS est requise mais n'a pu être obtenue. Veuillez vérifier les permissions et réessayer.",
|
||||
"obtainingLocation": "Obtention de la localisation...",
|
||||
"failedToGetLocation": "Échec de l'obtention de la localisation : {error}",
|
||||
"@failedToGetLocation": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"currentCoordinates": "Coordonnées actuelles : Lat {latitude}, Lon {longitude}",
|
||||
"@currentCoordinates": {
|
||||
"placeholders": {
|
||||
"latitude": {
|
||||
"type": "double"
|
||||
},
|
||||
"longitude": {
|
||||
"type": "double"
|
||||
}
|
||||
}
|
||||
},
|
||||
"errorLoadingDrones": "Erreur lors du chargement des drones",
|
||||
"errorLoadingBatteries": "Erreur lors du chargement des batteries",
|
||||
"noDronesYet": "Aucun drone ajouté pour le moment. Appuyez sur la carte '+' pour en ajouter un !",
|
||||
"noBatteriesYet": "Aucune batterie ajoutée pour le moment. Appuyez sur la carte '+' pour en ajouter une !",
|
||||
"flightSavedSuccessfully": "Vol enregistré avec succès !",
|
||||
"failedToSaveFlight": "Échec de l'enregistrement du vol : {error}",
|
||||
"@failedToSaveFlight": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"errorLoadingFlights": "Erreur lors du chargement des vols",
|
||||
"noFlightsYet": "Aucun vol enregistré pour le moment.",
|
||||
"droneName": "Nom du drone",
|
||||
"pleaseEnterDroneName": "Veuillez entrer un nom de drone",
|
||||
"imageUuidOptional": "UUID de l'image (Facultatif)",
|
||||
"saveDrone": "Enregistrer le drone",
|
||||
"droneAddedSuccessfully": "Drone ajouté avec succès !",
|
||||
"failedToAddDrone": "Échec de l'ajout du drone",
|
||||
"batteryName": "Nom de la batterie",
|
||||
"batteryType": "Type de batterie",
|
||||
"batteryVoltage": "Tension (V)",
|
||||
"pleaseEnterBatteryName": "Veuillez entrer un nom de batterie",
|
||||
"pleaseEnterBatteryType": "Veuillez entrer un type de batterie",
|
||||
"pleaseEnterBatteryVoltage": "Veuillez entrer la tension de la batterie",
|
||||
"pleaseEnterValidNumber": "Veuillez entrer un nombre valide",
|
||||
"saveBattery": "Enregistrer la batterie",
|
||||
"batteryAddedSuccessfully": "Batterie ajoutée avec succès !",
|
||||
"failedToAddBattery": "Échec de l'ajout de la batterie",
|
||||
"droneImage": "Image du drone",
|
||||
"batteryImage": "Image de la batterie",
|
||||
"takePhoto": "Prendre une photo",
|
||||
"chooseFromGallery": "Choisir de la galerie",
|
||||
"imageSelected": "Image sélectionnée !",
|
||||
"imageSelectionCancelled": "Sélection d'image annulée.",
|
||||
"removeImage": "Supprimer l'image",
|
||||
"imageDeletedSuccessfully": "Image supprimée avec succès !",
|
||||
"droneDetails": "Détails du drone",
|
||||
"editDrone": "Modifier le drone",
|
||||
"saveChanges": "Enregistrer les modifications",
|
||||
"droneUpdatedSuccessfully": "Drone mis à jour avec succès !",
|
||||
"failedToUpdateDrone": "Échec de la mise à jour du drone",
|
||||
"deleteDroneConfirmationTitle": "Supprimer le drone ?",
|
||||
"deleteDroneConfirmationMessage": "Êtes-vous sûr de vouloir supprimer {droneName} ? Cette action est irréversible.",
|
||||
"@deleteDroneConfirmationMessage": {
|
||||
"placeholders": {
|
||||
"droneName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"droneDeletedSuccessfully": "Drone supprimé avec succès !",
|
||||
"failedToDeleteDrone": "Échec de la suppression du drone",
|
||||
"batteryDetails": "Détails de la batterie",
|
||||
"editBattery": "Modifier la batterie",
|
||||
"batteryUpdatedSuccessfully": "Batterie mise à jour avec succès !",
|
||||
"failedToUpdateBattery": "Échec de la mise à jour de la batterie",
|
||||
"deleteBatteryConfirmationTitle": "Supprimer la batterie ?",
|
||||
"deleteBatteryConfirmationMessage": "Êtes-vous sûr de vouloir supprimer {batteryName} ? Cette action est irréversible.",
|
||||
"@deleteBatteryConfirmationMessage": {
|
||||
"placeholders": {
|
||||
"batteryName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"batteryDeletedSuccessfully": "Batterie supprimée avec succès !",
|
||||
"failedToDeleteBattery": "Échec de la suppression de la batterie",
|
||||
"cancel": "Annuler",
|
||||
"delete": "Supprimer",
|
||||
"flightDetails": "Détails du vol",
|
||||
"startTime": "Heure de début",
|
||||
"endTime": "Heure de fin",
|
||||
"duration": "Durée",
|
||||
"flightLocation": "Localisation du vol",
|
||||
"noLocationData": "Pas de données de localisation pour ce vol.",
|
||||
"unknown": "Inconnu",
|
||||
"errorLoadingData": "Erreur de chargement des données",
|
||||
"drone": "Drone",
|
||||
"battery": "Batterie",
|
||||
"themeSetting": "Thème",
|
||||
"themeLight": "Thème Clair",
|
||||
"themeDark": "Thème Sombre",
|
||||
"themeSystem": "Par défaut du système",
|
||||
"themeChangedTo": "Thème changé en {themeName}",
|
||||
"@themeChangedTo": {
|
||||
"placeholders": {
|
||||
"themeName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deleteFlightConfirmationTitle": "Supprimer le vol ?",
|
||||
"deleteFlightConfirmationMessage": "Êtes-vous sûr de vouloir supprimer {flightName} ? Cette action est irréversible.",
|
||||
"@deleteFlightConfirmationMessage": {
|
||||
"placeholders": {
|
||||
"flightName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"flightDeletedSuccessfully": "Vol supprimé avec succès !",
|
||||
"failedToDeleteFlight": "Échec de la suppression du vol"
|
||||
}
|
||||
747
lib/l10n/app_localizations.dart
Normal file
747
lib/l10n/app_localizations.dart
Normal file
@ -0,0 +1,747 @@
|
||||
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_fr.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<AppLocalizations>(context, AppLocalizations);
|
||||
}
|
||||
|
||||
static const LocalizationsDelegate<AppLocalizations> 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<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[
|
||||
delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
];
|
||||
|
||||
/// A list of this localizations delegate's supported locales.
|
||||
static const List<Locale> supportedLocales = <Locale>[
|
||||
Locale('en'),
|
||||
Locale('fr')
|
||||
];
|
||||
|
||||
/// The title of the application
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Rtime'**
|
||||
String get appTitle;
|
||||
|
||||
/// No description provided for @yourDrones.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Your Drones'**
|
||||
String get yourDrones;
|
||||
|
||||
/// No description provided for @addDrone.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add Drone'**
|
||||
String get addDrone;
|
||||
|
||||
/// No description provided for @yourBatteries.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Your Batteries'**
|
||||
String get yourBatteries;
|
||||
|
||||
/// No description provided for @addBattery.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add Battery'**
|
||||
String get addBattery;
|
||||
|
||||
/// No description provided for @latestFlights.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Latest Flights'**
|
||||
String get latestFlights;
|
||||
|
||||
/// No description provided for @newFlight.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'New Flight'**
|
||||
String get newFlight;
|
||||
|
||||
/// No description provided for @detailsOfDrone.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Details of drone: {droneName}'**
|
||||
String detailsOfDrone(String droneName);
|
||||
|
||||
/// No description provided for @detailsOfBattery.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Details of battery: {batteryName}'**
|
||||
String detailsOfBattery(String batteryName);
|
||||
|
||||
/// No description provided for @detailsOfFlight.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Details of flight: {flightName}'**
|
||||
String detailsOfFlight(String flightName);
|
||||
|
||||
/// No description provided for @settingsTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Settings'**
|
||||
String get settingsTitle;
|
||||
|
||||
/// No description provided for @languageSetting.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Language'**
|
||||
String get languageSetting;
|
||||
|
||||
/// No description provided for @english.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'English'**
|
||||
String get english;
|
||||
|
||||
/// No description provided for @french.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'French'**
|
||||
String get french;
|
||||
|
||||
/// No description provided for @languageChangedTo.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Language changed to {languageName}'**
|
||||
String languageChangedTo(String languageName);
|
||||
|
||||
/// No description provided for @chooseDrone.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose your Drone'**
|
||||
String get chooseDrone;
|
||||
|
||||
/// No description provided for @selectDroneHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select a drone'**
|
||||
String get selectDroneHint;
|
||||
|
||||
/// No description provided for @chooseBattery.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose your Battery'**
|
||||
String get chooseBattery;
|
||||
|
||||
/// No description provided for @selectBatteryHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select a battery'**
|
||||
String get selectBatteryHint;
|
||||
|
||||
/// No description provided for @startFlight.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Start Flight'**
|
||||
String get startFlight;
|
||||
|
||||
/// No description provided for @stopFlight.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Stop Flight'**
|
||||
String get stopFlight;
|
||||
|
||||
/// No description provided for @selectDroneBattery.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Please select a drone and a battery.'**
|
||||
String get selectDroneBattery;
|
||||
|
||||
/// No description provided for @flightStarted.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Flight started!'**
|
||||
String get flightStarted;
|
||||
|
||||
/// No description provided for @flightStopped.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Flight stopped. Duration: {flightDuration}'**
|
||||
String flightStopped(String flightDuration);
|
||||
|
||||
/// No description provided for @recordFlightLocation.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Record flight location (GPS)'**
|
||||
String get recordFlightLocation;
|
||||
|
||||
/// No description provided for @locationServicesDisabled.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Location services are disabled.'**
|
||||
String get locationServicesDisabled;
|
||||
|
||||
/// No description provided for @locationPermissionsDenied.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Location permissions are denied.'**
|
||||
String get locationPermissionsDenied;
|
||||
|
||||
/// No description provided for @locationPermissionsDeniedForever.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Location permissions are permanently denied, we cannot request permissions.'**
|
||||
String get locationPermissionsDeniedForever;
|
||||
|
||||
/// No description provided for @locationObtained.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Location obtained: Lat {latitude}, Lng {longitude}'**
|
||||
String locationObtained(double latitude, double longitude);
|
||||
|
||||
/// No description provided for @locationRequiredForFlight.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'GPS location is required but could not be obtained. Please check permissions and try again.'**
|
||||
String get locationRequiredForFlight;
|
||||
|
||||
/// No description provided for @obtainingLocation.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Obtaining location...'**
|
||||
String get obtainingLocation;
|
||||
|
||||
/// No description provided for @failedToGetLocation.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to get location: {error}'**
|
||||
String failedToGetLocation(String error);
|
||||
|
||||
/// No description provided for @currentCoordinates.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Current coordinates: Lat {latitude}, Lng {longitude}'**
|
||||
String currentCoordinates(double latitude, double longitude);
|
||||
|
||||
/// No description provided for @errorLoadingDrones.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Error loading drones'**
|
||||
String get errorLoadingDrones;
|
||||
|
||||
/// No description provided for @errorLoadingBatteries.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Error loading batteries'**
|
||||
String get errorLoadingBatteries;
|
||||
|
||||
/// No description provided for @noDronesYet.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No drones added yet. Tap the \'+\' card to add one!'**
|
||||
String get noDronesYet;
|
||||
|
||||
/// No description provided for @noBatteriesYet.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No batteries added yet. Tap the \'+\' card to add one!'**
|
||||
String get noBatteriesYet;
|
||||
|
||||
/// No description provided for @flightSavedSuccessfully.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Flight saved successfully!'**
|
||||
String get flightSavedSuccessfully;
|
||||
|
||||
/// No description provided for @failedToSaveFlight.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to save flight: {error}'**
|
||||
String failedToSaveFlight(String error);
|
||||
|
||||
/// No description provided for @errorLoadingFlights.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Error loading flights'**
|
||||
String get errorLoadingFlights;
|
||||
|
||||
/// No description provided for @noFlightsYet.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No flights recorded yet.'**
|
||||
String get noFlightsYet;
|
||||
|
||||
/// No description provided for @droneName.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Drone Name'**
|
||||
String get droneName;
|
||||
|
||||
/// No description provided for @pleaseEnterDroneName.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Please enter a drone name'**
|
||||
String get pleaseEnterDroneName;
|
||||
|
||||
/// No description provided for @imageUuidOptional.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Image UUID (Optional)'**
|
||||
String get imageUuidOptional;
|
||||
|
||||
/// No description provided for @saveDrone.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Save Drone'**
|
||||
String get saveDrone;
|
||||
|
||||
/// No description provided for @droneAddedSuccessfully.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Drone added successfully!'**
|
||||
String get droneAddedSuccessfully;
|
||||
|
||||
/// No description provided for @failedToAddDrone.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to add drone'**
|
||||
String get failedToAddDrone;
|
||||
|
||||
/// No description provided for @batteryName.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Battery Name'**
|
||||
String get batteryName;
|
||||
|
||||
/// No description provided for @batteryType.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Battery Type'**
|
||||
String get batteryType;
|
||||
|
||||
/// No description provided for @batteryVoltage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Voltage (V)'**
|
||||
String get batteryVoltage;
|
||||
|
||||
/// No description provided for @pleaseEnterBatteryName.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Please enter a battery name'**
|
||||
String get pleaseEnterBatteryName;
|
||||
|
||||
/// No description provided for @pleaseEnterBatteryType.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Please enter a battery type'**
|
||||
String get pleaseEnterBatteryType;
|
||||
|
||||
/// No description provided for @pleaseEnterBatteryVoltage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Please enter battery voltage'**
|
||||
String get pleaseEnterBatteryVoltage;
|
||||
|
||||
/// No description provided for @pleaseEnterValidNumber.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Please enter a valid number'**
|
||||
String get pleaseEnterValidNumber;
|
||||
|
||||
/// No description provided for @saveBattery.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Save Battery'**
|
||||
String get saveBattery;
|
||||
|
||||
/// No description provided for @batteryAddedSuccessfully.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Battery added successfully!'**
|
||||
String get batteryAddedSuccessfully;
|
||||
|
||||
/// No description provided for @failedToAddBattery.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to add battery'**
|
||||
String get failedToAddBattery;
|
||||
|
||||
/// No description provided for @droneImage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Drone Image'**
|
||||
String get droneImage;
|
||||
|
||||
/// No description provided for @batteryImage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Battery Image'**
|
||||
String get batteryImage;
|
||||
|
||||
/// No description provided for @takePhoto.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Take Photo'**
|
||||
String get takePhoto;
|
||||
|
||||
/// No description provided for @chooseFromGallery.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose from Gallery'**
|
||||
String get chooseFromGallery;
|
||||
|
||||
/// No description provided for @imageSelected.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Image selected!'**
|
||||
String get imageSelected;
|
||||
|
||||
/// No description provided for @imageSelectionCancelled.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Image selection cancelled.'**
|
||||
String get imageSelectionCancelled;
|
||||
|
||||
/// No description provided for @removeImage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Remove Image'**
|
||||
String get removeImage;
|
||||
|
||||
/// No description provided for @imageDeletedSuccessfully.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Image deleted successfully!'**
|
||||
String get imageDeletedSuccessfully;
|
||||
|
||||
/// No description provided for @droneDetails.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Drone Details'**
|
||||
String get droneDetails;
|
||||
|
||||
/// No description provided for @editDrone.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Edit Drone'**
|
||||
String get editDrone;
|
||||
|
||||
/// No description provided for @saveChanges.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Save Changes'**
|
||||
String get saveChanges;
|
||||
|
||||
/// No description provided for @droneUpdatedSuccessfully.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Drone updated successfully!'**
|
||||
String get droneUpdatedSuccessfully;
|
||||
|
||||
/// No description provided for @failedToUpdateDrone.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to update drone'**
|
||||
String get failedToUpdateDrone;
|
||||
|
||||
/// No description provided for @deleteDroneConfirmationTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete Drone?'**
|
||||
String get deleteDroneConfirmationTitle;
|
||||
|
||||
/// No description provided for @deleteDroneConfirmationMessage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Are you sure you want to delete {droneName}? This action cannot be undone.'**
|
||||
String deleteDroneConfirmationMessage(String droneName);
|
||||
|
||||
/// No description provided for @droneDeletedSuccessfully.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Drone deleted successfully!'**
|
||||
String get droneDeletedSuccessfully;
|
||||
|
||||
/// No description provided for @failedToDeleteDrone.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to delete drone'**
|
||||
String get failedToDeleteDrone;
|
||||
|
||||
/// No description provided for @batteryDetails.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Battery Details'**
|
||||
String get batteryDetails;
|
||||
|
||||
/// No description provided for @editBattery.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Edit Battery'**
|
||||
String get editBattery;
|
||||
|
||||
/// No description provided for @batteryUpdatedSuccessfully.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Battery updated successfully!'**
|
||||
String get batteryUpdatedSuccessfully;
|
||||
|
||||
/// No description provided for @failedToUpdateBattery.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to update battery'**
|
||||
String get failedToUpdateBattery;
|
||||
|
||||
/// No description provided for @deleteBatteryConfirmationTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete Battery?'**
|
||||
String get deleteBatteryConfirmationTitle;
|
||||
|
||||
/// No description provided for @deleteBatteryConfirmationMessage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Are you sure you want to delete {batteryName}? This action cannot be undone.'**
|
||||
String deleteBatteryConfirmationMessage(String batteryName);
|
||||
|
||||
/// No description provided for @batteryDeletedSuccessfully.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Battery deleted successfully!'**
|
||||
String get batteryDeletedSuccessfully;
|
||||
|
||||
/// No description provided for @failedToDeleteBattery.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to delete battery'**
|
||||
String get failedToDeleteBattery;
|
||||
|
||||
/// No description provided for @cancel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Cancel'**
|
||||
String get cancel;
|
||||
|
||||
/// No description provided for @delete.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete'**
|
||||
String get delete;
|
||||
|
||||
/// No description provided for @flightDetails.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Flight Details'**
|
||||
String get flightDetails;
|
||||
|
||||
/// No description provided for @startTime.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Start Time'**
|
||||
String get startTime;
|
||||
|
||||
/// No description provided for @endTime.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'End Time'**
|
||||
String get endTime;
|
||||
|
||||
/// No description provided for @duration.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Duration'**
|
||||
String get duration;
|
||||
|
||||
/// No description provided for @flightLocation.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Flight Location'**
|
||||
String get flightLocation;
|
||||
|
||||
/// No description provided for @noLocationData.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No location data for this flight.'**
|
||||
String get noLocationData;
|
||||
|
||||
/// No description provided for @unknown.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Unknown'**
|
||||
String get unknown;
|
||||
|
||||
/// No description provided for @errorLoadingData.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Error loading data'**
|
||||
String get errorLoadingData;
|
||||
|
||||
/// No description provided for @drone.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Drone'**
|
||||
String get drone;
|
||||
|
||||
/// No description provided for @battery.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Battery'**
|
||||
String get battery;
|
||||
|
||||
/// No description provided for @themeSetting.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Theme'**
|
||||
String get themeSetting;
|
||||
|
||||
/// No description provided for @themeLight.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Light Theme'**
|
||||
String get themeLight;
|
||||
|
||||
/// No description provided for @themeDark.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Dark Theme'**
|
||||
String get themeDark;
|
||||
|
||||
/// No description provided for @themeSystem.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'System Default'**
|
||||
String get themeSystem;
|
||||
|
||||
/// No description provided for @themeChangedTo.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Theme changed to {themeName}'**
|
||||
String themeChangedTo(String themeName);
|
||||
|
||||
/// No description provided for @deleteFlightConfirmationTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete Flight?'**
|
||||
String get deleteFlightConfirmationTitle;
|
||||
|
||||
/// No description provided for @deleteFlightConfirmationMessage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Are you sure you want to delete {flightName}? This action cannot be undone.'**
|
||||
String deleteFlightConfirmationMessage(String flightName);
|
||||
|
||||
/// No description provided for @flightDeletedSuccessfully.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Flight deleted successfully!'**
|
||||
String get flightDeletedSuccessfully;
|
||||
|
||||
/// No description provided for @failedToDeleteFlight.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to delete flight'**
|
||||
String get failedToDeleteFlight;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
const _AppLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
Future<AppLocalizations> load(Locale locale) {
|
||||
return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
|
||||
}
|
||||
|
||||
@override
|
||||
bool isSupported(Locale locale) => <String>['en', 'fr'].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 'fr': return AppLocalizationsFr();
|
||||
}
|
||||
|
||||
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.'
|
||||
);
|
||||
}
|
||||
345
lib/l10n/app_localizations_en.dart
Normal file
345
lib/l10n/app_localizations_en.dart
Normal file
@ -0,0 +1,345 @@
|
||||
// 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 => 'Rtime';
|
||||
|
||||
@override
|
||||
String get yourDrones => 'Your Drones';
|
||||
|
||||
@override
|
||||
String get addDrone => 'Add Drone';
|
||||
|
||||
@override
|
||||
String get yourBatteries => 'Your Batteries';
|
||||
|
||||
@override
|
||||
String get addBattery => 'Add Battery';
|
||||
|
||||
@override
|
||||
String get latestFlights => 'Latest Flights';
|
||||
|
||||
@override
|
||||
String get newFlight => 'New Flight';
|
||||
|
||||
@override
|
||||
String detailsOfDrone(String droneName) {
|
||||
return 'Details of drone: $droneName';
|
||||
}
|
||||
|
||||
@override
|
||||
String detailsOfBattery(String batteryName) {
|
||||
return 'Details of battery: $batteryName';
|
||||
}
|
||||
|
||||
@override
|
||||
String detailsOfFlight(String flightName) {
|
||||
return 'Details of flight: $flightName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsTitle => 'Settings';
|
||||
|
||||
@override
|
||||
String get languageSetting => 'Language';
|
||||
|
||||
@override
|
||||
String get english => 'English';
|
||||
|
||||
@override
|
||||
String get french => 'French';
|
||||
|
||||
@override
|
||||
String languageChangedTo(String languageName) {
|
||||
return 'Language changed to $languageName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chooseDrone => 'Choose your Drone';
|
||||
|
||||
@override
|
||||
String get selectDroneHint => 'Select a drone';
|
||||
|
||||
@override
|
||||
String get chooseBattery => 'Choose your Battery';
|
||||
|
||||
@override
|
||||
String get selectBatteryHint => 'Select a battery';
|
||||
|
||||
@override
|
||||
String get startFlight => 'Start Flight';
|
||||
|
||||
@override
|
||||
String get stopFlight => 'Stop Flight';
|
||||
|
||||
@override
|
||||
String get selectDroneBattery => 'Please select a drone and a battery.';
|
||||
|
||||
@override
|
||||
String get flightStarted => 'Flight started!';
|
||||
|
||||
@override
|
||||
String flightStopped(String flightDuration) {
|
||||
return 'Flight stopped. Duration: $flightDuration';
|
||||
}
|
||||
|
||||
@override
|
||||
String get recordFlightLocation => 'Record flight location (GPS)';
|
||||
|
||||
@override
|
||||
String get locationServicesDisabled => 'Location services are disabled.';
|
||||
|
||||
@override
|
||||
String get locationPermissionsDenied => 'Location permissions are denied.';
|
||||
|
||||
@override
|
||||
String get locationPermissionsDeniedForever => 'Location permissions are permanently denied, we cannot request permissions.';
|
||||
|
||||
@override
|
||||
String locationObtained(double latitude, double longitude) {
|
||||
return 'Location obtained: Lat $latitude, Lng $longitude';
|
||||
}
|
||||
|
||||
@override
|
||||
String get locationRequiredForFlight => 'GPS location is required but could not be obtained. Please check permissions and try again.';
|
||||
|
||||
@override
|
||||
String get obtainingLocation => 'Obtaining location...';
|
||||
|
||||
@override
|
||||
String failedToGetLocation(String error) {
|
||||
return 'Failed to get location: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String currentCoordinates(double latitude, double longitude) {
|
||||
return 'Current coordinates: Lat $latitude, Lng $longitude';
|
||||
}
|
||||
|
||||
@override
|
||||
String get errorLoadingDrones => 'Error loading drones';
|
||||
|
||||
@override
|
||||
String get errorLoadingBatteries => 'Error loading batteries';
|
||||
|
||||
@override
|
||||
String get noDronesYet => 'No drones added yet. Tap the \'+\' card to add one!';
|
||||
|
||||
@override
|
||||
String get noBatteriesYet => 'No batteries added yet. Tap the \'+\' card to add one!';
|
||||
|
||||
@override
|
||||
String get flightSavedSuccessfully => 'Flight saved successfully!';
|
||||
|
||||
@override
|
||||
String failedToSaveFlight(String error) {
|
||||
return 'Failed to save flight: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get errorLoadingFlights => 'Error loading flights';
|
||||
|
||||
@override
|
||||
String get noFlightsYet => 'No flights recorded yet.';
|
||||
|
||||
@override
|
||||
String get droneName => 'Drone Name';
|
||||
|
||||
@override
|
||||
String get pleaseEnterDroneName => 'Please enter a drone name';
|
||||
|
||||
@override
|
||||
String get imageUuidOptional => 'Image UUID (Optional)';
|
||||
|
||||
@override
|
||||
String get saveDrone => 'Save Drone';
|
||||
|
||||
@override
|
||||
String get droneAddedSuccessfully => 'Drone added successfully!';
|
||||
|
||||
@override
|
||||
String get failedToAddDrone => 'Failed to add drone';
|
||||
|
||||
@override
|
||||
String get batteryName => 'Battery Name';
|
||||
|
||||
@override
|
||||
String get batteryType => 'Battery Type';
|
||||
|
||||
@override
|
||||
String get batteryVoltage => 'Voltage (V)';
|
||||
|
||||
@override
|
||||
String get pleaseEnterBatteryName => 'Please enter a battery name';
|
||||
|
||||
@override
|
||||
String get pleaseEnterBatteryType => 'Please enter a battery type';
|
||||
|
||||
@override
|
||||
String get pleaseEnterBatteryVoltage => 'Please enter battery voltage';
|
||||
|
||||
@override
|
||||
String get pleaseEnterValidNumber => 'Please enter a valid number';
|
||||
|
||||
@override
|
||||
String get saveBattery => 'Save Battery';
|
||||
|
||||
@override
|
||||
String get batteryAddedSuccessfully => 'Battery added successfully!';
|
||||
|
||||
@override
|
||||
String get failedToAddBattery => 'Failed to add battery';
|
||||
|
||||
@override
|
||||
String get droneImage => 'Drone Image';
|
||||
|
||||
@override
|
||||
String get batteryImage => 'Battery Image';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Take Photo';
|
||||
|
||||
@override
|
||||
String get chooseFromGallery => 'Choose from Gallery';
|
||||
|
||||
@override
|
||||
String get imageSelected => 'Image selected!';
|
||||
|
||||
@override
|
||||
String get imageSelectionCancelled => 'Image selection cancelled.';
|
||||
|
||||
@override
|
||||
String get removeImage => 'Remove Image';
|
||||
|
||||
@override
|
||||
String get imageDeletedSuccessfully => 'Image deleted successfully!';
|
||||
|
||||
@override
|
||||
String get droneDetails => 'Drone Details';
|
||||
|
||||
@override
|
||||
String get editDrone => 'Edit Drone';
|
||||
|
||||
@override
|
||||
String get saveChanges => 'Save Changes';
|
||||
|
||||
@override
|
||||
String get droneUpdatedSuccessfully => 'Drone updated successfully!';
|
||||
|
||||
@override
|
||||
String get failedToUpdateDrone => 'Failed to update drone';
|
||||
|
||||
@override
|
||||
String get deleteDroneConfirmationTitle => 'Delete Drone?';
|
||||
|
||||
@override
|
||||
String deleteDroneConfirmationMessage(String droneName) {
|
||||
return 'Are you sure you want to delete $droneName? This action cannot be undone.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get droneDeletedSuccessfully => 'Drone deleted successfully!';
|
||||
|
||||
@override
|
||||
String get failedToDeleteDrone => 'Failed to delete drone';
|
||||
|
||||
@override
|
||||
String get batteryDetails => 'Battery Details';
|
||||
|
||||
@override
|
||||
String get editBattery => 'Edit Battery';
|
||||
|
||||
@override
|
||||
String get batteryUpdatedSuccessfully => 'Battery updated successfully!';
|
||||
|
||||
@override
|
||||
String get failedToUpdateBattery => 'Failed to update battery';
|
||||
|
||||
@override
|
||||
String get deleteBatteryConfirmationTitle => 'Delete Battery?';
|
||||
|
||||
@override
|
||||
String deleteBatteryConfirmationMessage(String batteryName) {
|
||||
return 'Are you sure you want to delete $batteryName? This action cannot be undone.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get batteryDeletedSuccessfully => 'Battery deleted successfully!';
|
||||
|
||||
@override
|
||||
String get failedToDeleteBattery => 'Failed to delete battery';
|
||||
|
||||
@override
|
||||
String get cancel => 'Cancel';
|
||||
|
||||
@override
|
||||
String get delete => 'Delete';
|
||||
|
||||
@override
|
||||
String get flightDetails => 'Flight Details';
|
||||
|
||||
@override
|
||||
String get startTime => 'Start Time';
|
||||
|
||||
@override
|
||||
String get endTime => 'End Time';
|
||||
|
||||
@override
|
||||
String get duration => 'Duration';
|
||||
|
||||
@override
|
||||
String get flightLocation => 'Flight Location';
|
||||
|
||||
@override
|
||||
String get noLocationData => 'No location data for this flight.';
|
||||
|
||||
@override
|
||||
String get unknown => 'Unknown';
|
||||
|
||||
@override
|
||||
String get errorLoadingData => 'Error loading data';
|
||||
|
||||
@override
|
||||
String get drone => 'Drone';
|
||||
|
||||
@override
|
||||
String get battery => 'Battery';
|
||||
|
||||
@override
|
||||
String get themeSetting => 'Theme';
|
||||
|
||||
@override
|
||||
String get themeLight => 'Light Theme';
|
||||
|
||||
@override
|
||||
String get themeDark => 'Dark Theme';
|
||||
|
||||
@override
|
||||
String get themeSystem => 'System Default';
|
||||
|
||||
@override
|
||||
String themeChangedTo(String themeName) {
|
||||
return 'Theme changed to $themeName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get deleteFlightConfirmationTitle => 'Delete Flight?';
|
||||
|
||||
@override
|
||||
String deleteFlightConfirmationMessage(String flightName) {
|
||||
return 'Are you sure you want to delete $flightName? This action cannot be undone.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get flightDeletedSuccessfully => 'Flight deleted successfully!';
|
||||
|
||||
@override
|
||||
String get failedToDeleteFlight => 'Failed to delete flight';
|
||||
}
|
||||
345
lib/l10n/app_localizations_fr.dart
Normal file
345
lib/l10n/app_localizations_fr.dart
Normal file
@ -0,0 +1,345 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for French (`fr`).
|
||||
class AppLocalizationsFr extends AppLocalizations {
|
||||
AppLocalizationsFr([String locale = 'fr']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'Rtime';
|
||||
|
||||
@override
|
||||
String get yourDrones => 'Vos Drones';
|
||||
|
||||
@override
|
||||
String get addDrone => 'Ajouter un drone';
|
||||
|
||||
@override
|
||||
String get yourBatteries => 'Vos Batteries';
|
||||
|
||||
@override
|
||||
String get addBattery => 'Ajouter une batterie';
|
||||
|
||||
@override
|
||||
String get latestFlights => 'Derniers Vols';
|
||||
|
||||
@override
|
||||
String get newFlight => 'Nouveau Vol';
|
||||
|
||||
@override
|
||||
String detailsOfDrone(String droneName) {
|
||||
return 'Détails du drone : $droneName';
|
||||
}
|
||||
|
||||
@override
|
||||
String detailsOfBattery(String batteryName) {
|
||||
return 'Détails de la batterie : $batteryName';
|
||||
}
|
||||
|
||||
@override
|
||||
String detailsOfFlight(String flightName) {
|
||||
return 'Détails du vol : $flightName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsTitle => 'Paramètres';
|
||||
|
||||
@override
|
||||
String get languageSetting => 'Langue';
|
||||
|
||||
@override
|
||||
String get english => 'Anglais';
|
||||
|
||||
@override
|
||||
String get french => 'Français';
|
||||
|
||||
@override
|
||||
String languageChangedTo(String languageName) {
|
||||
return 'Langue changée en $languageName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chooseDrone => 'Choisissez votre Drone';
|
||||
|
||||
@override
|
||||
String get selectDroneHint => 'Sélectionnez un drone';
|
||||
|
||||
@override
|
||||
String get chooseBattery => 'Choisissez votre Batterie';
|
||||
|
||||
@override
|
||||
String get selectBatteryHint => 'Sélectionnez une batterie';
|
||||
|
||||
@override
|
||||
String get startFlight => 'Lancer le vol';
|
||||
|
||||
@override
|
||||
String get stopFlight => 'Arrêter le vol';
|
||||
|
||||
@override
|
||||
String get selectDroneBattery => 'Veuillez sélectionner un drone et une batterie.';
|
||||
|
||||
@override
|
||||
String get flightStarted => 'Vol démarré !';
|
||||
|
||||
@override
|
||||
String flightStopped(String flightDuration) {
|
||||
return 'Vol arrêté. Durée : $flightDuration';
|
||||
}
|
||||
|
||||
@override
|
||||
String get recordFlightLocation => 'Enregistrer la localisation du vol (GPS)';
|
||||
|
||||
@override
|
||||
String get locationServicesDisabled => 'Les services de localisation sont désactivés.';
|
||||
|
||||
@override
|
||||
String get locationPermissionsDenied => 'Les permissions de localisation sont refusées.';
|
||||
|
||||
@override
|
||||
String get locationPermissionsDeniedForever => 'Les permissions de localisation sont refusées de manière permanente, nous ne pouvons pas les demander.';
|
||||
|
||||
@override
|
||||
String locationObtained(double latitude, double longitude) {
|
||||
return 'Localisation obtenue : Lat $latitude, Lon $longitude';
|
||||
}
|
||||
|
||||
@override
|
||||
String get locationRequiredForFlight => 'La localisation GPS est requise mais n\'a pu être obtenue. Veuillez vérifier les permissions et réessayer.';
|
||||
|
||||
@override
|
||||
String get obtainingLocation => 'Obtention de la localisation...';
|
||||
|
||||
@override
|
||||
String failedToGetLocation(String error) {
|
||||
return 'Échec de l\'obtention de la localisation : $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String currentCoordinates(double latitude, double longitude) {
|
||||
return 'Coordonnées actuelles : Lat $latitude, Lon $longitude';
|
||||
}
|
||||
|
||||
@override
|
||||
String get errorLoadingDrones => 'Erreur lors du chargement des drones';
|
||||
|
||||
@override
|
||||
String get errorLoadingBatteries => 'Erreur lors du chargement des batteries';
|
||||
|
||||
@override
|
||||
String get noDronesYet => 'Aucun drone ajouté pour le moment. Appuyez sur la carte \'+\' pour en ajouter un !';
|
||||
|
||||
@override
|
||||
String get noBatteriesYet => 'Aucune batterie ajoutée pour le moment. Appuyez sur la carte \'+\' pour en ajouter une !';
|
||||
|
||||
@override
|
||||
String get flightSavedSuccessfully => 'Vol enregistré avec succès !';
|
||||
|
||||
@override
|
||||
String failedToSaveFlight(String error) {
|
||||
return 'Échec de l\'enregistrement du vol : $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get errorLoadingFlights => 'Erreur lors du chargement des vols';
|
||||
|
||||
@override
|
||||
String get noFlightsYet => 'Aucun vol enregistré pour le moment.';
|
||||
|
||||
@override
|
||||
String get droneName => 'Nom du drone';
|
||||
|
||||
@override
|
||||
String get pleaseEnterDroneName => 'Veuillez entrer un nom de drone';
|
||||
|
||||
@override
|
||||
String get imageUuidOptional => 'UUID de l\'image (Facultatif)';
|
||||
|
||||
@override
|
||||
String get saveDrone => 'Enregistrer le drone';
|
||||
|
||||
@override
|
||||
String get droneAddedSuccessfully => 'Drone ajouté avec succès !';
|
||||
|
||||
@override
|
||||
String get failedToAddDrone => 'Échec de l\'ajout du drone';
|
||||
|
||||
@override
|
||||
String get batteryName => 'Nom de la batterie';
|
||||
|
||||
@override
|
||||
String get batteryType => 'Type de batterie';
|
||||
|
||||
@override
|
||||
String get batteryVoltage => 'Tension (V)';
|
||||
|
||||
@override
|
||||
String get pleaseEnterBatteryName => 'Veuillez entrer un nom de batterie';
|
||||
|
||||
@override
|
||||
String get pleaseEnterBatteryType => 'Veuillez entrer un type de batterie';
|
||||
|
||||
@override
|
||||
String get pleaseEnterBatteryVoltage => 'Veuillez entrer la tension de la batterie';
|
||||
|
||||
@override
|
||||
String get pleaseEnterValidNumber => 'Veuillez entrer un nombre valide';
|
||||
|
||||
@override
|
||||
String get saveBattery => 'Enregistrer la batterie';
|
||||
|
||||
@override
|
||||
String get batteryAddedSuccessfully => 'Batterie ajoutée avec succès !';
|
||||
|
||||
@override
|
||||
String get failedToAddBattery => 'Échec de l\'ajout de la batterie';
|
||||
|
||||
@override
|
||||
String get droneImage => 'Image du drone';
|
||||
|
||||
@override
|
||||
String get batteryImage => 'Image de la batterie';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Prendre une photo';
|
||||
|
||||
@override
|
||||
String get chooseFromGallery => 'Choisir de la galerie';
|
||||
|
||||
@override
|
||||
String get imageSelected => 'Image sélectionnée !';
|
||||
|
||||
@override
|
||||
String get imageSelectionCancelled => 'Sélection d\'image annulée.';
|
||||
|
||||
@override
|
||||
String get removeImage => 'Supprimer l\'image';
|
||||
|
||||
@override
|
||||
String get imageDeletedSuccessfully => 'Image supprimée avec succès !';
|
||||
|
||||
@override
|
||||
String get droneDetails => 'Détails du drone';
|
||||
|
||||
@override
|
||||
String get editDrone => 'Modifier le drone';
|
||||
|
||||
@override
|
||||
String get saveChanges => 'Enregistrer les modifications';
|
||||
|
||||
@override
|
||||
String get droneUpdatedSuccessfully => 'Drone mis à jour avec succès !';
|
||||
|
||||
@override
|
||||
String get failedToUpdateDrone => 'Échec de la mise à jour du drone';
|
||||
|
||||
@override
|
||||
String get deleteDroneConfirmationTitle => 'Supprimer le drone ?';
|
||||
|
||||
@override
|
||||
String deleteDroneConfirmationMessage(String droneName) {
|
||||
return 'Êtes-vous sûr de vouloir supprimer $droneName ? Cette action est irréversible.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get droneDeletedSuccessfully => 'Drone supprimé avec succès !';
|
||||
|
||||
@override
|
||||
String get failedToDeleteDrone => 'Échec de la suppression du drone';
|
||||
|
||||
@override
|
||||
String get batteryDetails => 'Détails de la batterie';
|
||||
|
||||
@override
|
||||
String get editBattery => 'Modifier la batterie';
|
||||
|
||||
@override
|
||||
String get batteryUpdatedSuccessfully => 'Batterie mise à jour avec succès !';
|
||||
|
||||
@override
|
||||
String get failedToUpdateBattery => 'Échec de la mise à jour de la batterie';
|
||||
|
||||
@override
|
||||
String get deleteBatteryConfirmationTitle => 'Supprimer la batterie ?';
|
||||
|
||||
@override
|
||||
String deleteBatteryConfirmationMessage(String batteryName) {
|
||||
return 'Êtes-vous sûr de vouloir supprimer $batteryName ? Cette action est irréversible.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get batteryDeletedSuccessfully => 'Batterie supprimée avec succès !';
|
||||
|
||||
@override
|
||||
String get failedToDeleteBattery => 'Échec de la suppression de la batterie';
|
||||
|
||||
@override
|
||||
String get cancel => 'Annuler';
|
||||
|
||||
@override
|
||||
String get delete => 'Supprimer';
|
||||
|
||||
@override
|
||||
String get flightDetails => 'Détails du vol';
|
||||
|
||||
@override
|
||||
String get startTime => 'Heure de début';
|
||||
|
||||
@override
|
||||
String get endTime => 'Heure de fin';
|
||||
|
||||
@override
|
||||
String get duration => 'Durée';
|
||||
|
||||
@override
|
||||
String get flightLocation => 'Localisation du vol';
|
||||
|
||||
@override
|
||||
String get noLocationData => 'Pas de données de localisation pour ce vol.';
|
||||
|
||||
@override
|
||||
String get unknown => 'Inconnu';
|
||||
|
||||
@override
|
||||
String get errorLoadingData => 'Erreur de chargement des données';
|
||||
|
||||
@override
|
||||
String get drone => 'Drone';
|
||||
|
||||
@override
|
||||
String get battery => 'Batterie';
|
||||
|
||||
@override
|
||||
String get themeSetting => 'Thème';
|
||||
|
||||
@override
|
||||
String get themeLight => 'Thème Clair';
|
||||
|
||||
@override
|
||||
String get themeDark => 'Thème Sombre';
|
||||
|
||||
@override
|
||||
String get themeSystem => 'Par défaut du système';
|
||||
|
||||
@override
|
||||
String themeChangedTo(String themeName) {
|
||||
return 'Thème changé en $themeName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get deleteFlightConfirmationTitle => 'Supprimer le vol ?';
|
||||
|
||||
@override
|
||||
String deleteFlightConfirmationMessage(String flightName) {
|
||||
return 'Êtes-vous sûr de vouloir supprimer $flightName ? Cette action est irréversible.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get flightDeletedSuccessfully => 'Vol supprimé avec succès !';
|
||||
|
||||
@override
|
||||
String get failedToDeleteFlight => 'Échec de la suppression du vol';
|
||||
}
|
||||
245
lib/main.dart
245
lib/main.dart
@ -1,38 +1,229 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:rtime/db/db_helper.dart';
|
||||
import 'package:rtime/images_manager.dart';
|
||||
import 'package:rtime/models/drone.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:rtime/l10n/app_localizations.dart';
|
||||
import 'package:rtime/pages/home_page.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
|
||||
import 'package:rtime/providers/local_provider.dart';
|
||||
import 'package:rtime/providers/theme_provider.dart';
|
||||
|
||||
void main() {
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
sqfliteFfiInit();
|
||||
databaseFactory = databaseFactoryFfi;
|
||||
}
|
||||
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen(
|
||||
(record) =>
|
||||
print('${record.level.name}: ${record.time}: ${record.message}'),
|
||||
runApp(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => LocaleProvider(),
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => ThemeProvider(),
|
||||
),
|
||||
],
|
||||
child: const MyApp(),
|
||||
),
|
||||
);
|
||||
runApp(const RTimeApp());
|
||||
|
||||
//DbHelper.instance.closeDb();
|
||||
}
|
||||
|
||||
class RTimeApp extends StatelessWidget {
|
||||
const RTimeApp({super.key});
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
State<MyApp> createState() => _MyAppState();
|
||||
}
|
||||
|
||||
class _MyAppState extends State<MyApp> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text("RtimeAPP");
|
||||
}
|
||||
return Consumer2<LocaleProvider, ThemeProvider>(
|
||||
builder: (context, localeProvider, themeProvider, child) {
|
||||
return MaterialApp(
|
||||
title: 'Rtime',
|
||||
locale: localeProvider.locale,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [
|
||||
Locale('en', ''),
|
||||
Locale('fr', ''),
|
||||
],
|
||||
|
||||
theme: ThemeData(
|
||||
primaryColor: Colors.white,
|
||||
colorScheme: ColorScheme.light(
|
||||
primary: Colors.blue.shade600,
|
||||
secondary: Colors.teal.shade400,
|
||||
surface: Colors.white,
|
||||
background: Colors.white,
|
||||
error: Colors.red.shade700,
|
||||
onPrimary: Colors.white,
|
||||
onSecondary: Colors.white,
|
||||
onSurface: Colors.black87,
|
||||
onBackground: Colors.black87,
|
||||
),
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black87,
|
||||
elevation: 0.5,
|
||||
iconTheme: const IconThemeData(color: Colors.black87),
|
||||
titleTextStyle: const TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
floatingActionButtonTheme: FloatingActionButtonThemeData(
|
||||
backgroundColor: Colors.blue.shade600,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
),
|
||||
extendedSizeConstraints: const BoxConstraints.tightFor(
|
||||
height: 70.0,
|
||||
width: 250.0,
|
||||
),
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
elevation: 2,
|
||||
color: Colors.white,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
),
|
||||
listTileTheme: ListTileThemeData(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
tileColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
selectedTileColor: Colors.blue.shade50,
|
||||
),
|
||||
textTheme: const TextTheme(
|
||||
headlineSmall: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87),
|
||||
titleMedium: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: Colors.black87),
|
||||
titleSmall: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.black54),
|
||||
bodyLarge: TextStyle(color: Colors.black87),
|
||||
bodyMedium: TextStyle(color: Colors.black54),
|
||||
bodySmall: TextStyle(fontSize: 12, color: Colors.black45),
|
||||
),
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
buttonTheme: ButtonThemeData(
|
||||
buttonColor: Colors.blue.shade600,
|
||||
textTheme: ButtonTextTheme.primary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(color: Colors.grey.shade400),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(color: Colors.blue.shade600, width: 2),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
labelStyle: const TextStyle(color: Colors.black54),
|
||||
hintStyle: TextStyle(color: Colors.grey.shade500),
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
darkTheme: ThemeData.dark().copyWith(
|
||||
primaryColor: Colors.blueGrey[900],
|
||||
colorScheme: ColorScheme.dark(
|
||||
primary: Colors.lightBlueAccent,
|
||||
secondary: Colors.tealAccent,
|
||||
surface: Colors.blueGrey.shade800,
|
||||
background: Colors.blueGrey.shade900,
|
||||
onPrimary: Colors.black,
|
||||
onSecondary: Colors.black,
|
||||
onSurface: Colors.white,
|
||||
onBackground: Colors.white,
|
||||
),
|
||||
|
||||
|
||||
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: Colors.blueGrey[900],
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 4,
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
titleTextStyle: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
floatingActionButtonTheme: FloatingActionButtonThemeData(
|
||||
backgroundColor: Colors.lightBlueAccent,
|
||||
foregroundColor: Colors.black,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
),
|
||||
extendedSizeConstraints: const BoxConstraints.tightFor(
|
||||
height: 70.0,
|
||||
width: 250.0,
|
||||
),
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
elevation: 8,
|
||||
color: Colors.blueGrey[800],
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
),
|
||||
listTileTheme: ListTileThemeData(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
tileColor: Colors.blueGrey[800],
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
selectedTileColor: Colors.blueGrey[700],
|
||||
),
|
||||
textTheme: const TextTheme(
|
||||
headlineSmall: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.white),
|
||||
titleMedium: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
titleSmall: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
bodyLarge: TextStyle(color: Colors.white70),
|
||||
bodyMedium: TextStyle(color: Colors.white60),
|
||||
bodySmall: TextStyle(fontSize: 12, color: Colors.white70),
|
||||
),
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
buttonTheme: ButtonThemeData(
|
||||
buttonColor: Colors.lightBlueAccent,
|
||||
textTheme: ButtonTextTheme.primary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(color: Colors.blueGrey.shade600),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(color: Colors.lightBlueAccent, width: 2),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(color: Colors.blueGrey.shade700),
|
||||
),
|
||||
labelStyle: const TextStyle(color: Colors.white70),
|
||||
hintStyle: TextStyle(color: Colors.blueGrey.shade500),
|
||||
),
|
||||
),
|
||||
themeMode: themeProvider.themeMode,
|
||||
home: const HomePage(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ class Flight {
|
||||
startTimestamp: map["start_timestamp"],
|
||||
endTimestamp: map["end_timestamp"],
|
||||
droneId: map["drone_id"],
|
||||
batteryId: map["batteryId"],
|
||||
batteryId: map["battery_id"],
|
||||
locationLat: map["location_lat"],
|
||||
locationLong: map["location_long"],
|
||||
);
|
||||
|
||||
425
lib/pages/battery_detail_page.dart
Normal file
425
lib/pages/battery_detail_page.dart
Normal file
@ -0,0 +1,425 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rtime/l10n/app_localizations.dart';
|
||||
import 'package:rtime/models/battery.dart';
|
||||
import 'package:rtime/db/db_helper.dart';
|
||||
import 'package:rtime/images_manager.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:rtime/models/flight.dart';
|
||||
import 'package:rtime/pages/flight_detail_page.dart';
|
||||
|
||||
class BatteryDetailPage extends StatefulWidget {
|
||||
final Battery battery;
|
||||
|
||||
const BatteryDetailPage({super.key, required this.battery});
|
||||
|
||||
@override
|
||||
State<BatteryDetailPage> createState() => _BatteryDetailPageState();
|
||||
}
|
||||
|
||||
class _BatteryDetailPageState extends State<BatteryDetailPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
late TextEditingController _nameController;
|
||||
late TextEditingController _typeController;
|
||||
late TextEditingController _voltageController;
|
||||
String? _currentImageUuid;
|
||||
Image? _displayImage;
|
||||
bool _isEditing = false;
|
||||
List<Flight> _associatedFlights = [];
|
||||
bool _isLoadingFlights = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_nameController = TextEditingController(text: widget.battery.name);
|
||||
_typeController = TextEditingController(text: widget.battery.type);
|
||||
_voltageController = TextEditingController(text: widget.battery.voltage.toString());
|
||||
_currentImageUuid = widget.battery.imageUuid;
|
||||
_loadImage();
|
||||
_loadAssociatedFlights();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_typeController.dispose();
|
||||
_voltageController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadImage() async {
|
||||
if (_currentImageUuid != null && _currentImageUuid!.isNotEmpty) {
|
||||
final loadedImage = await ImagesManager.instance.loadImage(_currentImageUuid!);
|
||||
setState(() {
|
||||
_displayImage = loadedImage;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_displayImage = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> _loadAssociatedFlights() async {
|
||||
setState(() {
|
||||
_isLoadingFlights = true;
|
||||
});
|
||||
try {
|
||||
final allFlights = await DbHelper.instance.getBatteryFlights(widget.battery.id!);
|
||||
|
||||
_associatedFlights = allFlights
|
||||
.where((flight) => flight.batteryId == widget.battery.id)
|
||||
.toList();
|
||||
|
||||
_associatedFlights.sort((a, b) => b.startTimestamp.compareTo(a.startTimestamp));
|
||||
|
||||
setState(() {
|
||||
_isLoadingFlights = false;
|
||||
});
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
|
||||
|
||||
|
||||
|
||||
setState(() {
|
||||
_isLoadingFlights = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _pickImage(ImageSource source) async {
|
||||
final uuid = await ImagesManager.instance.createImage(source);
|
||||
|
||||
if (uuid != null) {
|
||||
|
||||
if (_currentImageUuid != null && _currentImageUuid != uuid) {
|
||||
await ImagesManager.instance.deleteImage(_currentImageUuid!);
|
||||
}
|
||||
setState(() {
|
||||
_currentImageUuid = uuid;
|
||||
});
|
||||
await _loadImage();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _deleteImage() async {
|
||||
if (_currentImageUuid != null) {
|
||||
await ImagesManager.instance.deleteImage(_currentImageUuid!);
|
||||
setState(() {
|
||||
_currentImageUuid = null;
|
||||
_displayImage = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveBattery() async {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final updatedBattery = Battery(
|
||||
id: widget.battery.id,
|
||||
name: _nameController.text.trim(),
|
||||
type: _typeController.text.trim(),
|
||||
voltage: double.parse(_voltageController.text.trim()),
|
||||
imageUuid: _currentImageUuid,
|
||||
);
|
||||
|
||||
try {
|
||||
await DbHelper.instance.updateBattery(updatedBattery);
|
||||
if (mounted) {
|
||||
|
||||
|
||||
|
||||
Navigator.of(context).pop(true);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _deleteBattery() async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final bool? confirm = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(l10n.deleteBatteryConfirmationTitle),
|
||||
content: Text(l10n.deleteBatteryConfirmationMessage(widget.battery.name)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: Text(l10n.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirm == true) {
|
||||
try {
|
||||
|
||||
if (_currentImageUuid != null && _currentImageUuid!.isNotEmpty) {
|
||||
await ImagesManager.instance.deleteImage(_currentImageUuid!);
|
||||
}
|
||||
await DbHelper.instance.deleteBattery(widget.battery.id!);
|
||||
if (mounted) {
|
||||
|
||||
|
||||
|
||||
Navigator.of(context).pop(true);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
appBar: AppBar(
|
||||
title: Text(_isEditing ? l10n.editBattery : l10n.batteryDetails),
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(_isEditing ? Icons.save : Icons.edit),
|
||||
onPressed: () {
|
||||
if (_isEditing) {
|
||||
_saveBattery();
|
||||
}
|
||||
setState(() {
|
||||
_isEditing = !_isEditing;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (!_isEditing)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete_forever, color: Colors.redAccent),
|
||||
onPressed: _deleteBattery,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
Center(
|
||||
child: Container(
|
||||
width: 180,
|
||||
height: 180,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blueGrey[800],
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(color: Colors.tealAccent, width: 2),
|
||||
image: _displayImage != null
|
||||
? DecorationImage(
|
||||
image: _displayImage!.image,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: _displayImage == null
|
||||
? Icon(
|
||||
Icons.camera_alt,
|
||||
size: 80,
|
||||
color: Colors.blueGrey[400],
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
if (_isEditing) ...[
|
||||
const SizedBox(height: 15),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _pickImage(ImageSource.camera),
|
||||
icon: const Icon(Icons.camera_alt),
|
||||
label: Text(l10n.takePhoto),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blueGrey[700],
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _pickImage(ImageSource.gallery),
|
||||
icon: const Icon(Icons.photo_library),
|
||||
label: Text(l10n.chooseFromGallery),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blueGrey[700],
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_currentImageUuid != null)
|
||||
TextButton.icon(
|
||||
onPressed: _deleteImage,
|
||||
icon: const Icon(Icons.clear, color: Colors.redAccent),
|
||||
label: Text(l10n.removeImage, style: const TextStyle(color: Colors.redAccent)),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 30),
|
||||
TextFormField(
|
||||
controller: _nameController,
|
||||
readOnly: !_isEditing,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.batteryName,
|
||||
labelStyle: const TextStyle(color: Colors.white70),
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.white30),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.tealAccent),
|
||||
),
|
||||
),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return l10n.pleaseEnterBatteryName;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
TextFormField(
|
||||
controller: _typeController,
|
||||
readOnly: !_isEditing,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.batteryType,
|
||||
labelStyle: const TextStyle(color: Colors.white70),
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.white30),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.tealAccent),
|
||||
),
|
||||
),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return l10n.pleaseEnterBatteryType;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
TextFormField(
|
||||
controller: _voltageController,
|
||||
keyboardType: TextInputType.number,
|
||||
readOnly: !_isEditing,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.batteryVoltage,
|
||||
labelStyle: const TextStyle(color: Colors.white70),
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.white30),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.tealAccent),
|
||||
),
|
||||
),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return l10n.pleaseEnterBatteryVoltage;
|
||||
}
|
||||
if (double.tryParse(value) == null) {
|
||||
return l10n.pleaseEnterValidNumber;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
if (_isEditing)
|
||||
Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: _saveBattery,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
foregroundColor: Colors.black,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 15),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
l10n.saveChanges,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
Text(
|
||||
l10n.latestFlights,
|
||||
style: Theme.of(context).textTheme.headlineSmall!.copyWith(color: Colors.white),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
_isLoadingFlights
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _associatedFlights.isEmpty
|
||||
? Center(
|
||||
child: Text(
|
||||
l10n.noFlightsYet,
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(color: Colors.white70),
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: _associatedFlights.length,
|
||||
itemBuilder: (context, index) {
|
||||
final flight = _associatedFlights[index];
|
||||
final DateTime flightDate =
|
||||
DateTime.fromMillisecondsSinceEpoch(flight.startTimestamp * 1000);
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20, vertical: 12),
|
||||
leading: const Icon(Icons.flight_takeoff,
|
||||
color: Colors.orangeAccent, size: 32),
|
||||
title: Text(
|
||||
'${flight.name} - ${flightDate.toLocal().toString().split(' ')[0]}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
trailing: const Icon(Icons.arrow_forward_ios,
|
||||
size: 20, color: Colors.white54),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => FlightDetailPage(flight: flight),
|
||||
),
|
||||
).then((_) => _loadAssociatedFlights());
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
367
lib/pages/drone_detail_page.dart
Normal file
367
lib/pages/drone_detail_page.dart
Normal file
@ -0,0 +1,367 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rtime/l10n/app_localizations.dart';
|
||||
import 'package:rtime/models/drone.dart';
|
||||
import 'package:rtime/db/db_helper.dart';
|
||||
import 'package:rtime/images_manager.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:rtime/models/flight.dart';
|
||||
import 'package:rtime/pages/flight_detail_page.dart';
|
||||
|
||||
class DroneDetailPage extends StatefulWidget {
|
||||
final Drone drone;
|
||||
|
||||
const DroneDetailPage({super.key, required this.drone});
|
||||
|
||||
@override
|
||||
State<DroneDetailPage> createState() => _DroneDetailPageState();
|
||||
}
|
||||
|
||||
class _DroneDetailPageState extends State<DroneDetailPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
late TextEditingController _nameController;
|
||||
String? _currentImageUuid;
|
||||
Image? _displayImage;
|
||||
bool _isEditing = false;
|
||||
List<Flight> _associatedFlights = [];
|
||||
bool _isLoadingFlights = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_nameController = TextEditingController(text: widget.drone.name);
|
||||
_currentImageUuid = widget.drone.imageUuid;
|
||||
_loadImage();
|
||||
_loadAssociatedFlights();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadImage() async {
|
||||
if (_currentImageUuid != null && _currentImageUuid!.isNotEmpty) {
|
||||
final loadedImage = await ImagesManager.instance.loadImage(_currentImageUuid!);
|
||||
setState(() {
|
||||
_displayImage = loadedImage;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_displayImage = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> _loadAssociatedFlights() async {
|
||||
setState(() {
|
||||
_isLoadingFlights = true;
|
||||
});
|
||||
try {
|
||||
final allFlights = await DbHelper.instance.getDroneFlights(widget.drone.id!);
|
||||
|
||||
_associatedFlights = allFlights
|
||||
.where((flight) => flight.droneId == widget.drone.id)
|
||||
.toList();
|
||||
|
||||
_associatedFlights.sort((a, b) => b.startTimestamp.compareTo(a.startTimestamp));
|
||||
|
||||
setState(() {
|
||||
_isLoadingFlights = false;
|
||||
});
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
|
||||
|
||||
|
||||
|
||||
setState(() {
|
||||
_isLoadingFlights = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _pickImage(ImageSource source) async {
|
||||
final uuid = await ImagesManager.instance.createImage(source);
|
||||
|
||||
if (uuid != null) {
|
||||
if (_currentImageUuid != null && _currentImageUuid != uuid) {
|
||||
await ImagesManager.instance.deleteImage(_currentImageUuid!);
|
||||
}
|
||||
setState(() {
|
||||
_currentImageUuid = uuid;
|
||||
});
|
||||
await _loadImage();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _deleteImage() async {
|
||||
if (_currentImageUuid != null) {
|
||||
await ImagesManager.instance.deleteImage(_currentImageUuid!);
|
||||
setState(() {
|
||||
_currentImageUuid = null;
|
||||
_displayImage = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveDrone() async {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final updatedDrone = Drone(
|
||||
id: widget.drone.id,
|
||||
name: _nameController.text.trim(),
|
||||
imageUuid: _currentImageUuid,
|
||||
);
|
||||
|
||||
try {
|
||||
await DbHelper.instance.updateDrone(updatedDrone);
|
||||
if (mounted) {
|
||||
|
||||
|
||||
|
||||
Navigator.of(context).pop(true);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _deleteDrone() async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final bool? confirm = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(l10n.deleteDroneConfirmationTitle),
|
||||
content: Text(l10n.deleteDroneConfirmationMessage(widget.drone.name)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: Text(l10n.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirm == true) {
|
||||
try {
|
||||
if (_currentImageUuid != null && _currentImageUuid!.isNotEmpty) {
|
||||
await ImagesManager.instance.deleteImage(_currentImageUuid!);
|
||||
}
|
||||
await DbHelper.instance.deleteDrone(widget.drone.id!);
|
||||
if (mounted) {
|
||||
|
||||
|
||||
|
||||
Navigator.of(context).pop(true);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
appBar: AppBar(
|
||||
title: Text(_isEditing ? l10n.editDrone : l10n.droneDetails),
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(_isEditing ? Icons.save : Icons.edit),
|
||||
onPressed: () {
|
||||
if (_isEditing) {
|
||||
_saveDrone();
|
||||
}
|
||||
setState(() {
|
||||
_isEditing = !_isEditing;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (!_isEditing)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete_forever, color: Colors.redAccent),
|
||||
onPressed: _deleteDrone,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
Center(
|
||||
child: Container(
|
||||
width: 180,
|
||||
height: 180,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blueGrey[800],
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(color: Colors.lightBlueAccent, width: 2),
|
||||
image: _displayImage != null
|
||||
? DecorationImage(
|
||||
image: _displayImage!.image,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: _displayImage == null
|
||||
? Icon(
|
||||
Icons.camera_alt,
|
||||
size: 80,
|
||||
color: Colors.blueGrey[400],
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
if (_isEditing) ...[
|
||||
const SizedBox(height: 15),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _pickImage(ImageSource.camera),
|
||||
icon: const Icon(Icons.camera_alt),
|
||||
label: Text(l10n.takePhoto),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blueGrey[700],
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _pickImage(ImageSource.gallery),
|
||||
icon: const Icon(Icons.photo_library),
|
||||
label: Text(l10n.chooseFromGallery),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blueGrey[700],
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_currentImageUuid != null)
|
||||
TextButton.icon(
|
||||
onPressed: _deleteImage,
|
||||
icon: const Icon(Icons.clear, color: Colors.redAccent),
|
||||
label: Text(l10n.removeImage, style: const TextStyle(color: Colors.redAccent)),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 30),
|
||||
TextFormField(
|
||||
controller: _nameController,
|
||||
readOnly: !_isEditing,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.droneName,
|
||||
labelStyle: const TextStyle(color: Colors.white70),
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.white30),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.lightBlueAccent),
|
||||
),
|
||||
),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return l10n.pleaseEnterDroneName;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
if (_isEditing)
|
||||
Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: _saveDrone,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
foregroundColor: Colors.black,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 15),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
l10n.saveChanges,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
Text(
|
||||
l10n.latestFlights,
|
||||
style: Theme.of(context).textTheme.headlineSmall!.copyWith(color: Colors.white),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
_isLoadingFlights
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _associatedFlights.isEmpty
|
||||
? Center(
|
||||
child: Text(
|
||||
l10n.noFlightsYet,
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(color: Colors.white70),
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: _associatedFlights.length,
|
||||
itemBuilder: (context, index) {
|
||||
final flight = _associatedFlights[index];
|
||||
final DateTime flightDate =
|
||||
DateTime.fromMillisecondsSinceEpoch(flight.startTimestamp * 1000);
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20, vertical: 12),
|
||||
leading: const Icon(Icons.flight_takeoff,
|
||||
color: Colors.orangeAccent, size: 32),
|
||||
title: Text(
|
||||
'${flight.name} - ${flightDate.toLocal().toString().split(' ')[0]}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
trailing: const Icon(Icons.arrow_forward_ios,
|
||||
size: 20, color: Colors.white54),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => FlightDetailPage(flight: flight),
|
||||
),
|
||||
).then((_) => _loadAssociatedFlights());
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
373
lib/pages/flight_detail_page.dart
Normal file
373
lib/pages/flight_detail_page.dart
Normal file
@ -0,0 +1,373 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rtime/l10n/app_localizations.dart';
|
||||
import 'package:rtime/models/flight.dart';
|
||||
import 'package:rtime/models/drone.dart';
|
||||
import 'package:rtime/models/battery.dart';
|
||||
import 'package:rtime/db/db_helper.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:rtime/images_manager.dart';
|
||||
|
||||
class FlightDetailPage extends StatefulWidget {
|
||||
final Flight flight;
|
||||
|
||||
const FlightDetailPage({super.key, required this.flight});
|
||||
|
||||
@override
|
||||
State<FlightDetailPage> createState() => _FlightDetailPageState();
|
||||
}
|
||||
|
||||
class _FlightDetailPageState extends State<FlightDetailPage> {
|
||||
Drone? _associatedDrone;
|
||||
Battery? _associatedBattery;
|
||||
Image? _droneDisplayImage;
|
||||
Image? _batteryDisplayImage;
|
||||
bool _isLoading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadAssociatedData();
|
||||
}
|
||||
|
||||
Future<void> _loadImage(String? uuid, Function(Image?) setImage) async {
|
||||
if (uuid != null && uuid.isNotEmpty) {
|
||||
final loadedImage = await ImagesManager.instance.loadImage(uuid);
|
||||
if (mounted) {
|
||||
setImage(loadedImage);
|
||||
}
|
||||
} else {
|
||||
if (mounted) {
|
||||
setImage(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadAssociatedData() async {
|
||||
try {
|
||||
final droneList = await DbHelper.instance.getDrones();
|
||||
final batteryList = await DbHelper.instance.getBatteries();
|
||||
|
||||
final drone = droneList.firstWhere(
|
||||
(d) => d.id == widget.flight.droneId,
|
||||
orElse: () => Drone(name: 'Unknown Drone'));
|
||||
|
||||
final battery = batteryList.firstWhere(
|
||||
(b) => b.id == widget.flight.batteryId,
|
||||
orElse: () => Battery(
|
||||
name: 'Unknown Battery', type: '', voltage: 0.0));
|
||||
|
||||
|
||||
await _loadImage(drone.imageUuid, (image) {
|
||||
setState(() {
|
||||
_droneDisplayImage = image;
|
||||
});
|
||||
});
|
||||
await _loadImage(battery.imageUuid, (image) {
|
||||
setState(() {
|
||||
_batteryDisplayImage = image;
|
||||
});
|
||||
});
|
||||
|
||||
setState(() {
|
||||
_associatedDrone = drone;
|
||||
_associatedBattery = battery;
|
||||
_isLoading = false;
|
||||
});
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _formatDuration(int startTimestamp, int endTimestamp) {
|
||||
final duration = Duration(seconds: endTimestamp - startTimestamp);
|
||||
String twoDigits(int n) => n.toString().padLeft(2, '0');
|
||||
String hours = twoDigits(duration.inHours);
|
||||
String minutes = twoDigits(duration.inMinutes.remainder(60));
|
||||
String seconds = twoDigits(duration.inSeconds.remainder(60));
|
||||
return '$hours:$minutes:$seconds';
|
||||
}
|
||||
|
||||
Future<void> _deleteFlight() async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final bool? confirm = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(l10n.deleteFlightConfirmationTitle),
|
||||
content: Text(l10n.deleteFlightConfirmationMessage(widget.flight.name)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: Text(l10n.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirm == true) {
|
||||
try {
|
||||
await DbHelper.instance.deleteFlight(widget.flight.id!);
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop(true);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final flight = widget.flight;
|
||||
|
||||
final DateTime startDate =
|
||||
DateTime.fromMillisecondsSinceEpoch(flight.startTimestamp * 1000);
|
||||
final DateTime endDate =
|
||||
DateTime.fromMillisecondsSinceEpoch(flight.endTimestamp * 1000);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.flightDetails),
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
elevation: 0,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete_forever, color: Colors.redAccent),
|
||||
onPressed: _deleteFlight,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Card(
|
||||
elevation: 4,
|
||||
margin: const EdgeInsets.only(bottom: 20),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
flight.name,
|
||||
style: Theme.of(context).textTheme.headlineMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Divider(color: Colors.grey[300]),
|
||||
|
||||
_buildImageAndDetailRow(
|
||||
context,
|
||||
l10n.drone,
|
||||
_associatedDrone?.name ?? l10n.unknown,
|
||||
_droneDisplayImage,
|
||||
Icons.flight_takeoff,
|
||||
),
|
||||
|
||||
_buildImageAndDetailRow(
|
||||
context,
|
||||
l10n.battery,
|
||||
_associatedBattery?.name ?? l10n.unknown,
|
||||
_batteryDisplayImage,
|
||||
Icons.battery_std,
|
||||
),
|
||||
_buildDetailRow(
|
||||
l10n.startTime,
|
||||
'${startDate.toLocal().toString().split(' ')[0]} ${startDate.toLocal().toString().split(' ')[1].substring(0, 5)}'),
|
||||
_buildDetailRow(
|
||||
l10n.endTime,
|
||||
'${endDate.toLocal().toString().split(' ')[0]} ${endDate.toLocal().toString().split(' ')[1].substring(0, 5)}'),
|
||||
_buildDetailRow(
|
||||
l10n.duration,
|
||||
_formatDuration(
|
||||
flight.startTimestamp, flight.endTimestamp)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
if (flight.locationLat != null && flight.locationLong != null) ...[
|
||||
Text(
|
||||
l10n.flightLocation,
|
||||
style: Theme.of(context).textTheme.headlineSmall!.copyWith(color: Colors.white),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
Container(
|
||||
height: 300,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
child: FlutterMap(
|
||||
options: MapOptions(
|
||||
initialCenter: LatLng(
|
||||
flight.locationLat!, flight.locationLong!),
|
||||
initialZoom: 15.0,
|
||||
interactionOptions: const InteractionOptions(
|
||||
flags: InteractiveFlag.all & ~InteractiveFlag.rotate,
|
||||
),
|
||||
),
|
||||
children: [
|
||||
TileLayer(
|
||||
urlTemplate:
|
||||
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
userAgentPackageName: 'com.example.rtime',
|
||||
),
|
||||
MarkerLayer(
|
||||
markers: [
|
||||
Marker(
|
||||
point: LatLng(
|
||||
flight.locationLat!, flight.locationLong!),
|
||||
width: 80,
|
||||
height: 80,
|
||||
child: const Icon(
|
||||
Icons.location_on,
|
||||
color: Colors.red,
|
||||
size: 40.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
const SizedBox(height: 20),
|
||||
Center(
|
||||
child: Text(
|
||||
l10n.noLocationData,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(color: Colors.white70),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget _buildDetailRow(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
'$label:',
|
||||
style: Theme.of(context).textTheme.titleSmall!.copyWith(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Text(
|
||||
value,
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget _buildImageAndDetailRow(
|
||||
BuildContext context, String label, String value, Image? displayImage, IconData defaultIcon) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
margin: const EdgeInsets.only(right: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blueGrey[800],
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.blueGrey[600]!, width: 1),
|
||||
image: displayImage != null
|
||||
? DecorationImage(
|
||||
image: displayImage.image,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: displayImage == null
|
||||
? Icon(
|
||||
defaultIcon,
|
||||
size: 24,
|
||||
color: Colors.blueGrey[400],
|
||||
)
|
||||
: null,
|
||||
),
|
||||
|
||||
Expanded(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
'$label:',
|
||||
style: Theme.of(context).textTheme.titleSmall!.copyWith(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Text(
|
||||
value,
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
389
lib/pages/home_page.dart
Normal file
389
lib/pages/home_page.dart
Normal file
@ -0,0 +1,389 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rtime/models/drone.dart';
|
||||
import 'package:rtime/models/battery.dart';
|
||||
import 'package:rtime/models/flight.dart';
|
||||
import 'package:rtime/widgets/drone_cart.dart';
|
||||
import 'package:rtime/widgets/battery_card.dart';
|
||||
import 'package:rtime/l10n/app_localizations.dart';
|
||||
import 'package:rtime/pages/settings_page.dart';
|
||||
import 'package:rtime/pages/new_flight_page.dart';
|
||||
import 'package:rtime/pages/new_drone_page.dart';
|
||||
import 'package:rtime/pages/new_battery_page.dart';
|
||||
import 'package:rtime/pages/drone_detail_page.dart';
|
||||
import 'package:rtime/pages/battery_detail_page.dart';
|
||||
import 'package:rtime/db/db_helper.dart';
|
||||
import 'package:rtime/pages/flight_detail_page.dart';
|
||||
|
||||
|
||||
import 'package:rtime/widgets/page_transition_animations.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
State<HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
late Future<List<Drone>> _dronesFuture;
|
||||
late Future<List<Battery>> _batteriesFuture;
|
||||
late Future<List<Flight>> _flightsFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadData();
|
||||
}
|
||||
|
||||
void _loadData() {
|
||||
setState(() {
|
||||
_dronesFuture = DbHelper.instance.getDrones();
|
||||
_batteriesFuture = DbHelper.instance.getBatteries();
|
||||
_flightsFuture = DbHelper.instance.getFlights();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
_loadData();
|
||||
await Future.wait([_dronesFuture, _batteriesFuture, _flightsFuture]);
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 60.0, left: 16.0, right: 16.0, bottom: 90.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
l10n.appTitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 48,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.lightBlueAccent,
|
||||
fontFamily: 'Montserrat',
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon:
|
||||
const Icon(Icons.settings, size: 30, color: Colors.white70),
|
||||
onPressed: () {
|
||||
|
||||
Navigator.of(context).push(
|
||||
SlideRightPageRoute(
|
||||
page: const SettingsPage(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
|
||||
|
||||
Text(
|
||||
l10n.yourDrones,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
SizedBox(
|
||||
height: 170,
|
||||
child: FutureBuilder<List<Drone>>(
|
||||
future: _dronesFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(
|
||||
child: Text(
|
||||
'${l10n.errorLoadingDrones}: ${snapshot.error}'));
|
||||
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(right: 16),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
|
||||
Navigator.of(context).push(
|
||||
SlideUpPageRoute(
|
||||
page: const NewDronePage(),
|
||||
),
|
||||
).then((_) => _loadData());
|
||||
},
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: SizedBox(
|
||||
width: 140,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.add_circle_outline,
|
||||
size: 50, color: Colors.blueGrey[400]),
|
||||
const SizedBox(height: 10),
|
||||
Text(l10n.addDrone,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleSmall),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final drones = snapshot.data!;
|
||||
return ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: drones.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == drones.length) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(right: 16),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
|
||||
Navigator.of(context).push(
|
||||
SlideUpPageRoute(
|
||||
page: const NewDronePage(),
|
||||
),
|
||||
).then((_) => _loadData());
|
||||
},
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: SizedBox(
|
||||
width: 140,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.add_circle_outline,
|
||||
size: 50, color: Colors.blueGrey[400]),
|
||||
const SizedBox(height: 10),
|
||||
Text(l10n.addDrone,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleSmall),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return DroneCard(
|
||||
drone: drones[index],
|
||||
onTap: () {
|
||||
|
||||
Navigator.of(context).push(
|
||||
SlideRightPageRoute(
|
||||
page: DroneDetailPage(drone: drones[index]),
|
||||
),
|
||||
).then((result) {
|
||||
|
||||
if (result == true) {
|
||||
_loadData();
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const Divider(
|
||||
height: 50, thickness: 2, indent: 0, endIndent: 0, color: Colors.white10),
|
||||
|
||||
|
||||
Text(
|
||||
l10n.yourBatteries,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
SizedBox(
|
||||
height: 170,
|
||||
child: FutureBuilder<List<Battery>>(
|
||||
future: _batteriesFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(
|
||||
child: Text(
|
||||
'${l10n.errorLoadingBatteries}: ${snapshot.error}'));
|
||||
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(right: 16),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
|
||||
Navigator.of(context).push(
|
||||
SlideUpPageRoute(
|
||||
page: const NewBatteryPage(),
|
||||
),
|
||||
).then((_) => _loadData());
|
||||
},
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: SizedBox(
|
||||
width: 140,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.add_circle_outline,
|
||||
size: 50, color: Colors.blueGrey[400]),
|
||||
const SizedBox(height: 10),
|
||||
Text(l10n.addBattery,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleSmall),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final batteries = snapshot.data!;
|
||||
return ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: batteries.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == batteries.length) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(right: 16),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
|
||||
Navigator.of(context).push(
|
||||
SlideUpPageRoute(
|
||||
page: const NewBatteryPage(),
|
||||
),
|
||||
).then((_) => _loadData());
|
||||
},
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: SizedBox(
|
||||
width: 140,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.add_circle_outline,
|
||||
size: 50, color: Colors.blueGrey[400]),
|
||||
const SizedBox(height: 10),
|
||||
Text(l10n.addBattery,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleSmall),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return BatteryCard(
|
||||
battery: batteries[index],
|
||||
onTap: () {
|
||||
|
||||
Navigator.of(context).push(
|
||||
SlideRightPageRoute(
|
||||
page: BatteryDetailPage(battery: batteries[index]),
|
||||
),
|
||||
).then((result) {
|
||||
|
||||
if (result == true) {
|
||||
_loadData();
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const Divider(
|
||||
height: 50, thickness: 2, indent: 0, endIndent: 0, color: Colors.white10),
|
||||
|
||||
|
||||
Text(
|
||||
l10n.latestFlights,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
FutureBuilder<List<Flight>>(
|
||||
future: _flightsFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(
|
||||
child: Text(
|
||||
'${l10n.errorLoadingFlights}: ${snapshot.error}'));
|
||||
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
l10n.noFlightsYet,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(color: Colors.white70),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final flights = snapshot.data!;
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: flights.length,
|
||||
itemBuilder: (context, index) {
|
||||
final flight = flights[index];
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20, vertical: 12),
|
||||
leading: const Icon(Icons.flight_takeoff,
|
||||
color: Colors.orangeAccent, size: 32),
|
||||
title: Text(
|
||||
'${flight.name} - ${DateTime.fromMillisecondsSinceEpoch(flight.startTimestamp * 1000).toLocal().toString().split(' ')[0]}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
trailing: const Icon(Icons.arrow_forward_ios,
|
||||
size: 20, color: Colors.white54),
|
||||
onTap: () {
|
||||
|
||||
Navigator.of(context).push(
|
||||
SlideRightPageRoute(
|
||||
page: FlightDetailPage(flight: flight),
|
||||
),
|
||||
).then((_) => _loadData());
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
|
||||
Navigator.of(context).push(
|
||||
SlideUpPageRoute(
|
||||
page: const NewFlightPage(),
|
||||
),
|
||||
).then((_) => _loadData());
|
||||
},
|
||||
label: Text(
|
||||
l10n.newFlight,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
icon: const Icon(Icons.add_to_photos, size: 28),
|
||||
),
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||
);
|
||||
}
|
||||
}
|
||||
252
lib/pages/new_battery_page.dart
Normal file
252
lib/pages/new_battery_page.dart
Normal file
@ -0,0 +1,252 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rtime/l10n/app_localizations.dart';
|
||||
import 'package:rtime/models/battery.dart';
|
||||
import 'package:rtime/db/db_helper.dart';
|
||||
import 'package:rtime/images_manager.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
class NewBatteryPage extends StatefulWidget {
|
||||
const NewBatteryPage({super.key});
|
||||
|
||||
@override
|
||||
State<NewBatteryPage> createState() => _NewBatteryPageState();
|
||||
}
|
||||
|
||||
class _NewBatteryPageState extends State<NewBatteryPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
final TextEditingController _typeController = TextEditingController();
|
||||
final TextEditingController _voltageController = TextEditingController();
|
||||
String? _selectedImageUuid;
|
||||
Image? _displayImage;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_typeController.dispose();
|
||||
_voltageController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
Future<void> _pickImage(ImageSource source) async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final uuid = await ImagesManager.instance.createImage(source);
|
||||
if (uuid != null) {
|
||||
final loadedImage = await ImagesManager.instance.loadImage(uuid);
|
||||
setState(() {
|
||||
_selectedImageUuid = uuid;
|
||||
_displayImage = loadedImage;
|
||||
});
|
||||
if (mounted) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
} else {
|
||||
if (mounted) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _saveBattery() async {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final newBattery = Battery(
|
||||
name: _nameController.text.trim(),
|
||||
type: _typeController.text.trim(),
|
||||
voltage: double.parse(_voltageController.text.trim()),
|
||||
imageUuid: _selectedImageUuid,
|
||||
);
|
||||
|
||||
try {
|
||||
await DbHelper.instance.insertBattery(newBattery);
|
||||
if (mounted) {
|
||||
|
||||
|
||||
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.addBattery),
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 150,
|
||||
height: 150,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blueGrey[800],
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(color: Colors.lightBlueAccent, width: 2),
|
||||
|
||||
image: _displayImage != null
|
||||
? DecorationImage(
|
||||
image: _displayImage!.image,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
|
||||
child: _displayImage == null
|
||||
? Icon(
|
||||
Icons.camera_alt,
|
||||
size: 60,
|
||||
color: Colors.blueGrey[400],
|
||||
)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(l10n.batteryImage,
|
||||
style: Theme.of(context).textTheme.titleSmall),
|
||||
const SizedBox(height: 15),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _pickImage(ImageSource.camera),
|
||||
icon: const Icon(Icons.camera_alt),
|
||||
label: Text(l10n.takePhoto),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blueGrey[700],
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _pickImage(ImageSource.gallery),
|
||||
icon: const Icon(Icons.photo_library),
|
||||
label: Text(l10n.chooseFromGallery),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blueGrey[700],
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
TextFormField(
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.batteryName,
|
||||
labelStyle: const TextStyle(color: Colors.white70),
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.white30),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.lightBlueAccent),
|
||||
),
|
||||
),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return l10n.pleaseEnterBatteryName;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
TextFormField(
|
||||
controller: _typeController,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.batteryType,
|
||||
labelStyle: const TextStyle(color: Colors.white70),
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.white30),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.lightBlueAccent),
|
||||
),
|
||||
),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return l10n.pleaseEnterBatteryType;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
TextFormField(
|
||||
controller: _voltageController,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.batteryVoltage,
|
||||
labelStyle: const TextStyle(color: Colors.white70),
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.white30),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.lightBlueAccent),
|
||||
),
|
||||
),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return l10n.pleaseEnterBatteryVoltage;
|
||||
}
|
||||
if (double.tryParse(value) == null) {
|
||||
return l10n.pleaseEnterValidNumber;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: _saveBattery,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
foregroundColor: Colors.black,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 40, vertical: 15),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
l10n.saveBattery,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
200
lib/pages/new_drone_page.dart
Normal file
200
lib/pages/new_drone_page.dart
Normal file
@ -0,0 +1,200 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rtime/l10n/app_localizations.dart';
|
||||
import 'package:rtime/models/drone.dart';
|
||||
import 'package:rtime/db/db_helper.dart';
|
||||
import 'package:rtime/images_manager.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
class NewDronePage extends StatefulWidget {
|
||||
const NewDronePage({super.key});
|
||||
|
||||
@override
|
||||
State<NewDronePage> createState() => _NewDronePageState();
|
||||
}
|
||||
|
||||
class _NewDronePageState extends State<NewDronePage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
String? _selectedImageUuid;
|
||||
Image? _displayImage;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
Future<void> _pickImage(ImageSource source) async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final uuid = await ImagesManager.instance.createImage(source);
|
||||
if (uuid != null) {
|
||||
final loadedImage = await ImagesManager.instance.loadImage(uuid);
|
||||
setState(() {
|
||||
_selectedImageUuid = uuid;
|
||||
_displayImage = loadedImage;
|
||||
});
|
||||
if (mounted) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
} else {
|
||||
if (mounted) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _saveDrone() async {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final newDrone = Drone(
|
||||
name: _nameController.text.trim(),
|
||||
imageUuid: _selectedImageUuid,
|
||||
);
|
||||
|
||||
try {
|
||||
await DbHelper.instance.insertDrone(newDrone);
|
||||
if (mounted) {
|
||||
|
||||
|
||||
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.addDrone),
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 150,
|
||||
height: 150,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blueGrey[800],
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(color: Colors.lightBlueAccent, width: 2),
|
||||
|
||||
image: _displayImage != null
|
||||
? DecorationImage(
|
||||
image: _displayImage!.image,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
|
||||
child: _displayImage == null
|
||||
? Icon(
|
||||
Icons.camera_alt,
|
||||
size: 60,
|
||||
color: Colors.blueGrey[400],
|
||||
)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(l10n.droneImage,
|
||||
style: Theme.of(context).textTheme.titleSmall),
|
||||
const SizedBox(height: 15),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _pickImage(ImageSource.camera),
|
||||
icon: const Icon(Icons.camera_alt),
|
||||
label: Text(l10n.takePhoto),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blueGrey[700],
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _pickImage(ImageSource.gallery),
|
||||
icon: const Icon(Icons.photo_library),
|
||||
label: Text(l10n.chooseFromGallery),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blueGrey[700],
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
TextFormField(
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.droneName,
|
||||
labelStyle: const TextStyle(color: Colors.white70),
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.white30),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.lightBlueAccent),
|
||||
),
|
||||
),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return l10n.pleaseEnterDroneName;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: _saveDrone,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
foregroundColor: Colors.black,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 40, vertical: 15),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
l10n.saveDrone,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
578
lib/pages/new_flight_page.dart
Normal file
578
lib/pages/new_flight_page.dart
Normal file
@ -0,0 +1,578 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rtime/l10n/app_localizations.dart';
|
||||
import 'package:rtime/models/drone.dart';
|
||||
import 'package:rtime/models/battery.dart';
|
||||
import 'package:rtime/models/flight.dart';
|
||||
import 'package:rtime/db/db_helper.dart';
|
||||
import 'dart:async';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
class NewFlightPage extends StatefulWidget {
|
||||
const NewFlightPage({super.key});
|
||||
|
||||
@override
|
||||
State<NewFlightPage> createState() => _NewFlightPageState();
|
||||
}
|
||||
|
||||
class _NewFlightPageState extends State<NewFlightPage> {
|
||||
|
||||
late Future<List<Drone>> _dronesFuture;
|
||||
late Future<List<Battery>> _batteriesFuture;
|
||||
|
||||
|
||||
Drone? selectedDrone;
|
||||
Battery? selectedBattery;
|
||||
bool isFlightActive = false;
|
||||
Stopwatch stopwatch = Stopwatch();
|
||||
Timer? timer;
|
||||
String formattedTime = '00:00:00';
|
||||
int? flightStartTime;
|
||||
|
||||
|
||||
bool useGpsLocation = false;
|
||||
Position? currentPosition;
|
||||
bool isGettingLocation = false;
|
||||
|
||||
|
||||
final MapController _mapController = MapController();
|
||||
|
||||
|
||||
bool _initialMapCentered = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadAvailableItems();
|
||||
}
|
||||
|
||||
|
||||
void _loadAvailableItems() {
|
||||
_dronesFuture = DbHelper.instance.getDrones();
|
||||
_batteriesFuture = DbHelper.instance.getBatteries();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
|
||||
Future<void> _getCurrentLocation() async {
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() {
|
||||
isGettingLocation = true;
|
||||
});
|
||||
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
bool serviceEnabled;
|
||||
LocationPermission permission;
|
||||
|
||||
serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
if (mounted) {
|
||||
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
useGpsLocation = false;
|
||||
});
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isGettingLocation = false;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
if (mounted) {
|
||||
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
useGpsLocation = false;
|
||||
});
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isGettingLocation = false;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (permission == LocationPermission.deniedForever) {
|
||||
if (mounted) {
|
||||
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
useGpsLocation = false;
|
||||
});
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isGettingLocation = false;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Position position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high,
|
||||
timeLimit: const Duration(seconds: 15),
|
||||
);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
currentPosition = position;
|
||||
|
||||
|
||||
if (_initialMapCentered && _mapController.camera.center != LatLng(position.latitude, position.longitude)) {
|
||||
_mapController.move(
|
||||
LatLng(currentPosition!.latitude, currentPosition!.longitude),
|
||||
_mapController.camera.zoom,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (mounted) {
|
||||
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
useGpsLocation = false;
|
||||
currentPosition = null;
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isGettingLocation = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void _startFlight() {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
if (selectedDrone == null || selectedBattery == null) {
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.selectDroneBattery)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (useGpsLocation) {
|
||||
setState(() {
|
||||
isGettingLocation = true;
|
||||
});
|
||||
|
||||
_getCurrentLocation().then((_) {
|
||||
|
||||
if (useGpsLocation && currentPosition == null) {
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_proceedToStartFlight();
|
||||
});
|
||||
} else {
|
||||
_proceedToStartFlight();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void _proceedToStartFlight() {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
setState(() {
|
||||
isFlightActive = true;
|
||||
stopwatch.start();
|
||||
flightStartTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
timer = Timer.periodic(const Duration(milliseconds: 100), (Timer t) {
|
||||
if (stopwatch.isRunning) {
|
||||
setState(() {
|
||||
formattedTime = _formatDuration(stopwatch.elapsed);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
Future<void> _stopFlight() async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
|
||||
stopwatch.stop();
|
||||
timer?.cancel();
|
||||
|
||||
|
||||
final int flightEndTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
|
||||
final Duration flightDuration = stopwatch.elapsed;
|
||||
|
||||
|
||||
final Flight newFlight = Flight(
|
||||
name: '${selectedDrone!.name} - ${DateTime.fromMillisecondsSinceEpoch(flightStartTime! * 1000).toLocal().toString().split(' ')[0]}',
|
||||
startTimestamp: flightStartTime!,
|
||||
endTimestamp: flightEndTime,
|
||||
droneId: selectedDrone!.id!,
|
||||
batteryId: selectedBattery!.id!,
|
||||
locationLat: currentPosition?.latitude,
|
||||
locationLong: currentPosition?.longitude,
|
||||
);
|
||||
|
||||
try {
|
||||
|
||||
await DbHelper.instance.insertFlight(newFlight);
|
||||
if (mounted) {
|
||||
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
|
||||
}
|
||||
} finally {
|
||||
|
||||
setState(() {
|
||||
isFlightActive = false;
|
||||
stopwatch.reset();
|
||||
formattedTime = '00:00:00';
|
||||
selectedDrone = null;
|
||||
selectedBattery = null;
|
||||
currentPosition = null;
|
||||
useGpsLocation = false;
|
||||
isGettingLocation = false;
|
||||
_initialMapCentered = false;
|
||||
});
|
||||
|
||||
if (mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String _formatDuration(Duration duration) {
|
||||
String twoDigits(int n) => n.toString().padLeft(2, '0');
|
||||
String hours = twoDigits(duration.inHours);
|
||||
String minutes = twoDigits(duration.inMinutes.remainder(60));
|
||||
String seconds = twoDigits(duration.inSeconds.remainder(60));
|
||||
return '$hours:$minutes:$seconds';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.newFlight),
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
|
||||
Text(
|
||||
l10n.chooseDrone,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
Card(
|
||||
margin: const EdgeInsets.only(bottom: 20),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
child: FutureBuilder<List<Drone>>(
|
||||
future: _dronesFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(child: Text('${l10n.errorLoadingDrones}: ${snapshot.error}'));
|
||||
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
l10n.noDronesYet,
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final drones = snapshot.data!;
|
||||
return DropdownButtonHideUnderline(
|
||||
child: DropdownButton<Drone>(
|
||||
isExpanded: true,
|
||||
value: selectedDrone,
|
||||
hint: Text(
|
||||
l10n.selectDroneHint,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
dropdownColor: Theme.of(context).cardTheme.color,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
icon: const Icon(Icons.arrow_drop_down, color: Colors.white70),
|
||||
onChanged: isFlightActive || isGettingLocation
|
||||
? null
|
||||
: (Drone? newValue) {
|
||||
setState(() {
|
||||
selectedDrone = newValue;
|
||||
});
|
||||
},
|
||||
items: drones.map<DropdownMenuItem<Drone>>((Drone drone) {
|
||||
return DropdownMenuItem<Drone>(
|
||||
value: drone,
|
||||
child: Text(drone.name),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
Text(
|
||||
l10n.chooseBattery,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
Card(
|
||||
margin: const EdgeInsets.only(bottom: 30),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
child: FutureBuilder<List<Battery>>(
|
||||
future: _batteriesFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(child: Text('${l10n.errorLoadingBatteries}: ${snapshot.error}'));
|
||||
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
l10n.noBatteriesYet,
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final batteries = snapshot.data!;
|
||||
return DropdownButtonHideUnderline(
|
||||
child: DropdownButton<Battery>(
|
||||
isExpanded: true,
|
||||
value: selectedBattery,
|
||||
hint: Text(
|
||||
l10n.selectBatteryHint,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
dropdownColor: Theme.of(context).cardTheme.color,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
icon: const Icon(Icons.arrow_drop_down, color: Colors.white70),
|
||||
onChanged: isFlightActive || isGettingLocation
|
||||
? null
|
||||
: (Battery? newValue) {
|
||||
setState(() {
|
||||
selectedBattery = newValue;
|
||||
});
|
||||
},
|
||||
items: batteries.map<DropdownMenuItem<Battery>>((Battery battery) {
|
||||
return DropdownMenuItem<Battery>(
|
||||
value: battery,
|
||||
child: Text('${battery.name} (${battery.voltage}V)'),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
Card(
|
||||
margin: const EdgeInsets.only(bottom: 30),
|
||||
child: ListTile(
|
||||
title: Text(l10n.recordFlightLocation, style: Theme.of(context).textTheme.titleMedium),
|
||||
trailing: Switch(
|
||||
value: useGpsLocation,
|
||||
onChanged: isFlightActive || isGettingLocation
|
||||
? null
|
||||
: (bool value) async {
|
||||
setState(() {
|
||||
useGpsLocation = value;
|
||||
});
|
||||
if (value) {
|
||||
await _getCurrentLocation();
|
||||
} else {
|
||||
setState(() {
|
||||
currentPosition = null;
|
||||
_initialMapCentered = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
activeColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
leading: Icon(Icons.location_on, color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
if (currentPosition != null && useGpsLocation)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 20),
|
||||
height: 250,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
spreadRadius: 2,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 5),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
child: FlutterMap(
|
||||
mapController: _mapController,
|
||||
options: MapOptions(
|
||||
initialCenter: LatLng(currentPosition!.latitude, currentPosition!.longitude),
|
||||
initialZoom: 17.0,
|
||||
maxZoom: 19.0,
|
||||
minZoom: 3.0,
|
||||
interactionOptions: const InteractionOptions(
|
||||
flags: InteractiveFlag.all & ~InteractiveFlag.rotate,
|
||||
),
|
||||
onMapReady: () {
|
||||
if (!_initialMapCentered && currentPosition != null) {
|
||||
_mapController.move(
|
||||
LatLng(currentPosition!.latitude, currentPosition!.longitude),
|
||||
_mapController.camera.zoom,
|
||||
);
|
||||
setState(() {
|
||||
_initialMapCentered = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
children: [
|
||||
TileLayer(
|
||||
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
userAgentPackageName: 'com.example.rtime',
|
||||
),
|
||||
MarkerLayer(
|
||||
markers: [
|
||||
Marker(
|
||||
point: LatLng(currentPosition!.latitude, currentPosition!.longitude),
|
||||
width: 80,
|
||||
height: 80,
|
||||
child: const Icon(
|
||||
Icons.location_on,
|
||||
color: Colors.red,
|
||||
size: 40,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
Center(
|
||||
child: Text(
|
||||
formattedTime,
|
||||
style: const TextStyle(
|
||||
fontSize: 72,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.lightBlueAccent,
|
||||
fontFamily: 'RobotoMono',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
|
||||
|
||||
Center(
|
||||
child: isFlightActive
|
||||
? ElevatedButton.icon(
|
||||
onPressed: _stopFlight,
|
||||
icon: const Icon(Icons.stop, size: 30),
|
||||
label: Text(
|
||||
l10n.stopFlight,
|
||||
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.redAccent,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
minimumSize: const Size(250, 70),
|
||||
),
|
||||
)
|
||||
: (isGettingLocation
|
||||
? Column(
|
||||
children: [
|
||||
const CircularProgressIndicator(color: Colors.tealAccent),
|
||||
const SizedBox(height: 10),
|
||||
Text(l10n.obtainingLocation, style: Theme.of(context).textTheme.titleSmall),
|
||||
],
|
||||
)
|
||||
: ElevatedButton.icon(
|
||||
onPressed: _startFlight,
|
||||
icon: const Icon(Icons.flight_takeoff, size: 30),
|
||||
label: Text(
|
||||
l10n.startFlight,
|
||||
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.tealAccent,
|
||||
foregroundColor: Colors.black,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
minimumSize: const Size(250, 70),
|
||||
),
|
||||
)),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
126
lib/pages/settings_page.dart
Normal file
126
lib/pages/settings_page.dart
Normal file
@ -0,0 +1,126 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rtime/l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:rtime/providers/local_provider.dart';
|
||||
import 'package:rtime/providers/theme_provider.dart';
|
||||
|
||||
class SettingsPage extends StatefulWidget {
|
||||
const SettingsPage({super.key});
|
||||
|
||||
@override
|
||||
State<SettingsPage> createState() => _SettingsPageState();
|
||||
}
|
||||
|
||||
class _SettingsPageState extends State<SettingsPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final currentLocale = Localizations.localeOf(context);
|
||||
final themeProvider = Provider.of<ThemeProvider>(context);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.settingsTitle),
|
||||
backgroundColor: Theme.of(context).appBarTheme.backgroundColor,
|
||||
foregroundColor: Theme.of(context).appBarTheme.foregroundColor,
|
||||
iconTheme: Theme.of(context).iconTheme,
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l10n.languageSetting,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Card(
|
||||
color: Theme.of(context).cardTheme.color,
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text(l10n.english, style: Theme.of(context).textTheme.titleMedium),
|
||||
trailing: currentLocale.languageCode == 'en'
|
||||
? Icon(Icons.check_circle, color: Theme.of(context).colorScheme.secondary)
|
||||
: null,
|
||||
onTap: () {
|
||||
final localeProvider = Provider.of<LocaleProvider>(context, listen: false);
|
||||
localeProvider.setLocale(const Locale('en', ''));
|
||||
|
||||
|
||||
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(l10n.french, style: Theme.of(context).textTheme.titleMedium),
|
||||
trailing: currentLocale.languageCode == 'fr'
|
||||
? Icon(Icons.check_circle, color: Theme.of(context).colorScheme.secondary)
|
||||
: null,
|
||||
onTap: () {
|
||||
final localeProvider = Provider.of<LocaleProvider>(context, listen: false);
|
||||
localeProvider.setLocale(const Locale('fr', ''));
|
||||
|
||||
|
||||
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Text(
|
||||
l10n.themeSetting,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Card(
|
||||
color: Theme.of(context).cardTheme.color,
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text(l10n.themeSystem, style: Theme.of(context).textTheme.titleMedium),
|
||||
trailing: themeProvider.themeMode == ThemeMode.system
|
||||
? Icon(Icons.check_circle, color: Theme.of(context).colorScheme.secondary)
|
||||
: null,
|
||||
onTap: () {
|
||||
themeProvider.setThemeMode(ThemeMode.system);
|
||||
|
||||
|
||||
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(l10n.themeLight, style: Theme.of(context).textTheme.titleMedium),
|
||||
trailing: themeProvider.themeMode == ThemeMode.light
|
||||
? Icon(Icons.check_circle, color: Theme.of(context).colorScheme.secondary)
|
||||
: null,
|
||||
onTap: () {
|
||||
themeProvider.setThemeMode(ThemeMode.light);
|
||||
|
||||
|
||||
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(l10n.themeDark, style: Theme.of(context).textTheme.titleMedium),
|
||||
trailing: themeProvider.themeMode == ThemeMode.dark
|
||||
? Icon(Icons.check_circle, color: Theme.of(context).colorScheme.secondary)
|
||||
: null,
|
||||
onTap: () {
|
||||
themeProvider.setThemeMode(ThemeMode.dark);
|
||||
|
||||
|
||||
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
54
lib/providers/local_provider.dart
Normal file
54
lib/providers/local_provider.dart
Normal file
@ -0,0 +1,54 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class LocaleProvider extends ChangeNotifier {
|
||||
Locale? _locale;
|
||||
|
||||
Locale? get locale => _locale;
|
||||
|
||||
|
||||
LocaleProvider() {
|
||||
_loadLocale();
|
||||
}
|
||||
|
||||
|
||||
void _loadLocale() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final languageCode = prefs.getString('languageCode');
|
||||
final countryCode = prefs.getString('countryCode');
|
||||
|
||||
if (languageCode != null) {
|
||||
_locale = Locale(languageCode, countryCode);
|
||||
} else {
|
||||
_locale = null;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
||||
void setLocale(Locale newLocale) async {
|
||||
if (_locale == newLocale) return;
|
||||
|
||||
_locale = newLocale;
|
||||
notifyListeners();
|
||||
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('languageCode', newLocale.languageCode);
|
||||
if (newLocale.countryCode != null) {
|
||||
await prefs.setString('countryCode', newLocale.countryCode!);
|
||||
} else {
|
||||
await prefs.remove('countryCode');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void clearLocale() async {
|
||||
_locale = null;
|
||||
notifyListeners();
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove('languageCode');
|
||||
await prefs.remove('countryCode');
|
||||
}
|
||||
}
|
||||
28
lib/providers/theme_provider.dart
Normal file
28
lib/providers/theme_provider.dart
Normal file
@ -0,0 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class ThemeProvider extends ChangeNotifier {
|
||||
ThemeMode _themeMode = ThemeMode.system;
|
||||
|
||||
ThemeMode get themeMode => _themeMode;
|
||||
|
||||
ThemeProvider() {
|
||||
_loadThemeMode();
|
||||
}
|
||||
|
||||
void _loadThemeMode() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final themeIndex = prefs.getInt('themeMode') ?? 0;
|
||||
_themeMode = ThemeMode.values[themeIndex];
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setThemeMode(ThemeMode mode) async {
|
||||
if (_themeMode != mode) {
|
||||
_themeMode = mode;
|
||||
notifyListeners();
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt('themeMode', mode.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
lib/removecom.py
Normal file
83
lib/removecom.py
Normal file
@ -0,0 +1,83 @@
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
|
||||
def clean_comments_in_file(filepath):
|
||||
"""
|
||||
Supprime le reste de la ligne à partir de '//' dans un fichier donné,
|
||||
en essayant d'éviter les URL. Gère aussi les commentaires /* ... */.
|
||||
"""
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8') as f_read:
|
||||
content = f_read.read()
|
||||
|
||||
# Première passe : supprimer les commentaires /* ... */
|
||||
# Utilise re.DOTALL pour que . corresponde aussi aux retours à la ligne
|
||||
# et non-greedy quantifier *?
|
||||
content = re.sub(r'/\*.*?\*/', '', content, flags=re.DOTALL)
|
||||
|
||||
cleaned_lines = []
|
||||
for line in content.splitlines():
|
||||
# Vérifie si la ligne contient une URL avant de chercher un commentaire
|
||||
if re.search(r'https?://', line):
|
||||
# Si une URL est trouvée, nous sommes très prudents.
|
||||
# Nous ne supprimons les commentaires que s'ils sont CLAIREMENT après une URL et non mélangés
|
||||
# Cela reste une heuristique et n'est pas parfait.
|
||||
comment_index = line.find('//')
|
||||
if comment_index != -1 and not re.search(r'["\']https?://.*?//', line): # Évite les // dans les URLs entre guillemets
|
||||
# On tente de voir si le commentaire est vraiment à la fin de la ligne,
|
||||
# après d'éventuelles guillemets.
|
||||
# Ceci est une simplification et peut encore échouer.
|
||||
if comment_index > line.rfind('"') and comment_index > line.rfind("'"):
|
||||
cleaned_lines.append(line[:comment_index].rstrip())
|
||||
else:
|
||||
cleaned_lines.append(line) # Conserve la ligne si le // est potentiellement dans une chaîne/URL
|
||||
else:
|
||||
cleaned_lines.append(line)
|
||||
else:
|
||||
# Si pas d'URL, on peut être plus agressif avec les //
|
||||
if '//' in line:
|
||||
index = line.find('//')
|
||||
cleaned_lines.append(line[:index].rstrip())
|
||||
else:
|
||||
cleaned_lines.append(line)
|
||||
|
||||
# Rejoindre les lignes avec des retours à la ligne.
|
||||
# Ajoute un retour à la ligne final si le fichier en avait un.
|
||||
final_content = '\n'.join(cleaned_lines)
|
||||
if content.endswith('\n') and not final_content.endswith('\n'):
|
||||
final_content += '\n'
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f_write:
|
||||
f_write.write(final_content)
|
||||
print(f"Commentaires nettoyés dans : {filepath}")
|
||||
except Exception as e:
|
||||
print(f"Erreur lors du traitement de {filepath} : {e}")
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python clean_comments.py <path_to_directory>")
|
||||
print("Exemple: python clean_comments.py ./mon_projet_flutter")
|
||||
sys.exit(1)
|
||||
|
||||
target_directory = sys.argv[1]
|
||||
|
||||
if not os.path.isdir(target_directory):
|
||||
print(f"Erreur : Le chemin '{target_directory}' n'est pas un répertoire valide.")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Nettoyage des commentaires '//' et '/* ... */' dans le répertoire : {target_directory}")
|
||||
print("-" * 70)
|
||||
|
||||
for root, _, files in os.walk(target_directory):
|
||||
for file in files:
|
||||
filepath = os.path.join(root, file)
|
||||
# Filtrez les types de fichiers que vous souhaitez traiter
|
||||
if filepath.endswith(('.dart', '.js', '.ts', '.java', '.cpp', '.c', '.h', '.py')):
|
||||
clean_comments_in_file(filepath)
|
||||
|
||||
print("-" * 70)
|
||||
print("Nettoyage terminé.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
87
lib/widgets/battery_card.dart
Normal file
87
lib/widgets/battery_card.dart
Normal file
@ -0,0 +1,87 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rtime/models/battery.dart';
|
||||
import 'package:rtime/images_manager.dart';
|
||||
|
||||
class BatteryCard extends StatelessWidget {
|
||||
final Battery battery;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const BatteryCard({
|
||||
super.key,
|
||||
required this.battery,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final BorderRadius cardBorderRadius =
|
||||
Theme.of(context).cardTheme.shape is RoundedRectangleBorder
|
||||
? (Theme.of(context).cardTheme.shape as RoundedRectangleBorder).borderRadius as BorderRadius
|
||||
: BorderRadius.circular(12);
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(right: 16),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: cardBorderRadius,
|
||||
child: ClipRRect(
|
||||
borderRadius: cardBorderRadius,
|
||||
child: SizedBox(
|
||||
width: 140,
|
||||
height: 170,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.blueGrey[700]
|
||||
: Colors.grey[200],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: battery.imageUuid != null && battery.imageUuid!.isNotEmpty
|
||||
? FutureBuilder<Image?>(
|
||||
future: ImagesManager.instance.loadImage(battery.imageUuid!),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator(color: Colors.tealAccent));
|
||||
} else if (snapshot.hasError || !snapshot.hasData || snapshot.data == null) {
|
||||
return Icon(Icons.broken_image, size: 50, color: Colors.blueGrey[400]);
|
||||
} else {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: snapshot.data!,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
: Icon(Icons.battery_charging_full, size: 50, color: Theme.of(context).brightness == Brightness.dark ? Colors.tealAccent : Colors.green.shade400),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
battery.name,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
'${battery.voltage}V - ${battery.type}',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).brightness == Brightness.dark ? Colors.white70 : Colors.black54),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
82
lib/widgets/drone_cart.dart
Normal file
82
lib/widgets/drone_cart.dart
Normal file
@ -0,0 +1,82 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rtime/models/drone.dart';
|
||||
import 'package:rtime/images_manager.dart';
|
||||
|
||||
class DroneCard extends StatelessWidget {
|
||||
final Drone drone;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const DroneCard({
|
||||
super.key,
|
||||
required this.drone,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
final BorderRadius cardBorderRadius =
|
||||
Theme.of(context).cardTheme.shape is RoundedRectangleBorder
|
||||
? (Theme.of(context).cardTheme.shape as RoundedRectangleBorder).borderRadius as BorderRadius
|
||||
: BorderRadius.circular(12);
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(right: 16),
|
||||
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
|
||||
borderRadius: cardBorderRadius,
|
||||
child: ClipRRect(
|
||||
borderRadius: cardBorderRadius,
|
||||
child: SizedBox(
|
||||
width: 140,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.blueGrey[700]
|
||||
: Colors.grey[200],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: drone.imageUuid != null && drone.imageUuid!.isNotEmpty
|
||||
? FutureBuilder<Image?>(
|
||||
future: ImagesManager.instance.loadImage(drone.imageUuid!),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator(color: Colors.lightBlueAccent));
|
||||
} else if (snapshot.hasError || !snapshot.hasData || snapshot.data == null) {
|
||||
return Icon(Icons.broken_image, size: 50, color: Colors.blueGrey[400]);
|
||||
} else {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: snapshot.data!,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
: Icon(Icons.airplanemode_active, size: 50, color: Theme.of(context).brightness == Brightness.dark ? Colors.lightBlueAccent : Colors.blue.shade400),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
drone.name,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
88
lib/widgets/page_transition_animations.dart
Normal file
88
lib/widgets/page_transition_animations.dart
Normal file
@ -0,0 +1,88 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
class FadePageRoute extends PageRouteBuilder {
|
||||
final Widget page;
|
||||
FadePageRoute({required this.page})
|
||||
: super(
|
||||
pageBuilder: (
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) =>
|
||||
page,
|
||||
transitionsBuilder: (
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
) =>
|
||||
FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
class SlideRightPageRoute extends PageRouteBuilder {
|
||||
final Widget page;
|
||||
SlideRightPageRoute({required this.page})
|
||||
: super(
|
||||
pageBuilder: (
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) =>
|
||||
page,
|
||||
transitionsBuilder: (
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
) {
|
||||
const begin = Offset(1.0, 0.0);
|
||||
const end = Offset.zero;
|
||||
const curve = Curves.ease;
|
||||
|
||||
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
|
||||
|
||||
return SlideTransition(
|
||||
position: animation.drive(tween),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
class SlideUpPageRoute extends PageRouteBuilder {
|
||||
final Widget page;
|
||||
SlideUpPageRoute({required this.page})
|
||||
: super(
|
||||
pageBuilder: (
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) =>
|
||||
page,
|
||||
transitionsBuilder: (
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
) {
|
||||
const begin = Offset(0.0, 1.0);
|
||||
const end = Offset.zero;
|
||||
const curve = Curves.easeOut;
|
||||
|
||||
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
|
||||
|
||||
return SlideTransition(
|
||||
position: animation.drive(tween),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -6,13 +6,17 @@ import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import file_selector_macos
|
||||
import geolocator_apple
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
import sqlite3_flutter_libs
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
|
||||
}
|
||||
|
||||
213
pubspec.lock
213
pubspec.lock
@ -185,6 +185,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
dart_earcut:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_earcut
|
||||
sha256: e485001bfc05dcbc437d7bfb666316182e3522d4c3f9668048e004d0eb2ce43b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -270,6 +278,19 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_map:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_map
|
||||
sha256: f7d0379477274f323c3f3bc12d369a2b42eb86d1e7bd2970ae1ea3cff782449a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.1"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -296,6 +317,54 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
geolocator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: geolocator
|
||||
sha256: "149876cc5207a0f5daf4fdd3bfcf0a0f27258b3fe95108fa084f527ad0568f1b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.0"
|
||||
geolocator_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_android
|
||||
sha256: fcb1760a50d7500deca37c9a666785c047139b5f9ee15aa5469fae7dbbe3170d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.6.2"
|
||||
geolocator_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_apple
|
||||
sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.13"
|
||||
geolocator_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_platform_interface
|
||||
sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.6"
|
||||
geolocator_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_web
|
||||
sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.3"
|
||||
geolocator_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_windows
|
||||
sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.5"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -432,6 +501,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+1"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.20.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -464,6 +541,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.9.5"
|
||||
latlong2:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: latlong2
|
||||
sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.1"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -496,6 +581,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
lists:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lists
|
||||
sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
logger:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logger
|
||||
sha256: "2621da01aabaf223f8f961e751f2c943dbb374dc3559b982f200ccedadaa6999"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
logging:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -528,6 +629,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
mgrs_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mgrs_dart
|
||||
sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -536,6 +645,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nested
|
||||
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -624,6 +741,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
polylabel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: polylabel
|
||||
sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -640,6 +765,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.3"
|
||||
proj4dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: proj4dart
|
||||
sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: provider
|
||||
sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.5"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -656,6 +797,62 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.3"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.10"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.4"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -845,6 +1042,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
unicode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: unicode
|
||||
sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
uuid:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -901,6 +1106,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
wkt_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: wkt_parser
|
||||
sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@ -45,6 +45,14 @@ dependencies:
|
||||
uuid: ^4.5.1
|
||||
json_serializable: ^6.9.5
|
||||
image_cropper: ^9.1.0
|
||||
intl: ^0.20.2
|
||||
provider: ^6.1.2
|
||||
geolocator: ^12.0.0
|
||||
flutter_map: ^8.0.0
|
||||
latlong2: ^0.9.1
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
shared_preferences: ^2.2.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -67,6 +75,7 @@ flutter:
|
||||
# included with your application, so that you can use the icons in
|
||||
# the material Icons class.
|
||||
uses-material-design: true
|
||||
generate: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
|
||||
@ -7,11 +7,14 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <geolocator_windows/geolocator_windows.h>
|
||||
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
GeolocatorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("GeolocatorWindows"));
|
||||
Sqlite3FlutterLibsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin"));
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_windows
|
||||
geolocator_windows
|
||||
sqlite3_flutter_libs
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user