From 99ba68a46b498199173ebb954894a33e3987a135 Mon Sep 17 00:00:00 2001 From: NITISH-R-G <225521762+NITISH-R-G@users.noreply.github.com> Date: Fri, 21 Aug 2026 23:07:41 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Optimize=20rendering=20?= =?UTF-8?q?performance=20of=20SosActivityLogScreen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced ListView + Column with CustomScrollView + SliverList.builder to enable list virtualization for the activity history. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- lib/ui/sos_activity_log_screen.dart | 87 +++++---- lib/ui/sos_activity_log_screen.dart.orig | 223 +++++++++++++++++++++++ pubspec.lock | 18 +- 3 files changed, 282 insertions(+), 46 deletions(-) create mode 100644 lib/ui/sos_activity_log_screen.dart.orig diff --git a/lib/ui/sos_activity_log_screen.dart b/lib/ui/sos_activity_log_screen.dart index f3b045d..c0a7309 100644 --- a/lib/ui/sos_activity_log_screen.dart +++ b/lib/ui/sos_activity_log_screen.dart @@ -42,45 +42,58 @@ class SosActivityLogScreen extends ConsumerWidget { ), ], ), - body: ListView( - padding: const EdgeInsets.all(20), - children: [ - _insuranceBanner(), - const SizedBox(height: 16), - if (live.phase != SOSPhase.idle && - live.dispatchChannels.isNotEmpty) ...[ - _sectionTitle('CURRENT SESSION'), - const SizedBox(height: 8), - Text( - live.incidentId ?? '—', - style: const TextStyle( - color: Colors.white38, - fontSize: 11, - fontFamily: 'monospace', - ), + // ⚡ Bolt Optimization: Replace ListView + Column with CustomScrollView + SliverList + // This prevents O(n) rendering of the entire history list + body: CustomScrollView( + slivers: [ + SliverPadding( + padding: const EdgeInsets.only(top: 20, left: 20, right: 20), + sliver: SliverList( + delegate: SliverChildListDelegate([ + _insuranceBanner(), + const SizedBox(height: 16), + if (live.phase != SOSPhase.idle && + live.dispatchChannels.isNotEmpty) ...[ + _sectionTitle('CURRENT SESSION'), + const SizedBox(height: 8), + Text( + live.incidentId ?? '—', + style: const TextStyle( + color: Colors.white38, + fontSize: 11, + fontFamily: 'monospace', + ), + ), + const SizedBox(height: 12), + DispatchStatusPanel(channels: live.dispatchChannels), + const Divider(color: Colors.white12, height: 32), + ], + _sectionTitle('SAVED INCIDENTS'), + const SizedBox(height: 8), + ]), ), - const SizedBox(height: 12), - DispatchStatusPanel(channels: live.dispatchChannels), - const Divider(color: Colors.white12, height: 32), - ], - _sectionTitle('SAVED INCIDENTS'), - const SizedBox(height: 8), - history.when( - data: (items) { - if (items.isEmpty) { - return const Text( - 'No completed incidents stored yet. After an SOS finishes dispatch, a summary is saved here automatically.', - style: TextStyle(color: Colors.white54, height: 1.4), + ), + SliverPadding( + padding: const EdgeInsets.only(left: 20, right: 20, bottom: 20), + sliver: history.when( + data: (items) { + if (items.isEmpty) { + return const SliverToBoxAdapter( + child: Text( + 'No completed incidents stored yet. After an SOS finishes dispatch, a summary is saved here automatically.', + style: TextStyle(color: Colors.white54, height: 1.4), + ), + ); + } + return SliverList.builder( + itemCount: items.length, + itemBuilder: (context, index) => _historyCard(context, items[index]), ); - } - return Column( - children: items.map((e) => _historyCard(context, e)).toList(), - ); - }, - loading: () => const Center(child: CircularProgressIndicator()), - error: (e, _) => Text( - 'Could not load history: $e', - style: const TextStyle(color: Colors.redAccent), + }, + loading: () => const SliverToBoxAdapter(child: Center(child: CircularProgressIndicator())), + error: (e, _) => SliverToBoxAdapter( + child: Text('Could not load history: $e', style: const TextStyle(color: Colors.redAccent)), + ), ), ), ], diff --git a/lib/ui/sos_activity_log_screen.dart.orig b/lib/ui/sos_activity_log_screen.dart.orig new file mode 100644 index 0000000..f3b045d --- /dev/null +++ b/lib/ui/sos_activity_log_screen.dart.orig @@ -0,0 +1,223 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/intl.dart'; + +import '../models/sos_activity_record.dart'; +import '../services/emergency_orchestrator.dart'; +import '../services/sos_activity_log_service.dart'; +import 'dispatch_status_panel.dart'; + +final _activityHistoryProvider = FutureProvider>(( + ref, +) async { + return SosActivityLogService.instance.loadHistory(); +}); + +/// Post-SOS transparency: timestamps, GPS, AI triage, channels, sync — supports insurer / FIR context in India. +class SosActivityLogScreen extends ConsumerWidget { + const SosActivityLogScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final history = ref.watch(_activityHistoryProvider); + final live = ref.watch(emergencyOrchestratorProvider); + + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + title: const Text( + 'ACTIVITY LOG', + style: TextStyle( + fontWeight: FontWeight.w900, + fontSize: 14, + letterSpacing: 1, + ), + ), + backgroundColor: Colors.transparent, + actions: [ + IconButton( + tooltip: 'Refresh', + onPressed: () => ref.invalidate(_activityHistoryProvider), + icon: const Icon(Icons.refresh), + ), + ], + ), + body: ListView( + padding: const EdgeInsets.all(20), + children: [ + _insuranceBanner(), + const SizedBox(height: 16), + if (live.phase != SOSPhase.idle && + live.dispatchChannels.isNotEmpty) ...[ + _sectionTitle('CURRENT SESSION'), + const SizedBox(height: 8), + Text( + live.incidentId ?? '—', + style: const TextStyle( + color: Colors.white38, + fontSize: 11, + fontFamily: 'monospace', + ), + ), + const SizedBox(height: 12), + DispatchStatusPanel(channels: live.dispatchChannels), + const Divider(color: Colors.white12, height: 32), + ], + _sectionTitle('SAVED INCIDENTS'), + const SizedBox(height: 8), + history.when( + data: (items) { + if (items.isEmpty) { + return const Text( + 'No completed incidents stored yet. After an SOS finishes dispatch, a summary is saved here automatically.', + style: TextStyle(color: Colors.white54, height: 1.4), + ); + } + return Column( + children: items.map((e) => _historyCard(context, e)).toList(), + ); + }, + loading: () => const Center(child: CircularProgressIndicator()), + error: (e, _) => Text( + 'Could not load history: $e', + style: const TextStyle(color: Colors.redAccent), + ), + ), + ], + ), + ); + } + + Widget _insuranceBanner() { + return Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: Colors.blue.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.blue.withValues(alpha: 0.35)), + ), + child: const Text( + 'This log shows what the app attempted: GPS coordinates, AI triage, mesh/SMS/cloud steps, ' + 'and sync status. You can show it to insurers or police — it is not proof that emergency ' + 'services answered, only that RoadSOS ran these steps on your device.', + style: TextStyle(color: Colors.white70, fontSize: 13, height: 1.45), + ), + ); + } + + Widget _sectionTitle(String t) { + return Text( + t, + style: TextStyle( + color: Colors.blue.withValues(alpha: 0.75), + fontWeight: FontWeight.w900, + fontSize: 10, + letterSpacing: 1.5, + ), + ); + } + + Widget _historyCard(BuildContext context, SosActivityRecord r) { + final localTime = r.completedAtUtc.toLocal(); + final fmt = DateFormat.yMMMd().add_jm(); + final coarseLat = r.latitude.toStringAsFixed(3); + final coarseLng = r.longitude.toStringAsFixed(3); + return Card( + color: Colors.white.withValues(alpha: 0.05), + margin: const EdgeInsets.only(bottom: 14), + child: Padding( + padding: const EdgeInsets.all(14), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + fmt.format(localTime), + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + 'Incident ${r.incidentId}', + style: const TextStyle( + color: Colors.white38, + fontSize: 11, + fontFamily: 'monospace', + ), + ), + const SizedBox(height: 12), + Text( + 'GPS shared (coarse by default)', + style: TextStyle( + color: Colors.blue.withValues(alpha: 0.8), + fontWeight: FontWeight.w800, + fontSize: 10, + ), + ), + const SizedBox(height: 4), + SelectableText( + '$coarseLat, $coarseLng (±${r.accuracyM.round()} m • ${r.locationSource})', + style: const TextStyle(color: Colors.white70, fontSize: 13), + ), + const SizedBox(height: 6), + Text( + 'Precise coordinates are stored encrypted on-device. If police/insurer requests exact GPS, export via a dedicated signed report (not implemented yet).', + style: const TextStyle( + color: Colors.white38, + fontSize: 11, + height: 1.35, + ), + ), + const SizedBox(height: 12), + Text( + 'AI triage', + style: TextStyle( + color: Colors.blue.withValues(alpha: 0.8), + fontWeight: FontWeight.w800, + fontSize: 10, + ), + ), + const SizedBox(height: 4), + Text( + 'Severity ${r.triageSeverity}/5 • ${r.triageSourceName}${r.isBystander ? ' • bystander report' : ''}', + style: const TextStyle(color: Colors.white70, fontSize: 13), + ), + if (r.requiredServices.isNotEmpty) ...[ + const SizedBox(height: 4), + Text( + 'Services flagged: ${r.requiredServices.join(', ')}', + style: const TextStyle(color: Colors.white54, fontSize: 12), + ), + ], + const SizedBox(height: 12), + Text( + 'Sync / storage', + style: TextStyle( + color: Colors.blue.withValues(alpha: 0.8), + fontWeight: FontWeight.w800, + fontSize: 10, + ), + ), + const SizedBox(height: 4), + Text( + r.syncStatusLine, + style: const TextStyle(color: Colors.white70, fontSize: 13), + ), + const SizedBox(height: 14), + Text( + 'Channels', + style: TextStyle( + color: Colors.blue.withValues(alpha: 0.8), + fontWeight: FontWeight.w800, + fontSize: 10, + ), + ), + const SizedBox(height: 8), + DispatchStatusPanel(channels: r.channels), + ], + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 4689694..060f56a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1249,10 +1249,10 @@ packages: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.18" material_color_utilities: dependency: transitive description: @@ -1265,10 +1265,10 @@ packages: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.0" mgrs_dart: dependency: transitive description: @@ -1878,10 +1878,10 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.9" timezone: dependency: transitive description: @@ -1894,10 +1894,10 @@ packages: dependency: "direct main" description: name: torch_light - sha256: "6a2659cf413d11b838540d15f2a18ead0e7736250cecdacb153cff654beb5c4a" + sha256: a1397443a375c6991151547cb77361085df6cf8aa59999292e683db7385a0d15 url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "1.1.0" typed_data: dependency: transitive description: @@ -2108,4 +2108,4 @@ packages: version: "2.1.0" sdks: dart: ">=3.11.0 <4.0.0" - flutter: ">=3.41.0" + flutter: ">=3.38.4" From bf20e5a374d500a4ac323f46e179c28a16e79993 Mon Sep 17 00:00:00 2001 From: NITISH-R-G <225521762+NITISH-R-G@users.noreply.github.com> Date: Fri, 21 Aug 2026 23:12:14 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Optimize=20Activity=20L?= =?UTF-8?q?og=20rendering=20and=20fix=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced ListView + Column with CustomScrollView + SliverList.builder in SosActivityLogScreen to prevent O(N) rendering. Fixed GitHub Actions CI failure by conditionally applying the github-pages environment in health_dashboard.yml. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- .github/workflows/health_dashboard.yml | 2 +- lib/ui/sos_activity_log_screen.dart | 12 +- lib/ui/sos_activity_log_screen.dart.orig | 223 ----------------------- pubspec.lock | 18 +- 4 files changed, 19 insertions(+), 236 deletions(-) delete mode 100644 lib/ui/sos_activity_log_screen.dart.orig diff --git a/.github/workflows/health_dashboard.yml b/.github/workflows/health_dashboard.yml index 9920871..42669f6 100644 --- a/.github/workflows/health_dashboard.yml +++ b/.github/workflows/health_dashboard.yml @@ -25,7 +25,7 @@ jobs: name: Generate & Deploy Dashboard runs-on: ubuntu-latest environment: - name: github-pages + name: ${{ github.event_name != 'pull_request' && 'github-pages' || '' }} url: ${{ steps.deployment.outputs.page_url }} steps: diff --git a/lib/ui/sos_activity_log_screen.dart b/lib/ui/sos_activity_log_screen.dart index c0a7309..1985eca 100644 --- a/lib/ui/sos_activity_log_screen.dart +++ b/lib/ui/sos_activity_log_screen.dart @@ -87,12 +87,18 @@ class SosActivityLogScreen extends ConsumerWidget { } return SliverList.builder( itemCount: items.length, - itemBuilder: (context, index) => _historyCard(context, items[index]), + itemBuilder: (context, index) => + _historyCard(context, items[index]), ); }, - loading: () => const SliverToBoxAdapter(child: Center(child: CircularProgressIndicator())), + loading: () => const SliverToBoxAdapter( + child: Center(child: CircularProgressIndicator()), + ), error: (e, _) => SliverToBoxAdapter( - child: Text('Could not load history: $e', style: const TextStyle(color: Colors.redAccent)), + child: Text( + 'Could not load history: $e', + style: const TextStyle(color: Colors.redAccent), + ), ), ), ), diff --git a/lib/ui/sos_activity_log_screen.dart.orig b/lib/ui/sos_activity_log_screen.dart.orig deleted file mode 100644 index f3b045d..0000000 --- a/lib/ui/sos_activity_log_screen.dart.orig +++ /dev/null @@ -1,223 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:intl/intl.dart'; - -import '../models/sos_activity_record.dart'; -import '../services/emergency_orchestrator.dart'; -import '../services/sos_activity_log_service.dart'; -import 'dispatch_status_panel.dart'; - -final _activityHistoryProvider = FutureProvider>(( - ref, -) async { - return SosActivityLogService.instance.loadHistory(); -}); - -/// Post-SOS transparency: timestamps, GPS, AI triage, channels, sync — supports insurer / FIR context in India. -class SosActivityLogScreen extends ConsumerWidget { - const SosActivityLogScreen({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final history = ref.watch(_activityHistoryProvider); - final live = ref.watch(emergencyOrchestratorProvider); - - return Scaffold( - backgroundColor: Colors.black, - appBar: AppBar( - title: const Text( - 'ACTIVITY LOG', - style: TextStyle( - fontWeight: FontWeight.w900, - fontSize: 14, - letterSpacing: 1, - ), - ), - backgroundColor: Colors.transparent, - actions: [ - IconButton( - tooltip: 'Refresh', - onPressed: () => ref.invalidate(_activityHistoryProvider), - icon: const Icon(Icons.refresh), - ), - ], - ), - body: ListView( - padding: const EdgeInsets.all(20), - children: [ - _insuranceBanner(), - const SizedBox(height: 16), - if (live.phase != SOSPhase.idle && - live.dispatchChannels.isNotEmpty) ...[ - _sectionTitle('CURRENT SESSION'), - const SizedBox(height: 8), - Text( - live.incidentId ?? '—', - style: const TextStyle( - color: Colors.white38, - fontSize: 11, - fontFamily: 'monospace', - ), - ), - const SizedBox(height: 12), - DispatchStatusPanel(channels: live.dispatchChannels), - const Divider(color: Colors.white12, height: 32), - ], - _sectionTitle('SAVED INCIDENTS'), - const SizedBox(height: 8), - history.when( - data: (items) { - if (items.isEmpty) { - return const Text( - 'No completed incidents stored yet. After an SOS finishes dispatch, a summary is saved here automatically.', - style: TextStyle(color: Colors.white54, height: 1.4), - ); - } - return Column( - children: items.map((e) => _historyCard(context, e)).toList(), - ); - }, - loading: () => const Center(child: CircularProgressIndicator()), - error: (e, _) => Text( - 'Could not load history: $e', - style: const TextStyle(color: Colors.redAccent), - ), - ), - ], - ), - ); - } - - Widget _insuranceBanner() { - return Container( - padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - color: Colors.blue.withValues(alpha: 0.12), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.blue.withValues(alpha: 0.35)), - ), - child: const Text( - 'This log shows what the app attempted: GPS coordinates, AI triage, mesh/SMS/cloud steps, ' - 'and sync status. You can show it to insurers or police — it is not proof that emergency ' - 'services answered, only that RoadSOS ran these steps on your device.', - style: TextStyle(color: Colors.white70, fontSize: 13, height: 1.45), - ), - ); - } - - Widget _sectionTitle(String t) { - return Text( - t, - style: TextStyle( - color: Colors.blue.withValues(alpha: 0.75), - fontWeight: FontWeight.w900, - fontSize: 10, - letterSpacing: 1.5, - ), - ); - } - - Widget _historyCard(BuildContext context, SosActivityRecord r) { - final localTime = r.completedAtUtc.toLocal(); - final fmt = DateFormat.yMMMd().add_jm(); - final coarseLat = r.latitude.toStringAsFixed(3); - final coarseLng = r.longitude.toStringAsFixed(3); - return Card( - color: Colors.white.withValues(alpha: 0.05), - margin: const EdgeInsets.only(bottom: 14), - child: Padding( - padding: const EdgeInsets.all(14), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - fmt.format(localTime), - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 4), - Text( - 'Incident ${r.incidentId}', - style: const TextStyle( - color: Colors.white38, - fontSize: 11, - fontFamily: 'monospace', - ), - ), - const SizedBox(height: 12), - Text( - 'GPS shared (coarse by default)', - style: TextStyle( - color: Colors.blue.withValues(alpha: 0.8), - fontWeight: FontWeight.w800, - fontSize: 10, - ), - ), - const SizedBox(height: 4), - SelectableText( - '$coarseLat, $coarseLng (±${r.accuracyM.round()} m • ${r.locationSource})', - style: const TextStyle(color: Colors.white70, fontSize: 13), - ), - const SizedBox(height: 6), - Text( - 'Precise coordinates are stored encrypted on-device. If police/insurer requests exact GPS, export via a dedicated signed report (not implemented yet).', - style: const TextStyle( - color: Colors.white38, - fontSize: 11, - height: 1.35, - ), - ), - const SizedBox(height: 12), - Text( - 'AI triage', - style: TextStyle( - color: Colors.blue.withValues(alpha: 0.8), - fontWeight: FontWeight.w800, - fontSize: 10, - ), - ), - const SizedBox(height: 4), - Text( - 'Severity ${r.triageSeverity}/5 • ${r.triageSourceName}${r.isBystander ? ' • bystander report' : ''}', - style: const TextStyle(color: Colors.white70, fontSize: 13), - ), - if (r.requiredServices.isNotEmpty) ...[ - const SizedBox(height: 4), - Text( - 'Services flagged: ${r.requiredServices.join(', ')}', - style: const TextStyle(color: Colors.white54, fontSize: 12), - ), - ], - const SizedBox(height: 12), - Text( - 'Sync / storage', - style: TextStyle( - color: Colors.blue.withValues(alpha: 0.8), - fontWeight: FontWeight.w800, - fontSize: 10, - ), - ), - const SizedBox(height: 4), - Text( - r.syncStatusLine, - style: const TextStyle(color: Colors.white70, fontSize: 13), - ), - const SizedBox(height: 14), - Text( - 'Channels', - style: TextStyle( - color: Colors.blue.withValues(alpha: 0.8), - fontWeight: FontWeight.w800, - fontSize: 10, - ), - ), - const SizedBox(height: 8), - DispatchStatusPanel(channels: r.channels), - ], - ), - ), - ); - } -} diff --git a/pubspec.lock b/pubspec.lock index 060f56a..4689694 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1249,10 +1249,10 @@ packages: dependency: transitive description: name: matcher - sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.18" + version: "0.12.19" material_color_utilities: dependency: transitive description: @@ -1265,10 +1265,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" mgrs_dart: dependency: transitive description: @@ -1878,10 +1878,10 @@ packages: dependency: transitive description: name: test_api - sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.9" + version: "0.7.11" timezone: dependency: transitive description: @@ -1894,10 +1894,10 @@ packages: dependency: "direct main" description: name: torch_light - sha256: a1397443a375c6991151547cb77361085df6cf8aa59999292e683db7385a0d15 + sha256: "6a2659cf413d11b838540d15f2a18ead0e7736250cecdacb153cff654beb5c4a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "2.0.0" typed_data: dependency: transitive description: @@ -2108,4 +2108,4 @@ packages: version: "2.1.0" sdks: dart: ">=3.11.0 <4.0.0" - flutter: ">=3.38.4" + flutter: ">=3.41.0"