From 39254214283d11a2348745b25b606618c62c6c3e Mon Sep 17 00:00:00 2001 From: octagonal Date: Fri, 4 Jul 2025 23:39:34 +0200 Subject: [PATCH] Adds flight,battery models and image file managment --- lib/db/db_helper.dart | 86 ++++++++- lib/images_manager.dart | 79 +++++++++ lib/main.dart | 64 +++++-- lib/models/battery.dart | 33 ++++ lib/models/drone.dart | 30 ++-- lib/models/flight.dart | 51 ++++++ linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 165 ++++++++++++++++++ pubspec.yaml | 2 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 13 files changed, 485 insertions(+), 36 deletions(-) create mode 100644 lib/images_manager.dart create mode 100644 lib/models/battery.dart create mode 100644 lib/models/flight.dart diff --git a/lib/db/db_helper.dart b/lib/db/db_helper.dart index 5427baa..c8a3224 100644 --- a/lib/db/db_helper.dart +++ b/lib/db/db_helper.dart @@ -3,10 +3,13 @@ import 'dart:developer'; import 'dart:io'; import 'package:path/path.dart'; import 'package:logging/logging.dart'; +import 'package:rtime/models/battery.dart'; +import 'package:rtime/models/flight.dart'; import '../models/drone.dart'; -import 'package:path_provider/path_provider.dart';import 'package:sqflite/sqflite.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:sqflite/sqflite.dart'; class DbHelper { @@ -47,15 +50,42 @@ class DbHelper CREATE TABLE drones( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, - image TEXT + image_uuid TEXT ) '''); + + await db.execute(''' + CREATE TABLE batteries( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + type TEXT NOT NULL, + volatege REAL NOT NULL, + image_uuid TEXT + ) + '''); + + await db.execute(''' + CREATE TABLE flights( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + start_timestamp INTEGER NOT NULL, + end_timestamp INTEGER NOT NULL, + drone_id INTEGER NOT NULL, + battery_id INTEGER NOT NULL, + location_lat REAL, + location_long REAL, + FOREIGN KEY(drone_id) REFERENCES drones(id), + FOREIGN KEY(battery_id) REFERENCES batteries(id) + ) + '''); } - Future insertDrone(Drone drone) async + // Drone related helpers + Future insertDrone(Drone drone) async { final db = await database; - return await db.insert("drones", drone.toMap()); + drone.id = await db.insert("drones", drone.toMap()); + return drone; } Future> getDrones() async @@ -65,10 +95,54 @@ class DbHelper return maps.map((e) => Drone.fromMap(e)).toList(); } - Future deleteItem(int drone_id) async + Future deleteDrone(int droneId) async + { + // TODO: Delete image + final db = await database; + return await db.delete("drones", where: "id = ?", whereArgs: [droneId]); + } + + // Battery related helpers + Future insertBattery(Battery battery) async { final db = await database; - return await db.delete("drones", where: "id = ?", whereArgs: [drone_id]); + battery.id = await db.insert("batteries", battery.toMap()); + return battery; + } + + Future> getBatteries() async + { + final db = await database; + final maps = await db.query("batteries"); + return maps.map((e) => Battery.fromMap(e)).toList(); + } + + Future deleteBattery(int batteryId) async + { + // TODO: Delete image + final db = await database; + return await db.delete("batteries", where: "id = ?", whereArgs: [batteryId]); + } + + // Flight related helpers + Future insertFlight(Flight flight) async + { + final db = await database; + flight.id = await db.insert("flights", flight.toMap()); + return flight; + } + + Future> getFlights() async + { + final db = await database; + final maps = await db.query("flights"); + return maps.map((e) => Flight.fromMap(e)).toList(); + } + + Future deleteFlight(int flightId) async + { + final db = await database; + return await db.delete("flights", where: "id = ?", whereArgs: [flightId]); } Future closeDb() async diff --git a/lib/images_manager.dart b/lib/images_manager.dart new file mode 100644 index 0000000..a21abae --- /dev/null +++ b/lib/images_manager.dart @@ -0,0 +1,79 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:logging/logging.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as path; +import 'package:uuid/uuid.dart'; +import 'package:image/image.dart' as img; +import 'package:uuid/v5.dart'; + +class ImagesManager +{ + static final ImagesManager instance = ImagesManager._internal(); + ImagesManager._internal(); + + static final Logger log = Logger("ImagesManager"); + + static Uri? _imagesDirectory; + + Future get imageDirectory async + { + if(_imagesDirectory != null) return _imagesDirectory!; + await _initImagesDirectory(); + return _imagesDirectory!; + } + + Future _initImagesDirectory() async + { + final directoryLoc = await getApplicationDocumentsDirectory(); + final directoryName = "images"; + final directoryPath = path.join(directoryLoc.path, directoryName); + final directoryUri = Uri.directory(directoryPath); + final directory = Directory.fromUri(directoryUri); + + if(!await directory.exists()) + { + log.info("Image directory does not yet extists. Creating it."); + } + + directory.create(recursive: false); + + log.info("Image directory set up at '$directory'"); + + _imagesDirectory = directoryUri; + } + + Future createImage(ImageSource source) async + { + // Get image from camera or not + final XFile? ximage = await ImagePicker().pickImage(source: source); + if(ximage == null) return null; + + final uuid = Uuid().v6(); + final imageDir = await imageDirectory; + ximage.saveTo(path.join(imageDir.path, uuid + path.extension(ximage.name))); + return uuid; + } + + Future loadImage(String imageUuid) async + { + final imageDir = await imageDirectory; + if(!Uuid.isValidUUID(fromString: imageUuid)) + { + log.warning("Tried to load an image with an invalid UUID : '$imageUuid'."); + return null; + } + + 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'."); + return null; + } + + return Image.file(file); + } +} diff --git a/lib/main.dart b/lib/main.dart index c8538e2..c70bee2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,10 +1,14 @@ 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:sqflite_common_ffi/sqflite_ffi.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart'; void main() { if(Platform.isWindows || Platform.isLinux) @@ -74,7 +78,16 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { int _counter = 0; - void _incrementCounter() { + Future _incrementCounter() async { + + DbHelper.instance.insertDrone(Drone + ( + name: "Image test", + imageUuid: await ImagesManager.instance.createImage(ImageSource.camera) + )); + + + setState(() { // This call to setState tells the Flutter framework that something has // changed in this State, which causes it to rerun the build method below @@ -82,7 +95,6 @@ class _MyHomePageState extends State { // _counter without calling setState(), then the build method would not be // called again, and so nothing would appear to happen. _counter++; - DbHelper.instance.insertDrone(Drone(name: "skibidi drone")); }); } @@ -94,6 +106,43 @@ class _MyHomePageState extends State { // The Flutter framework has been optimized to make rerunning build methods // fast, so that you can just rebuild anything that needs updating rather // than having to individually change instances of widgets. + var children = + [ + const Text('You have pushed the button this many times:'), + Text( + '$_counter', + style: Theme.of(context).textTheme.headlineMedium, + ), + FutureBuilder( + future: Future(() async + { + final drones = await DbHelper.instance.getDrones(); + + if(drones.isEmpty) + { + return Icon(Icons.question_mark); + } + + final image = await ImagesManager.instance.loadImage(drones.first.imageUuid!); + if(image == null) + { + return Icon(Icons.error); + } + + return image; + }), + builder: (BuildContext ctx, AsyncSnapshot img) + { + if(!img.hasData) + { + return Center(child: CircularProgressIndicator(),); + } + + return img.data!; + } + ), + Icon(Icons.build)]; + return Scaffold( appBar: AppBar( // TRY THIS: Try changing the color here to a specific color (to @@ -122,14 +171,8 @@ class _MyHomePageState extends State { // action in the IDE, or press "p" in the console), to see the // wireframe for each widget. mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), + children: children, + ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, @@ -139,3 +182,4 @@ class _MyHomePageState extends State { ); } } + diff --git a/lib/models/battery.dart b/lib/models/battery.dart new file mode 100644 index 0000000..c4f18bd --- /dev/null +++ b/lib/models/battery.dart @@ -0,0 +1,33 @@ +class Battery { + int? id; + String name; + String type; + double voltage; + String? imageUuid; + + Battery({ + this.id, + required this.name, + required this.type, + required this.voltage, + this.imageUuid, + }); + + factory Battery.fromMap(Map map) => Battery( + id: map["id"], + name: map["name"], + type: map["type"], + voltage: map["voltage"], + imageUuid: map["image_uuid"], + ); + + Map toMap() { + return { + "id": id, + "name": name, + "type": type, + "voltage": voltage, + "image_uuid": imageUuid, + }; + } +} diff --git a/lib/models/drone.dart b/lib/models/drone.dart index eb63bc8..cccc52c 100644 --- a/lib/models/drone.dart +++ b/lib/models/drone.dart @@ -1,24 +1,14 @@ -import 'package:image/image.dart' as img; +class Drone { + int? id; + String name; + String? imageUuid; -class Drone -{ - final int? id; - final String name; - //final img.Image image; + Drone({this.id, required this.name, this.imageUuid}); - Drone ({this.id, required this.name}); //required this.image}); + factory Drone.fromMap(Map map) => + Drone(id: map["id"], name: map["name"], imageUuid: map["image_uuid"]); - factory Drone.fromMap(Map map) => Drone - ( - id: map["id"], - name: map["name"], - //image: img.decodeJpg(map["image"])!, - ); - - Map toMap() => - { - "id": id, - "name": name, - //"image": img.encodeJpg(image), - }; + Map toMap() { + return {"id": id, "name": name, "image_uuid": imageUuid}; + } } diff --git a/lib/models/flight.dart b/lib/models/flight.dart new file mode 100644 index 0000000..2f40094 --- /dev/null +++ b/lib/models/flight.dart @@ -0,0 +1,51 @@ +class Flight { + int? id; + String name; + + // Timestamp in seconds since epoch + int startTimestamp; + int endTimestamp; + + // Foreign keys + int droneId; + int batteryId; + + // Location + double? locationLat; + double? locationLong; + + Flight({ + this.id, + required this.name, + required this.startTimestamp, + required this.endTimestamp, + required this.droneId, + required this.batteryId, + this.locationLat, + this.locationLong, + }); + + factory Flight.fromMap(Map map) => Flight( + id: map["id"], + name: map["name"], + startTimestamp: map["start_timestamp"], + endTimestamp: map["end_timestamp"], + droneId: map["drone_id"], + batteryId: map["batteryId"], + locationLat: map["location_lat"], + locationLong: map["location_long"], + ); + + Map toMap() { + return { + "id": id, + "name": name, + "start_timestamp": startTimestamp, + "end_timestamp": endTimestamp, + "drone_id": droneId, + "battery_id": batteryId, + "location_lat": locationLat, + "location_long": locationLong, + }; + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 2c1ec4f..68ae3da 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin"); sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 7ea2a80..ee11cfe 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux sqlite3_flutter_libs ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 85d67e4..a41ae29 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,11 +5,13 @@ import FlutterMacOS import Foundation +import file_selector_macos import path_provider_foundation import sqflite_darwin import sqlite3_flutter_libs func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 1aa7ab8..8ab4f87 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" crypto: dependency: transitive description: @@ -81,6 +89,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711" + url: "https://pub.dev" + source: hosted + version: "0.9.4+3" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" + url: "https://pub.dev" + source: hosted + version: "0.9.3+4" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -94,11 +142,40 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e + url: "https://pub.dev" + source: hosted + version: "2.0.28" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" image: dependency: "direct main" description: @@ -107,6 +184,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.5.4" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb" + url: "https://pub.dev" + source: hosted + version: "0.8.12+23" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" + url: "https://pub.dev" + source: hosted + version: "0.8.12+2" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" leak_tracker: dependency: transitive description: @@ -171,6 +312,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" path: dependency: "direct main" description: @@ -272,6 +421,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" sqflite: dependency: "direct main" description: @@ -392,6 +549,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3b29f13..7fb098d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,8 @@ dependencies: sqlite3_flutter_libs: ^0.5.34 sqflite_common_ffi: ^2.3.6 image: ^4.5.4 + image_picker: ^1.1.2 + uuid: ^4.5.1 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 988f3c8..987fb3d 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,9 +6,12 @@ #include "generated_plugin_registrant.h" +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); Sqlite3FlutterLibsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 8abff95..3057813 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows sqlite3_flutter_libs )