diff --git a/frontend/lib/ui/artefact_page/artefact_page.dart b/frontend/lib/ui/artefact_page/artefact_page.dart index 445c560b..3dfbe800 100644 --- a/frontend/lib/ui/artefact_page/artefact_page.dart +++ b/frontend/lib/ui/artefact_page/artefact_page.dart @@ -1,48 +1,42 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:yaru/widgets.dart'; import '../../providers/artefact.dart'; +import '../blocking_provider_preloader.dart'; import '../spacing.dart'; import 'artefact_page_body.dart'; import 'artefact_page_header.dart'; import 'artefact_page_side.dart'; -class ArtefactPage extends ConsumerWidget { +class ArtefactPage extends StatelessWidget { const ArtefactPage({super.key, required this.artefactId}); final int artefactId; @override - Widget build(BuildContext context, WidgetRef ref) { - final artefact = ref.watch(artefactProvider(artefactId)); + Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only( left: Spacing.pageHorizontalPadding, right: Spacing.pageHorizontalPadding, top: Spacing.level5, ), - child: artefact.when( - data: (artefact) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ArtefactPageHeader(artefact: artefact), - const SizedBox(height: Spacing.level4), - Expanded( - child: Row( - children: [ - ArtefactPageSide(artefact: artefact), - Expanded(child: ArtefactPageBody(artefact: artefact)), - ], - ), + child: BlockingProviderPreloader( + provider: artefactProvider(artefactId), + builder: (_, artefact) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ArtefactPageHeader(artefact: artefact), + const SizedBox(height: Spacing.level4), + Expanded( + child: Row( + children: [ + ArtefactPageSide(artefact: artefact), + Expanded(child: ArtefactPageBody(artefact: artefact)), + ], ), - ], - ); - }, - error: (e, stack) => - Center(child: Text('Error:\n$e\nStackTrace:\n$stack')), - loading: () => const Center(child: YaruCircularProgressIndicator()), + ), + ], + ), ), ); } diff --git a/frontend/lib/ui/artefact_page/artefact_page_side.dart b/frontend/lib/ui/artefact_page/artefact_page_side.dart index 1fd07f76..34207161 100644 --- a/frontend/lib/ui/artefact_page/artefact_page_side.dart +++ b/frontend/lib/ui/artefact_page/artefact_page_side.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:yaru/widgets.dart'; import '../../models/artefact.dart'; import '../../providers/artefact_builds.dart'; +import '../blocking_provider_preloader.dart'; import '../page_filters/page_filters.dart'; import '../spacing.dart'; import 'artefact_page_info_section.dart'; @@ -37,20 +36,12 @@ class _ArtefactPageSideFilters extends StatelessWidget { @override Widget build(BuildContext context) { - return Consumer( - builder: (context, ref, child) { - final artefactBuilds = ref.watch(artefactBuildsProvider(artefact.id)); - - return artefactBuilds.when( - loading: () => const YaruCircularProgressIndicator(), - error: (e, stack) => - Center(child: Text('Error:\n$e\nStackTrace:\n$stack')), - data: (artefactBuilds) => const PageFiltersView( - searchHint: 'Search by environment name', - width: double.infinity, - ), - ); - }, + return BlockingProviderPreloader( + provider: artefactBuildsProvider(artefact.id), + builder: (_, artefactBuilds) => const PageFiltersView( + searchHint: 'Search by environment name', + width: double.infinity, + ), ); } } diff --git a/frontend/lib/ui/artefact_page/test_event_log_expandable.dart b/frontend/lib/ui/artefact_page/test_event_log_expandable.dart index 5eef3254..44080761 100644 --- a/frontend/lib/ui/artefact_page/test_event_log_expandable.dart +++ b/frontend/lib/ui/artefact_page/test_event_log_expandable.dart @@ -1,11 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:yaru/yaru.dart'; import '../../providers/test_events.dart'; -import '../expandable.dart'; +import '../blocking_provider_preloader.dart'; -class TestEventLogExpandable extends ConsumerWidget { +class TestEventLogExpandable extends StatelessWidget { const TestEventLogExpandable({ super.key, required this.testExecutionId, @@ -16,66 +14,57 @@ class TestEventLogExpandable extends ConsumerWidget { final bool initiallyExpanded; @override - Widget build(BuildContext context, WidgetRef ref) { - final testEvents = ref.watch(testEventsProvider(testExecutionId)); - - return Expandable( - title: const Text('Event Log'), - initiallyExpanded: initiallyExpanded, - children: [ - testEvents.when( - loading: () => const Center(child: YaruCircularProgressIndicator()), - error: (error, stackTrace) => Center(child: Text('Error: $error')), - data: (testEvents) => DataTable( - columns: const [ - DataColumn( - label: Expanded( - child: Text( - 'Event Name', - style: TextStyle(fontStyle: FontStyle.italic), - ), - ), + Widget build(BuildContext context) { + return BlockingProviderPreloader( + provider: testEventsProvider(testExecutionId), + builder: (_, testEvents) => DataTable( + columns: const [ + DataColumn( + label: Expanded( + child: Text( + 'Event Name', + style: TextStyle(fontStyle: FontStyle.italic), ), - DataColumn( - label: Expanded( - child: Text( - 'Timestamp', - style: TextStyle(fontStyle: FontStyle.italic), - ), - ), + ), + ), + DataColumn( + label: Expanded( + child: Text( + 'Timestamp', + style: TextStyle(fontStyle: FontStyle.italic), ), - DataColumn( - label: Expanded( - child: Text( - 'Detail', - style: TextStyle(fontStyle: FontStyle.italic), - ), - ), + ), + ), + DataColumn( + label: Expanded( + child: Text( + 'Detail', + style: TextStyle(fontStyle: FontStyle.italic), ), - ], - rows: testEvents - .map( - (testEvent) => DataRow( - cells: [ - DataCell(Text(testEvent.eventName)), - DataCell(Text(testEvent.timestamp)), - DataCell( - Tooltip( - message: testEvent.detail, - child: Text( - testEvent.detail, - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ), + ), + ), + ], + rows: testEvents + .map( + (testEvent) => DataRow( + cells: [ + DataCell(Text(testEvent.eventName)), + DataCell(Text(testEvent.timestamp)), + DataCell( + Tooltip( + message: testEvent.detail, + child: Text( + testEvent.detail, + overflow: TextOverflow.ellipsis, + maxLines: 1, ), - ], + ), ), - ) - .toList(), - ), - ), - ], + ], + ), + ) + .toList(), + ), ); } } diff --git a/frontend/lib/ui/artefact_page/test_result_filter_expandable.dart b/frontend/lib/ui/artefact_page/test_result_filter_expandable.dart index cf83643f..2307cdab 100644 --- a/frontend/lib/ui/artefact_page/test_result_filter_expandable.dart +++ b/frontend/lib/ui/artefact_page/test_result_filter_expandable.dart @@ -1,14 +1,14 @@ import 'package:dartx/dartx.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:yaru/yaru.dart'; import '../../models/test_result.dart'; import '../../providers/test_results.dart'; +import '../blocking_provider_preloader.dart'; import '../expandable.dart'; import 'test_result_expandable.dart'; -class TestResultsFilterExpandable extends ConsumerWidget { +class TestResultsFilterExpandable extends StatelessWidget { const TestResultsFilterExpandable({ super.key, required this.statusToFilterBy, @@ -19,9 +19,7 @@ class TestResultsFilterExpandable extends ConsumerWidget { final int testExecutionId; @override - Widget build(BuildContext context, WidgetRef ref) { - final testResults = ref.watch(testResultsProvider(testExecutionId)); - + Widget build(BuildContext context) { Color? fontColor; if (statusToFilterBy == TestResultStatus.failed) { fontColor = YaruColors.red; @@ -32,10 +30,9 @@ class TestResultsFilterExpandable extends ConsumerWidget { final headerStyle = Theme.of(context).textTheme.titleMedium?.apply(color: fontColor); - return testResults.when( - loading: () => const Center(child: YaruCircularProgressIndicator()), - error: (error, stackTrace) => Center(child: Text('Error: $error')), - data: (testResults) { + return BlockingProviderPreloader( + provider: testResultsProvider(testExecutionId), + builder: (_, testResults) { final filteredTestResults = testResults .filter((testResult) => testResult.status == statusToFilterBy) .toList(); diff --git a/frontend/lib/ui/blocking_provider_preloader.dart b/frontend/lib/ui/blocking_provider_preloader.dart new file mode 100644 index 00000000..7a16e2ff --- /dev/null +++ b/frontend/lib/ui/blocking_provider_preloader.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:yaru/yaru.dart'; + +class BlockingProviderPreloader extends ConsumerWidget { + const BlockingProviderPreloader({ + super.key, + required this.provider, + required this.builder, + }); + + final ProviderListenable> provider; + final Widget Function(BuildContext context, T value) builder; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final value = ref.watch(provider); + + return value.when( + data: (value) => builder(context, value), + error: (e, stack) => + Center(child: Text('Error:\n$e\nStackTrace:\n$stack')), + loading: () => const Center(child: YaruCircularProgressIndicator()), + ); + } +} diff --git a/frontend/lib/ui/dashboard/dashboard.dart b/frontend/lib/ui/dashboard/dashboard.dart index 916db271..dd457c2e 100644 --- a/frontend/lib/ui/dashboard/dashboard.dart +++ b/frontend/lib/ui/dashboard/dashboard.dart @@ -6,6 +6,7 @@ import 'package:yaru/widgets.dart'; import '../../providers/family_artefacts.dart'; import '../../providers/artefact_side_filters_visibility.dart'; import '../../routing.dart'; +import '../blocking_provider_preloader.dart'; import '../page_filters/page_filters.dart'; import '../spacing.dart'; import 'dashboard_body/dashboard_body.dart'; @@ -35,8 +36,9 @@ class Dashboard extends ConsumerWidget { ), ), Expanded( - child: _ArtefactsLoader( - child: Row( + child: BlockingProviderPreloader( + provider: familyArtefactsProvider(family), + builder: (_, __) => Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ YaruOptionButton( @@ -61,22 +63,3 @@ class Dashboard extends ConsumerWidget { ); } } - -class _ArtefactsLoader extends ConsumerWidget { - const _ArtefactsLoader({required this.child}); - - final Widget child; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final family = AppRoutes.familyFromUri(AppRoutes.uriFromContext(context)); - final artefacts = ref.watch(familyArtefactsProvider(family)); - - return artefacts.when( - data: (_) => child, - error: (e, stack) => - Center(child: Text('Error:\n$e\nStackTrace:\n$stack')), - loading: () => const Center(child: YaruCircularProgressIndicator()), - ); - } -} diff --git a/frontend/lib/ui/dashboard/dashboard_body/dashboard_body.dart b/frontend/lib/ui/dashboard/dashboard_body/dashboard_body.dart index d00e4c86..a3e883b4 100644 --- a/frontend/lib/ui/dashboard/dashboard_body/dashboard_body.dart +++ b/frontend/lib/ui/dashboard/dashboard_body/dashboard_body.dart @@ -1,27 +1,24 @@ import 'package:flutter/widgets.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:yaru/widgets.dart'; import '../../../models/family_name.dart'; import '../../../models/view_modes.dart'; import '../../../providers/view_mode.dart'; import '../../../routing.dart'; +import '../../blocking_provider_preloader.dart'; import 'artefacts_columns_view.dart'; import 'artefacts_list_view/artefacts_list_view.dart'; -class DashboardBody extends ConsumerWidget { +class DashboardBody extends StatelessWidget { const DashboardBody({super.key}); @override - Widget build(BuildContext context, WidgetRef ref) { + Widget build(BuildContext context) { final uri = AppRoutes.uriFromContext(context); final family = AppRoutes.familyFromUri(uri); - final viewMode = ref.watch(viewModeProvider); - return viewMode.when( - loading: () => const YaruCircularProgressIndicator(), - error: (_, __) => const SizedBox.shrink(), - data: (viewMode) => switch ((family, viewMode)) { + return BlockingProviderPreloader( + provider: viewModeProvider, + builder: (_, viewMode) => switch ((family, viewMode)) { (_, ViewModes.dashboard) => const ArtefactsColumnsView(), (FamilyName.snap, ViewModes.list) => const ArtefactsListView.snaps(), (FamilyName.deb, ViewModes.list) => const ArtefactsListView.debs(), diff --git a/frontend/lib/ui/dashboard/dashboard_header/view_mode_toggle.dart b/frontend/lib/ui/dashboard/dashboard_header/view_mode_toggle.dart index 9d3e301c..145dea55 100644 --- a/frontend/lib/ui/dashboard/dashboard_header/view_mode_toggle.dart +++ b/frontend/lib/ui/dashboard/dashboard_header/view_mode_toggle.dart @@ -9,22 +9,20 @@ class ViewModeToggle extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final viewMode = ref.watch(viewModeProvider); + final viewMode = ref.watch(viewModeProvider).value; - return viewMode.when( - loading: () => const SizedBox.shrink(), - error: (_, __) => const SizedBox.shrink(), - data: (viewMode) => ToggleButtons( - isSelected: [ - viewMode == ViewModes.list, - viewMode == ViewModes.dashboard, - ], - children: const [Icon(Icons.list), Icon(Icons.dashboard)], - onPressed: (i) { - final selectedView = [ViewModes.list, ViewModes.dashboard][i]; - ref.watch(viewModeProvider.notifier).set(selectedView); - }, - ), + if (viewMode == null) return const SizedBox.shrink(); + + return ToggleButtons( + isSelected: [ + viewMode == ViewModes.list, + viewMode == ViewModes.dashboard, + ], + children: const [Icon(Icons.list), Icon(Icons.dashboard)], + onPressed: (i) { + final selectedView = [ViewModes.list, ViewModes.dashboard][i]; + ref.watch(viewModeProvider.notifier).set(selectedView); + }, ); } }