diff --git a/.gitignore b/.gitignore index a12c0c5..3ddd9a4 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,17 @@ android/local.properties linux/build/ web/build/ +# Flutter misc +.last_build_id + +# FVM (Flutter Version Management) +.fvm/ +.fvmrc + +# Test coverage +coverage/ +test/.test_coverage.dart + # OS .DS_Store Thumbs.db \ No newline at end of file diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/app.dart b/lib/app.dart index 983973a..26c9a8f 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -23,18 +23,27 @@ class TelemetryApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Telemetry Monitor', - theme: ThemeData( - useMaterial3: true, - colorSchemeSeed: Colors.blueGrey, - ), - home: AppScope( - session: session, - layout: layout, - settings: settings, - exporter: exporter, - child: const TabScaffold(), + return ListenableBuilder( + listenable: settings, + builder: (_, __) => MaterialApp( + title: 'Telemetry Monitor', + theme: ThemeData( + useMaterial3: true, + colorSchemeSeed: Colors.blueGrey, + ), + darkTheme: ThemeData( + useMaterial3: true, + colorSchemeSeed: Colors.blueGrey, + brightness: Brightness.dark, + ), + themeMode: settings.darkMode ? ThemeMode.dark : ThemeMode.light, + home: AppScope( + session: session, + layout: layout, + settings: settings, + exporter: exporter, + child: const TabScaffold(), + ), ), ); } diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 52775d7..4ec8ea1 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -63,6 +63,9 @@ class AppConfig { /// Default mini-log severity filter (Dashboard tab). ERROR + FATAL. static const Set defaultMiniLogSeverities = {4, 5}; + /// Default theme mode on first launch. + static const bool defaultDarkMode = false; + /// Default chart x-window on launch and after Reset View. static const Duration defaultWindowWidth = Duration(seconds: 10); diff --git a/lib/config/settings.dart b/lib/config/settings.dart index 93467cd..83a02a1 100644 --- a/lib/config/settings.dart +++ b/lib/config/settings.dart @@ -19,6 +19,7 @@ class Settings extends ChangeNotifier { required this.reconnectBackoffFactor, required this.miniLogSeverities, required this.statusLookback, + required this.darkMode, }); String wsUrl; @@ -30,6 +31,7 @@ class Settings extends ChangeNotifier { double reconnectBackoffFactor; Set miniLogSeverities; int statusLookback; + bool darkMode; /// Load from shared_preferences with defaults for any missing keys. static Future load({bool isWeb = false}) async { @@ -62,6 +64,8 @@ class Settings extends ChangeNotifier { .toSet(), statusLookback: p.getInt('settings.statusLookback') ?? AppConfig.defaultStatusLookback, + darkMode: + p.getBool('settings.darkMode') ?? AppConfig.defaultDarkMode, ); } @@ -81,6 +85,7 @@ class Settings extends ChangeNotifier { await p.setStringList('settings.miniLogSeverities', miniLogSeverities.map((s) => s.toString()).toList()); await p.setInt('settings.statusLookback', statusLookback); + await p.setBool('settings.darkMode', darkMode); notifyListeners(); } @@ -98,6 +103,7 @@ class Settings extends ChangeNotifier { reconnectBackoffFactor = AppConfig.defaultReconnectBackoffFactor; miniLogSeverities = Set.of(AppConfig.defaultMiniLogSeverities); statusLookback = AppConfig.defaultStatusLookback; + darkMode = AppConfig.defaultDarkMode; notifyListeners(); } @@ -112,6 +118,7 @@ class Settings extends ChangeNotifier { reconnectBackoffFactor = other.reconnectBackoffFactor; miniLogSeverities = Set.of(other.miniLogSeverities); statusLookback = other.statusLookback; + darkMode = other.darkMode; notifyListeners(); } @@ -125,5 +132,6 @@ class Settings extends ChangeNotifier { reconnectBackoffFactor: reconnectBackoffFactor, miniLogSeverities: Set.of(miniLogSeverities), statusLookback: statusLookback, + darkMode: darkMode, ); } \ No newline at end of file diff --git a/lib/decoder/decoder_base.dart b/lib/decoder/decoder_base.dart index af91ea4..0d46018 100644 --- a/lib/decoder/decoder_base.dart +++ b/lib/decoder/decoder_base.dart @@ -11,6 +11,9 @@ abstract class Decoder { /// Stream of decoded envelopes (or batches thereof, flattened to per-envelope). Stream get envelopes; + /// Spin up any background machinery. No-op on the inline (Web) decoder. + Future start(); + /// Push a raw frame for decoding. Returns immediately. void feed(Uint8List frame); diff --git a/lib/decoder/decoder_inline.dart b/lib/decoder/decoder_inline.dart index 79b0491..018bc50 100644 --- a/lib/decoder/decoder_inline.dart +++ b/lib/decoder/decoder_inline.dart @@ -15,6 +15,9 @@ class DecoderInline implements Decoder { @override Stream get envelopes => _out.stream; + @override + Future start() async {} + @override void feed(Uint8List frame) { try { diff --git a/lib/decoder/decoder_isolate.dart b/lib/decoder/decoder_isolate.dart index ff82601..754d340 100644 --- a/lib/decoder/decoder_isolate.dart +++ b/lib/decoder/decoder_isolate.dart @@ -25,6 +25,7 @@ class DecoderIsolate implements Decoder { @override Stream get envelopes => _out.stream; + @override Future start() async { if (_isolate != null) return; _fromIsolate = ReceivePort(); diff --git a/lib/export/csv_exporter_base.dart b/lib/export/csv_exporter_base.dart index df073c0..e5f56fa 100644 --- a/lib/export/csv_exporter_base.dart +++ b/lib/export/csv_exporter_base.dart @@ -14,15 +14,15 @@ class ExportResult { final int bytesWritten; } -/// Builds CSV row strings. Shared by both platform implementations so the -/// row layout is defined exactly once. -class CsvRowBuilder { - CsvRowBuilder(this.layout); +/// Builds rows for the data CSV. Needs a layout to know which channels are +/// enabled and what their column names should be. +class CsvDataRowBuilder { + CsvDataRowBuilder(this.layout); final LayoutController layout; - /// Header row for data CSV. Disabled channels are omitted; pause and the - /// 8 status fields always appear. - String dataHeader() { + /// Header row. Disabled channels are omitted; pause and the 8 status + /// fields always appear. + String header() { final cols = ['timestamp_us']; for (var ch = 1; ch <= 16; ch++) { if (layout.configFor(ch).enabled) { @@ -37,7 +37,7 @@ class CsvRowBuilder { } /// Row for one packet. Missing values are blank cells. - String dataRow(packet) { + String row(packet) { final cells = []; cells.add(packet.hasTimestampUs() ? packet.timestampUs.toString() : ''); for (var ch = 1; ch <= 16; ch++) { @@ -51,16 +51,6 @@ class CsvRowBuilder { return cells.join(','); } - String logHeader() => 'timestamp_us,severity,error_number,description'; - - String logRow(entry) { - final ts = entry.hasTimestampUs() ? entry.timestampUs.toString() : ''; - final sev = entry.hasSeverity() ? entry.severity.name : ''; - final err = entry.hasErrorNumber() ? entry.errorNumber.toString() : ''; - final desc = entry.hasDescription() ? _escape(entry.description) : ''; - return '$ts,$sev,$err,$desc'; - } - static String _channelCell(packet, int ch) { switch (ch) { case 1: return packet.hasCh1() ? packet.ch1.toString() : ''; @@ -99,6 +89,22 @@ class CsvRowBuilder { /// Strip characters that would break CSV column names. static String _safe(String s) => s.replaceAll(RegExp(r'[,\s]+'), '_'); +} + +/// Builds rows for the log CSV. Takes no layout — log output is +/// fully determined by the `LogPacket` itself. +class CsvLogRowBuilder { + const CsvLogRowBuilder(); + + String header() => 'timestamp_us,severity,error_number,description'; + + String row(entry) { + final ts = entry.hasTimestampUs() ? entry.timestampUs.toString() : ''; + final sev = entry.hasSeverity() ? entry.severity.name : ''; + final err = entry.hasErrorNumber() ? entry.errorNumber.toString() : ''; + final desc = entry.hasDescription() ? _escape(entry.description) : ''; + return '$ts,$sev,$err,$desc'; + } /// Escape a free-form field for CSV. Wraps in quotes if needed. static String _escape(String s) { diff --git a/lib/export/csv_exporter_io.dart b/lib/export/csv_exporter_io.dart index b56541e..c1ebcb1 100644 --- a/lib/export/csv_exporter_io.dart +++ b/lib/export/csv_exporter_io.dart @@ -20,16 +20,16 @@ class CsvExporterImpl implements CsvExporter { required LayoutController layout, ProgressCallback? onProgress, }) async { - final builder = CsvRowBuilder(layout); + final builder = CsvDataRowBuilder(layout); final dir = await getApplicationDocumentsDirectory(); final ts = DateTime.now().toIso8601String().replaceAll(':', '-'); final file = File('${dir.path}/telemetry_data_$ts.csv'); final sink = file.openWrite(); - sink.writeln(builder.dataHeader()); + sink.writeln(builder.header()); final total = buffer.length; var written = 0; for (var i = 0; i < total; i++) { - sink.writeln(builder.dataRow(buffer[i])); + sink.writeln(builder.row(buffer[i])); written++; if (written % _chunkRows == 0) { onProgress?.call(written / total); @@ -51,17 +51,16 @@ class CsvExporterImpl implements CsvExporter { required LogBuffer buffer, ProgressCallback? onProgress, }) async { - // Logs don't need a layout to format (no column projection). final dir = await getApplicationDocumentsDirectory(); final ts = DateTime.now().toIso8601String().replaceAll(':', '-'); final file = File('${dir.path}/telemetry_log_$ts.csv'); final sink = file.openWrite(); - final builder = CsvRowBuilder(_NoLayoutPlaceholder()); - sink.writeln(builder.logHeader()); + const builder = CsvLogRowBuilder(); + sink.writeln(builder.header()); final total = buffer.length; var written = 0; for (final entry in buffer.iterate()) { - sink.writeln(builder.logRow(entry)); + sink.writeln(builder.row(entry)); written++; if (written % _chunkRows == 0) { onProgress?.call(written / total); @@ -77,10 +76,3 @@ class CsvExporterImpl implements CsvExporter { ); } } - -/// CsvRowBuilder.logRow doesn't actually need a layout. We pass a placeholder -/// so we don't have to plumb a real one through for log-only exports. -class _NoLayoutPlaceholder implements LayoutController { - @override - dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); -} \ No newline at end of file diff --git a/lib/export/csv_exporter_web.dart b/lib/export/csv_exporter_web.dart index df2a15d..961d902 100644 --- a/lib/export/csv_exporter_web.dart +++ b/lib/export/csv_exporter_web.dart @@ -22,14 +22,14 @@ class CsvExporterImpl implements CsvExporter { required LayoutController layout, ProgressCallback? onProgress, }) async { - final builder = CsvRowBuilder(layout); + final builder = CsvDataRowBuilder(layout); final chunks = []; - chunks.add('${builder.dataHeader()}\n'); + chunks.add('${builder.header()}\n'); final total = buffer.length; final sb = StringBuffer(); var written = 0; for (var i = 0; i < total; i++) { - sb.writeln(builder.dataRow(buffer[i])); + sb.writeln(builder.row(buffer[i])); written++; if (written % _chunkRows == 0) { chunks.add(sb.toString()); @@ -53,14 +53,14 @@ class CsvExporterImpl implements CsvExporter { required LogBuffer buffer, ProgressCallback? onProgress, }) async { - final builder = CsvRowBuilder(_NoLayoutPlaceholder()); + const builder = CsvLogRowBuilder(); final chunks = []; - chunks.add('${builder.logHeader()}\n'); + chunks.add('${builder.header()}\n'); final total = buffer.length; final sb = StringBuffer(); var written = 0; for (final entry in buffer.iterate()) { - sb.writeln(builder.logRow(entry)); + sb.writeln(builder.row(entry)); written++; if (written % _chunkRows == 0) { chunks.add(sb.toString()); @@ -104,8 +104,3 @@ class CsvExporterImpl implements CsvExporter { html.Url.revokeObjectUrl(url); } } - -class _NoLayoutPlaceholder implements LayoutController { - @override - dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); -} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index f5d00d4..d313189 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,11 +21,7 @@ Future main() async { backoffFactor: settings.reconnectBackoffFactor, ); final decoder = DecoderImpl(batchInterval: settings.decoderBatchInterval); - - // Native isolate decoders need an explicit start. - if (decoder is DecoderIsolate) { - await decoder.start(); - } + await decoder.start(); final session = SessionController( transport: transport, diff --git a/lib/transport/websocket_transport_base.dart b/lib/transport/websocket_transport_base.dart index 4b7836e..f110e46 100644 --- a/lib/transport/websocket_transport_base.dart +++ b/lib/transport/websocket_transport_base.dart @@ -48,7 +48,7 @@ abstract class WebSocketTransportBase { _shouldReconnect = false; _reconnectTimer?.cancel(); _reconnectTimer = null; - await _closePlatform(); + await closePlatform(); _state.value = WsConnectionState.disconnected; } @@ -78,7 +78,7 @@ abstract class WebSocketTransportBase { /// Subclass: close the platform-specific channel if open. @protected - Future _closePlatform(); + Future closePlatform(); Future _openOnce() async { if (_url == null) return; diff --git a/lib/transport/websocket_transport_io.dart b/lib/transport/websocket_transport_io.dart index 054a5e5..b55fa36 100644 --- a/lib/transport/websocket_transport_io.dart +++ b/lib/transport/websocket_transport_io.dart @@ -42,7 +42,7 @@ class WebSocketTransport extends WebSocketTransportBase { } @override - Future _closePlatform() async { + Future closePlatform() async { await _sub?.cancel(); _sub = null; await _channel?.sink.close(WebSocketStatus.normalClosure); diff --git a/lib/transport/websocket_transport_web.dart b/lib/transport/websocket_transport_web.dart index b02d90b..1c9b216 100644 --- a/lib/transport/websocket_transport_web.dart +++ b/lib/transport/websocket_transport_web.dart @@ -38,7 +38,7 @@ class WebSocketTransport extends WebSocketTransportBase { } @override - Future _closePlatform() async { + Future closePlatform() async { await _sub?.cancel(); _sub = null; await _channel?.sink.close(1000); // Normal closure. diff --git a/lib/ui/app_scope.dart b/lib/ui/app_scope.dart index 187b634..b8a19e7 100644 --- a/lib/ui/app_scope.dart +++ b/lib/ui/app_scope.dart @@ -30,6 +30,17 @@ class AppScope extends InheritedWidget { return scope!; } + /// Republish this scope above [child]. Used when opening dialogs, because + /// `showDialog` mounts the dialog in the root Navigator's overlay, which + /// sits above the AppScope in the widget tree. + Widget wrap(Widget child) => AppScope( + session: session, + layout: layout, + settings: settings, + exporter: exporter, + child: child, + ); + @override bool updateShouldNotify(AppScope old) => session != old.session || diff --git a/lib/ui/chart_grid.dart b/lib/ui/chart_grid.dart index 2cd283e..9a85212 100644 --- a/lib/ui/chart_grid.dart +++ b/lib/ui/chart_grid.dart @@ -14,7 +14,6 @@ class ChartGrid extends StatelessWidget { builder: (_, __) { final grid = scope.layout.grid; return GridView.builder( - physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: grid.cols, mainAxisSpacing: 8, diff --git a/lib/ui/chart_painter.dart b/lib/ui/chart_painter.dart index d9fe694..0e28b9b 100644 --- a/lib/ui/chart_painter.dart +++ b/lib/ui/chart_painter.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import '../layout/chart_config.dart'; import '../layout/layout_controller.dart'; -import '../session/decimator.dart'; import '../session/session_controller.dart'; /// Stateless per-frame painter. Reads everything fresh from the controllers diff --git a/lib/ui/chart_widget.dart b/lib/ui/chart_widget.dart index fafa7d2..7738712 100644 --- a/lib/ui/chart_widget.dart +++ b/lib/ui/chart_widget.dart @@ -1,8 +1,8 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import '../layout/chart_config.dart'; -import '../session/view_state.dart'; import 'app_scope.dart'; import 'chart_painter.dart'; diff --git a/lib/ui/dashboard_tab.dart b/lib/ui/dashboard_tab.dart index 5bda0cf..397dcb8 100644 --- a/lib/ui/dashboard_tab.dart +++ b/lib/ui/dashboard_tab.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'app_scope.dart'; import 'chart_grid.dart'; import 'mini_log_panel.dart'; @@ -9,18 +8,13 @@ class DashboardTab extends StatelessWidget { @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(8), + return const Padding( + padding: EdgeInsets.all(8), child: Column( children: [ - const Expanded(flex: 4, child: ChartGrid()), - const SizedBox(height: 8), - Expanded( - flex: 1, - child: MiniLogPanel( - severities: AppScope.of(context).settings.miniLogSeverities, - ), - ), + Expanded(flex: 4, child: ChartGrid()), + SizedBox(height: 8), + Expanded(flex: 1, child: MiniLogPanel()), ], ), ); diff --git a/lib/ui/dialogs/settings_dialog.dart b/lib/ui/dialogs/settings_dialog.dart index cb19265..b313cf6 100644 --- a/lib/ui/dialogs/settings_dialog.dart +++ b/lib/ui/dialogs/settings_dialog.dart @@ -140,6 +140,14 @@ class _SettingsDialogState extends State { hint: '≈ 1 s @ 1 kHz'), _section('Dashboard mini log'), ..._severityCheckboxes(), + _section('Appearance'), + SwitchListTile( + dense: true, + contentPadding: EdgeInsets.zero, + title: const Text('Dark theme'), + value: _draft.darkMode, + onChanged: (v) => setState(() => _draft.darkMode = v), + ), ], ), ), @@ -198,6 +206,7 @@ class _SettingsDialogState extends State { double.tryParse(_backoffCtrl.text) ?? _draft.reconnectBackoffFactor; _draft.statusLookback = int.tryParse(_lookbackCtrl.text) ?? _draft.statusLookback; + // darkMode is edited directly on _draft via the switch. final scope = AppScope.of(context); final urlChanged = scope.settings.wsUrl != _draft.wsUrl; diff --git a/lib/ui/full_log_tab.dart b/lib/ui/full_log_tab.dart index fc2f056..f2ed738 100644 --- a/lib/ui/full_log_tab.dart +++ b/lib/ui/full_log_tab.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import '../proto/messages.pb.dart'; import 'app_scope.dart'; import 'log_list_view.dart'; diff --git a/lib/ui/mini_log_panel.dart b/lib/ui/mini_log_panel.dart index c235c82..2e9fdcf 100644 --- a/lib/ui/mini_log_panel.dart +++ b/lib/ui/mini_log_panel.dart @@ -1,15 +1,13 @@ import 'package:flutter/material.dart'; -import '../proto/messages.pb.dart'; import 'app_scope.dart'; import 'log_list_view.dart'; /// Compact log panel on the Dashboard tab. Filters by severity using the -/// set configured in Settings (default ERROR + FATAL). +/// set configured in Settings (default ERROR + FATAL). Rebuilds when the +/// severity set changes. class MiniLogPanel extends StatelessWidget { - const MiniLogPanel({super.key, required this.severities}); - - final Set severities; + const MiniLogPanel({super.key}); @override Widget build(BuildContext context) { @@ -24,10 +22,10 @@ class MiniLogPanel extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), child: Row( - children: const [ + children: [ Text('Errors & fatals', style: TextStyle( fontWeight: FontWeight.w500, fontSize: 12)), @@ -39,15 +37,21 @@ class MiniLogPanel extends StatelessWidget { ), const Divider(height: 1), Expanded( - child: LogListView( - session: scope.session, - filter: (e) => severities.contains( - e.hasSeverity() ? e.severity.value : 0, - ), + child: ListenableBuilder( + listenable: scope.settings, + builder: (_, __) { + final severities = scope.settings.miniLogSeverities; + return LogListView( + session: scope.session, + filter: (e) => severities.contains( + e.hasSeverity() ? e.severity.value : 0, + ), + ); + }, ), ), ], ), ); } -} \ No newline at end of file +} diff --git a/lib/ui/toolbar.dart b/lib/ui/toolbar.dart index 628b3a8..df79160 100644 --- a/lib/ui/toolbar.dart +++ b/lib/ui/toolbar.dart @@ -57,7 +57,7 @@ class Toolbar extends StatelessWidget { OutlinedButton.icon( onPressed: () => showDialog( context: context, - builder: (_) => const LayoutDialog(), + builder: (_) => scope.wrap(const LayoutDialog()), ), icon: const Icon(Icons.grid_view), label: const Text('Layout'), @@ -66,7 +66,7 @@ class Toolbar extends StatelessWidget { OutlinedButton.icon( onPressed: () => showDialog( context: context, - builder: (_) => const SettingsDialog(), + builder: (_) => scope.wrap(const SettingsDialog()), ), icon: const Icon(Icons.settings), label: const Text('Settings'), diff --git a/linux/.gitignore b/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 0000000..bfc3af6 --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "telemetry_monitor") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.telemetry_monitor") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/linux/flutter/CMakeLists.txt b/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..e71a16d --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/linux/flutter/generated_plugin_registrant.h b/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..be1ee3e --- /dev/null +++ b/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/linux/runner/CMakeLists.txt b/linux/runner/CMakeLists.txt new file mode 100644 index 0000000..e97dabc --- /dev/null +++ b/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/linux/runner/main.cc b/linux/runner/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/linux/runner/my_application.cc b/linux/runner/my_application.cc new file mode 100644 index 0000000..c413fc0 --- /dev/null +++ b/linux/runner/my_application.cc @@ -0,0 +1,148 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView* view) { + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "telemetry_monitor"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "telemetry_monitor"); + } + + gtk_window_set_default_size(window, 1280, 720); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 + // for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), + self); + gtk_widget_realize(GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); +} diff --git a/linux/runner/my_application.h b/linux/runner/my_application.h new file mode 100644 index 0000000..db16367 --- /dev/null +++ b/linux/runner/my_application.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, + my_application, + MY, + APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..3fe8393 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,522 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + 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" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + hooks: + dependency: transitive + description: + name: hooks + sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + jni: + dependency: transitive + description: + name: jni + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.dev" + source: hosted + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" + url: "https://pub.dev" + source: hosted + version: "0.17.6" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + url: "https://pub.dev" + source: hosted + version: "9.3.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + protobuf: + dependency: "direct main" + description: + name: protobuf + sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + protoc_plugin: + dependency: "direct dev" + description: + name: protoc_plugin + sha256: fb0554851c9eca30bd18405fbbfe81e39166d4a2f0e5b770606fd69da3da0b2f + url: "https://pub.dev" + source: hosted + version: "21.1.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.dev" + source: hosted + version: "2.5.5" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 + url: "https://pub.dev" + source: hosted + version: "2.4.23" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + 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: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + 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" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "046d3928e16fa4dc46e8350415661755ab759d9fc97fc21b5ab295f71e4f0499" + url: "https://pub.dev" + source: hosted + version: "15.1.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: "direct main" + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.10.3 <4.0.0" + flutter: ">=3.41.0" diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..7a938a1 --- /dev/null +++ b/web/index.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + telemetry_monitor + + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..bab9ae3 --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "telemetry_monitor", + "short_name": "telemetry_monitor", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +}