From 8126327b95395365799294117d1ac06453ad7667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Schw=C3=B6rer?= Date: Thu, 13 Jun 2024 17:00:08 +0200 Subject: [PATCH] Add LinearProgressIndicator for background queries --- flutter/lib/components/layout/app_bar.dart | 5 ++ .../layout/app_bar_progress_indicator.dart | 21 ++++++ flutter/lib/main.dart | 8 ++- .../lib/pages/message_view/message_view.dart | 64 ++++++++----------- flutter/lib/state/app_bar_state.dart | 20 ++++++ flutter/lib/utils/navi.dart | 40 ++++++++++++ 6 files changed, 119 insertions(+), 39 deletions(-) create mode 100644 flutter/lib/components/layout/app_bar_progress_indicator.dart create mode 100644 flutter/lib/state/app_bar_state.dart diff --git a/flutter/lib/components/layout/app_bar.dart b/flutter/lib/components/layout/app_bar.dart index 3f6e4d2..3e03ac3 100644 --- a/flutter/lib/components/layout/app_bar.dart +++ b/flutter/lib/components/layout/app_bar.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:provider/provider.dart'; +import 'package:simplecloudnotifier/components/layout/app_bar_progress_indicator.dart'; import 'package:simplecloudnotifier/pages/debug/debug_main.dart'; import 'package:simplecloudnotifier/state/app_theme.dart'; import 'package:simplecloudnotifier/utils/navi.dart'; @@ -71,6 +72,10 @@ class SCNAppBar extends StatelessWidget implements PreferredSizeWidget { title: Text(title ?? 'Simple Cloud Notifier 2.0'), actions: actions, backgroundColor: Theme.of(context).secondaryHeaderColor, + bottom: PreferredSize( + preferredSize: Size(double.infinity, 1.0), + child: AppBarProgressIndicator(), + ), ); } diff --git a/flutter/lib/components/layout/app_bar_progress_indicator.dart b/flutter/lib/components/layout/app_bar_progress_indicator.dart new file mode 100644 index 0000000..c227ea7 --- /dev/null +++ b/flutter/lib/components/layout/app_bar_progress_indicator.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:simplecloudnotifier/state/app_bar_state.dart'; + +class AppBarProgressIndicator extends StatelessWidget implements PreferredSizeWidget { + @override + Size get preferredSize => Size(double.infinity, 1.0); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, value, child) { + if (value.loadingIndeterminate) { + return LinearProgressIndicator(value: null); + } else { + return SizedBox.square(dimension: 4); // 4 height is the same as the LinearProgressIndicator + } + }, + ); + } +} diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 497fb8e..f7a47c3 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -7,12 +7,14 @@ import 'package:hive_flutter/hive_flutter.dart'; import 'package:simplecloudnotifier/api/api_client.dart'; import 'package:simplecloudnotifier/models/client.dart'; import 'package:simplecloudnotifier/nav_layout.dart'; +import 'package:simplecloudnotifier/state/app_bar_state.dart'; import 'package:simplecloudnotifier/state/app_theme.dart'; import 'package:simplecloudnotifier/state/application_log.dart'; import 'package:simplecloudnotifier/state/globals.dart'; import 'package:simplecloudnotifier/state/request_log.dart'; import 'package:simplecloudnotifier/state/app_auth.dart'; import 'package:firebase_core/firebase_core.dart'; +import 'package:simplecloudnotifier/utils/navi.dart'; import 'package:toastification/toastification.dart'; import 'firebase_options.dart'; @@ -113,8 +115,9 @@ void main() async { providers: [ ChangeNotifierProvider(create: (context) => AppAuth(), lazy: false), ChangeNotifierProvider(create: (context) => AppTheme(), lazy: false), + ChangeNotifierProvider(create: (context) => AppBarState(), lazy: false), ], - child: const SCNApp(), + child: SCNApp(), ), ); } @@ -156,7 +159,7 @@ void setFirebaseToken(String fcmToken) async { } class SCNApp extends StatelessWidget { - const SCNApp({super.key}); + SCNApp({super.key}); @override Widget build(BuildContext context) { @@ -169,6 +172,7 @@ class SCNApp extends StatelessWidget { child: Consumer( builder: (context, appTheme, child) => MaterialApp( title: 'SimpleCloudNotifier', + navigatorObservers: [Navi.routeObserver], theme: ThemeData( //TODO color settings colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue, brightness: appTheme.darkMode ? Brightness.dark : Brightness.light), diff --git a/flutter/lib/pages/message_view/message_view.dart b/flutter/lib/pages/message_view/message_view.dart index 5bba5ad..b2f81a3 100644 --- a/flutter/lib/pages/message_view/message_view.dart +++ b/flutter/lib/pages/message_view/message_view.dart @@ -11,6 +11,7 @@ import 'package:simplecloudnotifier/models/keytoken.dart'; import 'package:simplecloudnotifier/models/message.dart'; import 'package:simplecloudnotifier/models/user.dart'; import 'package:simplecloudnotifier/state/app_auth.dart'; +import 'package:simplecloudnotifier/state/app_bar_state.dart'; import 'package:simplecloudnotifier/utils/toaster.dart'; import 'package:simplecloudnotifier/utils/ui.dart'; @@ -32,32 +33,38 @@ class _MessageViewPageState extends State { @override void initState() { - super.initState(); mainFuture = fetchData(); + super.initState(); } Future<(Message, ChannelPreview, KeyTokenPreview, UserPreview)> fetchData() async { - final acc = Provider.of(context, listen: false); + try { + await Future.delayed(const Duration(seconds: 0), () {}); // this is annoyingly important - otherwise we call setLoadingIndeterminate directly in initStat() and get an exception.... - final msg = await APIClient.getMessage(acc, widget.message.messageID); + AppBarState().setLoadingIndeterminate(true); - final fut_chn = APIClient.getChannelPreview(acc, msg.channelID); - final fut_key = APIClient.getKeyTokenPreview(acc, msg.usedKeyID); - final fut_usr = APIClient.getUserPreview(acc, msg.senderUserID); + final acc = Provider.of(context, listen: false); - final chn = await fut_chn; - final key = await fut_key; - final usr = await fut_usr; + final msg = await APIClient.getMessage(acc, widget.message.messageID); - //TODO getShortUser for sender + final fut_chn = APIClient.getChannelPreview(acc, msg.channelID); + final fut_key = APIClient.getKeyTokenPreview(acc, msg.usedKeyID); + final fut_usr = APIClient.getUserPreview(acc, msg.senderUserID); - //await Future.delayed(const Duration(seconds: 2), () {}); + final chn = await fut_chn; + final key = await fut_key; + final usr = await fut_usr; - final r = (msg, chn, key, usr); + //await Future.delayed(const Duration(seconds: 10), () {}); - mainFutureSnapshot = r; + final r = (msg, chn, key, usr); - return r; + mainFutureSnapshot = r; + + return r; + } finally { + AppBarState().setLoadingIndeterminate(false); + } } @override @@ -77,11 +84,11 @@ class _MessageViewPageState extends State { builder: (context, snapshot) { if (snapshot.hasData) { final (msg, chn, tok, usr) = snapshot.data!; - return _buildMessageView(context, msg, chn, tok, usr, false); + return _buildMessageView(context, msg, chn, tok, usr); } else if (snapshot.hasError) { return Center(child: Text('${snapshot.error}')); //TODO nice error page } else if (!widget.message.trimmed) { - return _buildMessageView(context, widget.message, null, null, null, true); + return _buildMessageView(context, widget.message, null, null, null); } else { return const Center(child: CircularProgressIndicator()); } @@ -111,7 +118,7 @@ class _MessageViewPageState extends State { } } - Widget _buildMessageView(BuildContext context, Message message, ChannelPreview? channel, KeyTokenPreview? token, UserPreview? user, bool loading) { + Widget _buildMessageView(BuildContext context, Message message, ChannelPreview? channel, KeyTokenPreview? token, UserPreview? user) { final userAccUserID = context.select((v) => v.userID); return SingleChildScrollView( @@ -120,7 +127,7 @@ class _MessageViewPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - ..._buildMessageHeader(context, message, channel, loading), + ..._buildMessageHeader(context, message, channel), SizedBox(height: 8), if (message.content != null) ..._buildMessageContent(context, message), SizedBox(height: 8), @@ -141,7 +148,7 @@ class _MessageViewPageState extends State { return channel?.displayName ?? message.channelInternalName; } - List _buildMessageHeader(BuildContext context, Message message, ChannelPreview? channel, bool loading) { + List _buildMessageHeader(BuildContext context, Message message, ChannelPreview? channel) { return [ Row( children: [ @@ -156,24 +163,7 @@ class _MessageViewPageState extends State { ], ), SizedBox(height: 8), - if (!loading) Text(message.title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), - if (loading) - Stack( - children: [ - Row( - children: [ - Flexible(child: Text(message.title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold))), - SizedBox(height: 20, width: 20), - ], - ), - Row( - children: [ - Expanded(child: SizedBox(width: 0)), - SizedBox(child: CircularProgressIndicator(), height: 20, width: 20), - ], - ), - ], - ), + Text(message.title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), ]; } diff --git a/flutter/lib/state/app_bar_state.dart b/flutter/lib/state/app_bar_state.dart new file mode 100644 index 0000000..b62384a --- /dev/null +++ b/flutter/lib/state/app_bar_state.dart @@ -0,0 +1,20 @@ +import 'package:flutter/foundation.dart'; + +class AppBarState extends ChangeNotifier { + static AppBarState? _singleton = AppBarState._internal(); + + factory AppBarState() { + return _singleton ?? (_singleton = AppBarState._internal()); + } + + AppBarState._internal() {} + + bool _loadingIndeterminate = false; + bool get loadingIndeterminate => _loadingIndeterminate; + + void setLoadingIndeterminate(bool v) { + if (_loadingIndeterminate == v) return; + _loadingIndeterminate = v; + notifyListeners(); + } +} diff --git a/flutter/lib/utils/navi.dart b/flutter/lib/utils/navi.dart index f250af1..51bb5a1 100644 --- a/flutter/lib/utils/navi.dart +++ b/flutter/lib/utils/navi.dart @@ -1,11 +1,51 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:simplecloudnotifier/state/app_bar_state.dart'; class Navi { + static final SCNRouteObserver routeObserver = SCNRouteObserver(); + static void push(BuildContext context, T Function() builder) { + Provider.of(context, listen: false).setLoadingIndeterminate(false); + Navigator.push(context, MaterialPageRoute(builder: (context) => builder())); } static void popToRoot(BuildContext context) { + Provider.of(context, listen: false).setLoadingIndeterminate(false); + Navigator.popUntil(context, (route) => route.isFirst); } } + +class SCNRouteObserver extends RouteObserver> { + @override + void didPush(Route route, Route? previousRoute) { + super.didPush(route, previousRoute); + if (route is PageRoute) { + AppBarState().setLoadingIndeterminate(false); + + print('[SCNRouteObserver] .didPush()'); + } + } + + @override + void didReplace({Route? newRoute, Route? oldRoute}) { + super.didReplace(newRoute: newRoute, oldRoute: oldRoute); + if (newRoute is PageRoute) { + AppBarState().setLoadingIndeterminate(false); + + print('[SCNRouteObserver] .didReplace()'); + } + } + + @override + void didPop(Route route, Route? previousRoute) { + super.didPop(route, previousRoute); + if (previousRoute is PageRoute && route is PageRoute) { + AppBarState().setLoadingIndeterminate(false); + + print('[SCNRouteObserver] .didPop()'); + } + } +}