diff --git a/Fly App/backup_pocketbase_app/login_page.dart b/Fly App/backup_pocketbase_app/login_page.dart deleted file mode 100644 index a6baace..0000000 --- a/Fly App/backup_pocketbase_app/login_page.dart +++ /dev/null @@ -1,146 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'pb_service.dart'; - -class LoginPage extends StatefulWidget { - const LoginPage({super.key, required this.onSignedIn}); - - final VoidCallback onSignedIn; - - @override - State createState() => _LoginPageState(); -} - -class _LoginPageState extends State { - late final TextEditingController _server = - TextEditingController(text: pb.defaultBaseUrl); - final TextEditingController _email = - TextEditingController(text: 'pilot@dji.local'); - final TextEditingController _password = TextEditingController(); - final TextEditingController _device = - TextEditingController(text: 'phone'); - - bool _busy = false; - String? _error; - - Future _signIn() async { - setState(() { - _busy = true; - _error = null; - }); - try { - await pb.signIn( - baseUrl: _server.text, - email: _email.text, - password: _password.text, - deviceId: _device.text, - ); - widget.onSignedIn(); - } catch (e) { - setState(() => _error = _friendly(e)); - } finally { - if (mounted) setState(() => _busy = false); - } - } - - String _friendly(Object e) { - final String s = e.toString(); - if (s.contains('Failed host lookup') || s.contains('SocketException')) { - return 'Cannot reach the server. Check the address and that PocketBase is running.'; - } - if (s.contains('Failed to authenticate')) return 'Invalid email or password.'; - return s; - } - - @override - void dispose() { - _server.dispose(); - _email.dispose(); - _password.dispose(); - _device.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Center( - child: SingleChildScrollView( - padding: const EdgeInsets.all(24), - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 380), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Icon(Icons.flight_takeoff, - size: 56, color: Theme.of(context).colorScheme.primary), - const SizedBox(height: 12), - Text('DJI MSDK Sample', - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headlineSmall), - Text('Sign in to the PocketBase backend', - textAlign: TextAlign.center, - style: TextStyle(color: Colors.grey.shade600)), - const SizedBox(height: 24), - TextField( - controller: _server, - keyboardType: TextInputType.url, - decoration: const InputDecoration( - labelText: 'Server', hintText: '10.2.1.101:8090', - border: OutlineInputBorder(), prefixIcon: Icon(Icons.dns), - ), - ), - const SizedBox(height: 12), - TextField( - controller: _email, - keyboardType: TextInputType.emailAddress, - autofillHints: const [AutofillHints.username], - decoration: const InputDecoration( - labelText: 'Email', border: OutlineInputBorder(), - prefixIcon: Icon(Icons.person), - ), - ), - const SizedBox(height: 12), - TextField( - controller: _password, - obscureText: true, - autofillHints: const [AutofillHints.password], - onSubmitted: (_) => _busy ? null : _signIn(), - decoration: const InputDecoration( - labelText: 'Password', border: OutlineInputBorder(), - prefixIcon: Icon(Icons.lock), - ), - ), - const SizedBox(height: 12), - TextField( - controller: _device, - decoration: const InputDecoration( - labelText: 'Device id', border: OutlineInputBorder(), - prefixIcon: Icon(Icons.smartphone), - ), - ), - if (_error != null) ...[ - const SizedBox(height: 12), - Text(_error!, style: const TextStyle(color: Colors.red)), - ], - const SizedBox(height: 20), - FilledButton( - onPressed: _busy ? null : _signIn, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 12), - child: _busy - ? const SizedBox( - height: 20, width: 20, - child: CircularProgressIndicator(strokeWidth: 2)) - : const Text('Sign in'), - ), - ), - ], - ), - ), - ), - ), - ); - } -} diff --git a/Fly App/backup_pocketbase_app/main_pocketbase.dart b/Fly App/backup_pocketbase_app/main_pocketbase.dart deleted file mode 100644 index b203958..0000000 --- a/Fly App/backup_pocketbase_app/main_pocketbase.dart +++ /dev/null @@ -1,424 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'dji_service.dart'; -import 'login_page.dart'; -import 'pb_service.dart'; - -Future main() async { - WidgetsFlutterBinding.ensureInitialized(); - await pb.init(); - runApp(const DjiSampleApp()); -} - -class DjiSampleApp extends StatelessWidget { - const DjiSampleApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'DJI MSDK Sample', - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF1565C0)), - useMaterial3: true, - ), - home: const AuthGate(), - ); - } -} - -/// Shows the login screen until the user is authenticated with PocketBase. -class AuthGate extends StatefulWidget { - const AuthGate({super.key}); - - @override - State createState() => _AuthGateState(); -} - -class _AuthGateState extends State { - StreamSubscription? _sub; - - @override - void initState() { - super.initState(); - _sub = pb.status.listen((_) { - if (mounted) setState(() {}); - }); - } - - @override - void dispose() { - _sub?.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - if (pb.isAuthed) { - return const HomePage(); - } - return LoginPage(onSignedIn: () => setState(() {})); - } -} - -enum RegistrationState { idle, registering, success, failed } - -class HomePage extends StatefulWidget { - const HomePage({super.key}); - - @override - State createState() => _HomePageState(); -} - -class _HomePageState extends State { - final DjiService _dji = DjiService(); - StreamSubscription>? _sub; - StreamSubscription? _backendSub; - BackendStatus _backend = BackendStatus.online; - - String _sdkVersion = '…'; - RegistrationState _registration = RegistrationState.idle; - String? _registrationError; - - bool _connected = false; - String? _model; - - int? _satellites; - bool? _isFlying; - String? _flightMode; - double? _altitude; - double? _latitude; - double? _longitude; - int? _batteryPercent; - - @override - void initState() { - super.initState(); - _backend = pb.currentStatus; - // Route server interactions through PocketBase. - pb.onCommand = _handleServerCommand; - pb.stateProvider = _currentState; - _backendSub = pb.status.listen((BackendStatus s) { - if (mounted) setState(() => _backend = s); - }); - _init(); - } - - Future _init() async { - _sub = _dji.events().listen(_onEvent, onError: (Object e) { - _snack('Event channel error: $e'); - }); - try { - final String version = await _dji.getSdkVersion(); - if (mounted) setState(() => _sdkVersion = version); - } catch (_) { - if (mounted) setState(() => _sdkVersion = 'unavailable'); - } - } - - void _onEvent(Map event) { - if (!mounted) return; - // Persist every event to PocketBase (telemetry is throttled inside). - pb.onEvent(event); - switch (event['type'] as String?) { - case 'registration': - setState(() { - switch (event['state'] as String?) { - case 'registering': - _registration = RegistrationState.registering; - _registrationError = null; - break; - case 'success': - _registration = RegistrationState.success; - break; - case 'failed': - _registration = RegistrationState.failed; - _registrationError = event['error'] as String?; - break; - } - }); - break; - case 'connection': - setState(() { - _connected = event['connected'] as bool? ?? false; - _model = event['model'] as String?; - if (!_connected) _clearTelemetry(); - }); - break; - case 'telemetry': - setState(() { - _satellites = event['satelliteCount'] as int?; - _isFlying = event['isFlying'] as bool?; - _flightMode = event['flightMode'] as String?; - _altitude = (event['altitude'] as num?)?.toDouble(); - _latitude = (event['latitude'] as num?)?.toDouble(); - _longitude = (event['longitude'] as num?)?.toDouble(); - }); - break; - case 'battery': - setState(() => _batteryPercent = event['percent'] as int?); - break; - } - } - - void _clearTelemetry() { - _satellites = null; - _isFlying = null; - _flightMode = null; - _altitude = null; - _latitude = null; - _longitude = null; - _batteryPercent = null; - } - - /// Current app state shared with PocketBase for the device presence record. - Map _currentState() => { - 'connected': _connected, - 'model': _model ?? '', - 'registration': _registrationWire(), - }; - - String _registrationWire() { - switch (_registration) { - case RegistrationState.success: - return 'success'; - case RegistrationState.registering: - return 'registering'; - case RegistrationState.failed: - return 'failed'; - case RegistrationState.idle: - return 'not registered'; - } - } - - Future _register() async { - try { - await _dji.registerApp(); - } catch (e) { - _snack('registerApp failed: $e'); - } - } - - Future _connect() async { - try { - final bool started = await _dji.startConnection(); - _snack(started ? 'Scanning for product…' : 'Could not start connection'); - } catch (e) { - _snack('startConnection failed: $e'); - } - } - - void _handleServerCommand(String command, Map payload) { - switch (command) { - case 'registerApp': - _register(); - break; - case 'startConnection': - _connect(); - break; - case 'stopConnection': - _dji.stopConnection(); - break; - default: - _snack('Unknown server command: $command'); - return; - } - _snack('Server command: $command'); - } - - Future _logout() async { - await pb.signOut(); - if (mounted) { - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: (_) => const AuthGate(), - ), - (Route route) => false, - ); - } - } - - void _snack(String msg) { - if (!mounted) return; - ScaffoldMessenger.of(context) - ..clearSnackBars() - ..showSnackBar(SnackBar(content: Text(msg))); - } - - @override - void dispose() { - _sub?.cancel(); - _backendSub?.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('DJI MSDK Sample'), - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - actions: [ - IconButton( - tooltip: 'Log out', - onPressed: _logout, - icon: const Icon(Icons.logout), - ), - ], - ), - body: ListView( - padding: const EdgeInsets.all(16), - children: [ - _backendCard(), - const SizedBox(height: 12), - _registrationCard(), - const SizedBox(height: 12), - _connectionCard(), - const SizedBox(height: 12), - _telemetryCard(), - ], - ), - ); - } - - Widget _backendCard() { - final (Color color, String label) = switch (_backend) { - BackendStatus.online => (Colors.green, 'Streaming to PocketBase'), - BackendStatus.signingIn => (Colors.orange, 'Connecting…'), - BackendStatus.error => (Colors.red, 'Backend error'), - BackendStatus.signedOut => (Colors.grey, 'Signed out'), - }; - return Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - Icon(Icons.cloud_done, color: color), - const SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(label, style: Theme.of(context).textTheme.titleMedium), - const SizedBox(height: 2), - Text('${pb.userEmail} · device "${pb.deviceId}"', - style: const TextStyle(color: Colors.black54, fontSize: 12)), - ], - ), - ), - TextButton.icon( - onPressed: _logout, - icon: const Icon(Icons.logout), - label: const Text('Log out'), - ), - ], - ), - ), - ); - } - - Widget _registrationCard() { - final (Color color, IconData icon, String label) = switch (_registration) { - RegistrationState.idle => (Colors.grey, Icons.help_outline, 'Not registered'), - RegistrationState.registering => (Colors.orange, Icons.sync, 'Registering…'), - RegistrationState.success => (Colors.green, Icons.verified, 'Registered'), - RegistrationState.failed => (Colors.red, Icons.error_outline, 'Registration failed'), - }; - - return Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon(icon, color: color), - const SizedBox(width: 8), - Text(label, style: Theme.of(context).textTheme.titleMedium), - ], - ), - const SizedBox(height: 4), - Text('SDK version: $_sdkVersion'), - if (_registrationError != null) ...[ - const SizedBox(height: 4), - Text(_registrationError!, style: const TextStyle(color: Colors.red)), - ], - const SizedBox(height: 12), - FilledButton.icon( - onPressed: _registration == RegistrationState.registering ? null : _register, - icon: const Icon(Icons.app_registration), - label: const Text('Register app'), - ), - ], - ), - ), - ); - } - - Widget _connectionCard() { - return Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon(_connected ? Icons.link : Icons.link_off, - color: _connected ? Colors.green : Colors.grey), - const SizedBox(width: 8), - Text(_connected ? 'Product connected' : 'No product', - style: Theme.of(context).textTheme.titleMedium), - ], - ), - const SizedBox(height: 4), - Text('Model: ${_model ?? '—'}'), - const SizedBox(height: 12), - OutlinedButton.icon( - onPressed: _registration == RegistrationState.success ? _connect : null, - icon: const Icon(Icons.usb), - label: const Text('Connect to product'), - ), - ], - ), - ), - ); - } - - Widget _telemetryCard() { - return Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Telemetry', style: Theme.of(context).textTheme.titleMedium), - const Divider(), - _row('Battery', _batteryPercent == null ? '—' : '$_batteryPercent%'), - _row('GPS satellites', _satellites?.toString() ?? '—'), - _row('Flight mode', _flightMode ?? '—'), - _row('Flying', _isFlying == null ? '—' : (_isFlying! ? 'yes' : 'no')), - _row('Altitude', _altitude == null ? '—' : '${_altitude!.toStringAsFixed(1)} m'), - _row('Latitude', _latitude?.toStringAsFixed(6) ?? '—'), - _row('Longitude', _longitude?.toStringAsFixed(6) ?? '—'), - ], - ), - ), - ); - } - - Widget _row(String label, String value) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(label, style: const TextStyle(color: Colors.black54)), - Text(value, style: const TextStyle(fontWeight: FontWeight.w600)), - ], - ), - ); - } -} diff --git a/Fly App/backup_pocketbase_app/pb_service.dart b/Fly App/backup_pocketbase_app/pb_service.dart deleted file mode 100644 index b6dd9c2..0000000 --- a/Fly App/backup_pocketbase_app/pb_service.dart +++ /dev/null @@ -1,230 +0,0 @@ -import 'dart:async'; - -import 'package:pocketbase/pocketbase.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -enum BackendStatus { signedOut, signingIn, online, error } - -/// PocketBase-backed service: authentication, telemetry persistence, device -/// presence, and inbound command subscription. A single global [pb] instance is -/// shared across the app. -class PbService { - PocketBase? _pb; - SharedPreferences? _prefs; - String _deviceId = 'phone'; - - Timer? _heartbeat; - String? _devicesRecordId; - UnsubscribeFunc? _cmdUnsub; - DateTime _lastTelemetry = DateTime.fromMillisecondsSinceEpoch(0); - - /// Called when a command record targeting this device is created. - void Function(String command, Map payload)? onCommand; - - /// Returns the app's current state for the device presence record. - /// Expected keys: connected (bool), model (String), registration (String). - Map Function()? stateProvider; - - final StreamController _statusController = - StreamController.broadcast(); - Stream get status => _statusController.stream; - - BackendStatus _status = BackendStatus.signedOut; - BackendStatus get currentStatus => _status; - - bool get isAuthed => _pb?.authStore.isValid ?? false; - String get userEmail => _pb?.authStore.record?.getStringValue('email') ?? ''; - String get deviceId => _deviceId; - String get baseUrl => _pb?.baseURL ?? ''; - - void _setStatus(BackendStatus s) { - _status = s; - if (!_statusController.isClosed) _statusController.add(s); - } - - /// Restores a persisted session (if any) on app start. - Future init() async { - _prefs = await SharedPreferences.getInstance(); - _deviceId = _prefs!.getString('pb_device') ?? 'phone'; - final String? url = _prefs!.getString('pb_url'); - final String? auth = _prefs!.getString('pb_auth'); - if (url != null && auth != null && auth.isNotEmpty) { - _pb = _build(url, auth); - if (_pb!.authStore.isValid) { - _setStatus(BackendStatus.online); - await _afterAuth(); - } - } - } - - PocketBase _build(String url, String? initialAuth) { - final AsyncAuthStore store = AsyncAuthStore( - save: (String data) async => _prefs?.setString('pb_auth', data), - clear: () async => _prefs?.remove('pb_auth'), - initial: initialAuth, - ); - return PocketBase(url, authStore: store); - } - - String get defaultBaseUrl => _prefs?.getString('pb_url') ?? 'http://10.2.1.101:8090'; - - Future signIn({ - required String baseUrl, - required String email, - required String password, - String deviceId = 'phone', - }) async { - _deviceId = deviceId.trim().isEmpty ? 'phone' : deviceId.trim(); - final String url = _normalize(baseUrl); - _setStatus(BackendStatus.signingIn); - try { - final PocketBase pb = _build(url, null); - await pb.collection('users').authWithPassword(email.trim(), password); - _pb = pb; - await _prefs?.setString('pb_url', url); - await _prefs?.setString('pb_device', _deviceId); - _setStatus(BackendStatus.online); - await _afterAuth(); - } catch (e) { - _setStatus(BackendStatus.error); - rethrow; - } - } - - Future signOut() async { - _heartbeat?.cancel(); - _heartbeat = null; - try { - await _upsertDevice(online: false); - } catch (_) {} - try { - await _cmdUnsub?.call(); - } catch (_) {} - _cmdUnsub = null; - _devicesRecordId = null; - _pb?.authStore.clear(); - _setStatus(BackendStatus.signedOut); - } - - Future _afterAuth() async { - await _upsertDevice(online: true); - _startHeartbeat(); - await _subscribeCommands(); - } - - String _normalize(String url) { - String u = url.trim(); - if (!u.startsWith('http://') && !u.startsWith('https://')) u = 'http://$u'; - return u; - } - - // ── Telemetry ingestion ──────────────────────────────────────────────────── - - /// Feed an event from `DjiService.events()`. - void onEvent(Map event) { - final PocketBase? pb = _pb; - if (pb == null || !pb.authStore.isValid) return; - final String? type = event['type'] as String?; - - if (type == 'telemetry') { - final DateTime now = DateTime.now(); - if (now.difference(_lastTelemetry) < const Duration(milliseconds: 500)) return; - _lastTelemetry = now; - } - - _appendEvent(type ?? 'event', event); - - // Reflect registration/connection promptly in the presence record. - if (type == 'registration' || type == 'connection') { - _upsertDevice(online: true); - } - } - - Future _appendEvent(String kind, Map event) async { - final PocketBase? pb = _pb; - if (pb == null) return; - try { - await pb.collection('telemetry').create(body: { - 'device': _deviceId, - 'kind': kind, - 'payload': event, - 'owner': pb.authStore.record?.id, - }); - } catch (_) { - // best-effort; drop on transient failure - } - } - - // ── Device presence ───────────────────────────────────────────────────────── - - void _startHeartbeat() { - _heartbeat?.cancel(); - _heartbeat = Timer.periodic(const Duration(seconds: 6), (_) => _upsertDevice(online: true)); - } - - Future _upsertDevice({required bool online}) async { - final PocketBase? pb = _pb; - if (pb == null) return; - final Map st = stateProvider?.call() ?? {}; - final Map body = { - 'device': _deviceId, - 'online': online, - 'connected': st['connected'] ?? false, - 'model': st['model'] ?? '', - 'registration': st['registration'] ?? '', - 'lastSeen': DateTime.now().millisecondsSinceEpoch ~/ 1000, - 'owner': pb.authStore.record?.id, - }; - try { - if (_devicesRecordId == null) { - try { - final RecordModel existing = - await pb.collection('devices').getFirstListItem('device="$_deviceId"'); - _devicesRecordId = existing.id; - } catch (_) { - // none yet - } - } - if (_devicesRecordId == null) { - final RecordModel rec = await pb.collection('devices').create(body: body); - _devicesRecordId = rec.id; - } else { - await pb.collection('devices').update(_devicesRecordId!, body: body); - } - } catch (_) { - _devicesRecordId = null; // record may have been removed; recreate next tick - } - } - - // ── Commands ──────────────────────────────────────────────────────────────── - - Future _subscribeCommands() async { - final PocketBase? pb = _pb; - if (pb == null) return; - try { - await _cmdUnsub?.call(); - } catch (_) {} - _cmdUnsub = await pb.collection('commands').subscribe( - '*', - (RecordSubscriptionEvent e) { - if (e.action != 'create') return; - final RecordModel? rec = e.record; - if (rec == null) return; - if (rec.getStringValue('device') != _deviceId) return; - final String cmd = rec.getStringValue('command'); - final Map payload = - (rec.get?>('payload') ?? {}); - if (cmd.isNotEmpty) onCommand?.call(cmd, payload); - }, - filter: 'device="$_deviceId"', - ); - } - - void dispose() { - _heartbeat?.cancel(); - _statusController.close(); - } -} - -/// App-wide PocketBase service instance. -final PbService pb = PbService(); diff --git a/Fly App/backup_pocketbase_app/pubspec_pocketbase.yaml b/Fly App/backup_pocketbase_app/pubspec_pocketbase.yaml deleted file mode 100644 index 962387a..0000000 --- a/Fly App/backup_pocketbase_app/pubspec_pocketbase.yaml +++ /dev/null @@ -1,94 +0,0 @@ -name: pilotvault_fly -description: "DJI Mobile SDK V4 sample app built with Flutter" -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. -version: 1.0.0+1 - -environment: - sdk: ^3.6.2 - -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. -dependencies: - flutter: - sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.8 - - # PocketBase backend client (auth, records, realtime). - pocketbase: ^0.22.0 - # Persists the auth token across app launches. - shared_preferences: ^2.3.2 - -dev_dependencies: - flutter_test: - sdk: flutter - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^5.0.0 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. -flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/to/resolution-aware-images - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/to/asset-from-package - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/to/font-from-package