From 1904e480048516cf8a45081fb2cd9e959ee9766b Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Wed, 12 Jun 2024 08:39:51 +0200 Subject: [PATCH] refactored landing page with initialization errors --- .../presentation/widgets/error_container.dart | 35 +++++++++ .../presentation/widgets/landing/action.dart | 44 +++-------- .../presentation/widgets/landing/content.dart | 78 +++++++++++++++++-- app/lib/presentation/hooks/cached_future.dart | 12 +++ .../presentation/widgets/failure_widget.dart | 7 +- 5 files changed, 134 insertions(+), 42 deletions(-) create mode 100644 app/lib/features/search_browser/presentation/widgets/error_container.dart create mode 100644 app/lib/presentation/hooks/cached_future.dart diff --git a/app/lib/features/search_browser/presentation/widgets/error_container.dart b/app/lib/features/search_browser/presentation/widgets/error_container.dart new file mode 100644 index 00000000..a32a9c69 --- /dev/null +++ b/app/lib/features/search_browser/presentation/widgets/error_container.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +class ErrorContainer extends StatelessWidget { + final Widget content; + + const ErrorContainer({required this.content}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16.0), + decoration: BoxDecoration( + color: theme.colorScheme.errorContainer, + borderRadius: BorderRadius.circular(4.0), + ), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + Icons.warning, + size: 36, + color: theme.colorScheme.onErrorContainer, + ), + ), + Expanded( + child: content, + ), + ], + ), + ); + } +} diff --git a/app/lib/features/search_browser/presentation/widgets/landing/action.dart b/app/lib/features/search_browser/presentation/widgets/landing/action.dart index a1245d8a..983c4b73 100644 --- a/app/lib/features/search_browser/presentation/widgets/landing/action.dart +++ b/app/lib/features/search_browser/presentation/widgets/landing/action.dart @@ -2,6 +2,7 @@ import 'package:bang_navigator/core/routing/routes.dart'; import 'package:bang_navigator/features/search_browser/domain/entities/modes.dart'; import 'package:bang_navigator/features/search_browser/domain/entities/sheet.dart'; import 'package:bang_navigator/features/search_browser/domain/services/create_tab.dart'; +import 'package:bang_navigator/features/search_browser/presentation/widgets/error_container.dart'; import 'package:bang_navigator/features/settings/data/repositories/settings_repository.dart'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; @@ -13,8 +14,6 @@ class LandingAction extends HookConsumerWidget { const LandingAction({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final theme = Theme.of(context); - final sessionTokenAvailable = ref.watch( settingsRepositoryProvider.select( (value) => value.valueOrNull?.kagiSession?.isNotEmpty ?? false, @@ -29,36 +28,17 @@ class LandingAction extends HookConsumerWidget { return Visibility( visible: sessionTokenAvailable, - replacement: Container( - margin: const EdgeInsets.symmetric(horizontal: 16.0), - decoration: BoxDecoration( - color: theme.colorScheme.errorContainer, - borderRadius: BorderRadius.circular(4.0), - ), - child: Row( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Icon( - Icons.warning, - size: 36, - color: theme.colorScheme.onErrorContainer, - ), - ), - Expanded( - child: Markdown( - shrinkWrap: true, - onTapLink: (text, href, title) async { - if (href == 'settings') { - await context.push(SettingsRoute().location); - } - }, - data: '## No Kagi Session Provided\n' - 'Please navigate to [Settings](settings) and enter your Kagi Session Token.\n' - "You must provide a valid session in order to use Kagi's features.", - ), - ), - ], + replacement: ErrorContainer( + content: Markdown( + shrinkWrap: true, + onTapLink: (text, href, title) async { + if (href == 'settings') { + await context.push(SettingsRoute().location); + } + }, + data: '## No Kagi Session Provided\n' + 'Please navigate to [Settings](settings) and enter your Kagi Session Token.\n' + "You must provide a valid session in order to use Kagi's features.", ), ), child: Wrap( diff --git a/app/lib/features/search_browser/presentation/widgets/landing/content.dart b/app/lib/features/search_browser/presentation/widgets/landing/content.dart index 9f1a25dd..ccf27e5b 100644 --- a/app/lib/features/search_browser/presentation/widgets/landing/content.dart +++ b/app/lib/features/search_browser/presentation/widgets/landing/content.dart @@ -1,7 +1,9 @@ +import 'package:bang_navigator/domain/services/app_initialization.dart'; +import 'package:bang_navigator/features/search_browser/presentation/widgets/error_container.dart'; import 'package:bang_navigator/features/search_browser/presentation/widgets/landing/action.dart'; +import 'package:bang_navigator/presentation/hooks/cached_future.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -12,17 +14,12 @@ class LandingContent extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final textTheme = Theme.of(context).textTheme; - // ignore: discarded_futures - final descriptionAssetFuture = useMemoized( + final descriptionAsset = useCachedFuture( () async => rootBundle.loadString('assets/landing/description.md'), ); - final descriptionAsset = useFuture(descriptionAssetFuture); - - // ignore: discarded_futures - final changelogAssetFuture = useMemoized( + final changelogAsset = useCachedFuture( () async => rootBundle.loadString('assets/landing/changelog.md'), ); - final changelogAsset = useFuture(changelogAssetFuture); return SingleChildScrollView( child: Column( @@ -42,6 +39,71 @@ class LandingContent extends HookConsumerWidget { child: Image.asset('assets/icon/icon.png'), ), const LandingAction(), + Consumer( + builder: (context, ref, child) { + final errors = ref.watch( + appInitializationServiceProvider + .select((result) => result.valueOrNull?.errors), + ); + + if (errors == null || errors.isEmpty) { + return const SizedBox.shrink(); + } + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + ...errors.map( + (error) { + final theme = Theme.of(context); + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: ErrorContainer( + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Error during App Initialization!', + style: theme.textTheme.titleMedium, + ), + Text( + error.message, + style: theme.textTheme.titleSmall, + ), + if (error.details != null) + Text(error.details.toString()), + ], + ), + ), + ); + }, + ), + const SizedBox( + height: 8, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: SizedBox( + width: double.infinity, + child: FilledButton.tonalIcon( + onPressed: () async { + await ref + .read(appInitializationServiceProvider.notifier) + .reinitialize(); + }, + style: OutlinedButton.styleFrom( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + label: const Text('Restart App'), + icon: const Icon(Icons.refresh_outlined), + ), + ), + ), + ], + ); + }, + ), Markdown( data: descriptionAsset.data ?? '', selectable: true, diff --git a/app/lib/presentation/hooks/cached_future.dart b/app/lib/presentation/hooks/cached_future.dart new file mode 100644 index 00000000..1375fb07 --- /dev/null +++ b/app/lib/presentation/hooks/cached_future.dart @@ -0,0 +1,12 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; + +AsyncSnapshot useCachedFuture( + Future Function() valueBuilder, +) { + // ignore: discarded_futures + final cachedFuture = useMemoized( + valueBuilder, + ); + return useFuture(cachedFuture); +} diff --git a/app/lib/presentation/widgets/failure_widget.dart b/app/lib/presentation/widgets/failure_widget.dart index 20e8b397..26056164 100644 --- a/app/lib/presentation/widgets/failure_widget.dart +++ b/app/lib/presentation/widgets/failure_widget.dart @@ -10,7 +10,7 @@ class FailureWidget extends StatelessWidget { }); final String? title; - final Object? exception; + final dynamic exception; final VoidCallback? onRetry; final bool compact; @@ -23,7 +23,10 @@ class FailureWidget extends StatelessWidget { ListTile( title: Text(title ?? 'Something went wrong'), subtitle: exception != null - ? Text(exception.runtimeType.toString()) + ? switch (exception) { + final String string => Text(string), + _ => Text(exception.runtimeType.toString()) + } : null, trailing: compact && onRetry != null ? IconButton.outlined(