From 01d728a1eefcdcc50a97908b4c3af8f5d77d49c7 Mon Sep 17 00:00:00 2001 From: Bruno Mendes Date: Thu, 28 Sep 2023 20:39:33 +0100 Subject: [PATCH] Improve course units loading behavior --- .../pages_layouts/general/general.dart | 92 ++++++++++++++----- .../course_unit_info/course_unit_info.dart | 73 ++++++--------- uni/lib/view/lazy_consumer.dart | 4 +- 3 files changed, 100 insertions(+), 69 deletions(-) diff --git a/uni/lib/view/common_widgets/pages_layouts/general/general.dart b/uni/lib/view/common_widgets/pages_layouts/general/general.dart index ef5753178..81af89363 100644 --- a/uni/lib/view/common_widgets/pages_layouts/general/general.dart +++ b/uni/lib/view/common_widgets/pages_layouts/general/general.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:uni/model/providers/startup/profile_provider.dart'; import 'package:uni/model/providers/startup/session_provider.dart'; import 'package:uni/utils/drawer_items.dart'; @@ -13,6 +14,8 @@ import 'package:uni/view/profile/profile.dart'; /// Page with a hamburger menu and the user profile picture abstract class GeneralPageViewState extends State { final double borderMargin = 18; + bool _loadedOnce = false; + bool _loading = true; Future onRefresh(BuildContext context); @@ -20,23 +23,55 @@ abstract class GeneralPageViewState extends State { @override Widget build(BuildContext context) { - WidgetsBinding.instance.addPostFrameCallback((_) => onLoad(context)); - return getScaffold(context, getBody(context)); + WidgetsBinding.instance.addPostFrameCallback((_) async { + if (_loadedOnce) { + return; + } + _loadedOnce = true; + setState(() { + _loading = true; + }); + + try { + await onLoad(context); + } catch (e, stackTrace) { + await Sentry.captureException(e, stackTrace: stackTrace); + } + + setState(() { + _loading = false; + }); + }); + + return getScaffold( + context, + _loading + ? const Flex( + direction: Axis.vertical, + children: [ + Expanded( + child: Center( + child: CircularProgressIndicator(), + ), + ) + ], + ) + : getBody(context), + ); } Widget getBody(BuildContext context) { return Container(); } - Future buildProfileDecorationImage( - BuildContext context, { + Future buildProfileDecorationImage(BuildContext context, { bool forceRetrieval = false, }) async { final sessionProvider = - Provider.of(context, listen: false); + Provider.of(context, listen: false); await sessionProvider.ensureInitializedFromStorage(); final profilePictureFile = - await ProfileProvider.fetchOrGetCachedProfilePicture( + await ProfileProvider.fetchOrGetCachedProfilePicture( sessionProvider.session, forceRetrieval: forceRetrieval, ); @@ -49,20 +84,23 @@ abstract class GeneralPageViewState extends State { DecorationImage getProfileDecorationImage(File? profilePicture) { const fallbackPicture = AssetImage('assets/images/profile_placeholder.png'); final image = - profilePicture == null ? fallbackPicture : FileImage(profilePicture); + profilePicture == null ? fallbackPicture : FileImage(profilePicture); final result = - DecorationImage(fit: BoxFit.cover, image: image as ImageProvider); + DecorationImage(fit: BoxFit.cover, image: image as ImageProvider); return result; } Widget refreshState(BuildContext context, Widget child) { return RefreshIndicator( key: GlobalKey(), - onRefresh: () => ProfileProvider.fetchOrGetCachedProfilePicture( - Provider.of(context, listen: false).session, - forceRetrieval: true, - ).then((value) => onRefresh(context)), + onRefresh: () => + ProfileProvider.fetchOrGetCachedProfilePicture( + Provider + .of(context, listen: false) + .session, + forceRetrieval: true, + ).then((value) => onRefresh(context)), child: child, ); } @@ -86,21 +124,30 @@ abstract class GeneralPageViewState extends State { bottom: PreferredSize( preferredSize: Size.zero, child: Container( - color: Theme.of(context).dividerColor, + color: Theme + .of(context) + .dividerColor, margin: EdgeInsets.only(left: borderMargin, right: borderMargin), height: 1.5, ), ), elevation: 0, - iconTheme: Theme.of(context).iconTheme, - backgroundColor: Theme.of(context).scaffoldBackgroundColor, + iconTheme: Theme + .of(context) + .iconTheme, + backgroundColor: Theme + .of(context) + .scaffoldBackgroundColor, titleSpacing: 0, title: ButtonTheme( materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: const RoundedRectangleBorder(), child: TextButton( onPressed: () { - final currentRouteName = ModalRoute.of(context)!.settings.name; + final currentRouteName = ModalRoute + .of(context)! + .settings + .name; if (currentRouteName != DrawerItem.navPersonalArea.title) { Navigator.pushNamed( context, @@ -110,7 +157,9 @@ abstract class GeneralPageViewState extends State { }, child: SvgPicture.asset( colorFilter: ColorFilter.mode( - Theme.of(context).primaryColor, + Theme + .of(context) + .primaryColor, BlendMode.srcIn, ), 'assets/images/logo_dark.svg', @@ -128,12 +177,11 @@ abstract class GeneralPageViewState extends State { Widget getTopRightButton(BuildContext context) { return FutureBuilder( future: buildProfileDecorationImage(context), - builder: ( - BuildContext context, - AsyncSnapshot decorationImage, - ) { + builder: (BuildContext context, + AsyncSnapshot decorationImage,) { return TextButton( - onPressed: () => { + onPressed: () => + { Navigator.push( context, MaterialPageRoute( diff --git a/uni/lib/view/course_unit_info/course_unit_info.dart b/uni/lib/view/course_unit_info/course_unit_info.dart index ade00b29d..dd858d9dd 100644 --- a/uni/lib/view/course_unit_info/course_unit_info.dart +++ b/uni/lib/view/course_unit_info/course_unit_info.dart @@ -6,13 +6,12 @@ import 'package:uni/model/providers/lazy/course_units_info_provider.dart'; import 'package:uni/model/providers/startup/session_provider.dart'; import 'package:uni/view/common_widgets/page_title.dart'; import 'package:uni/view/common_widgets/pages_layouts/secondary/secondary.dart'; -import 'package:uni/view/common_widgets/request_dependent_widget_builder.dart'; import 'package:uni/view/course_unit_info/widgets/course_unit_classes.dart'; import 'package:uni/view/course_unit_info/widgets/course_unit_sheet.dart'; -import 'package:uni/view/lazy_consumer.dart'; class CourseUnitDetailPageView extends StatefulWidget { const CourseUnitDetailPageView(this.courseUnit, {super.key}); + final CourseUnit courseUnit; @override @@ -91,50 +90,36 @@ class CourseUnitDetailPageViewState } Widget _courseUnitSheetView(BuildContext context) { - return LazyConsumer( - builder: (context, courseUnitsInfoProvider) { - return RequestDependentWidgetBuilder( - onNullContent: Center( - child: Text( - S.of(context).no_info, - textAlign: TextAlign.center, - ), - ), - status: courseUnitsInfoProvider.status, - builder: () => CourseUnitSheetView( - courseUnitsInfoProvider.courseUnitsSheets[widget.courseUnit]!, - ), - hasContentPredicate: - courseUnitsInfoProvider.courseUnitsSheets[widget.courseUnit] != - null && - courseUnitsInfoProvider.courseUnitsSheets[widget.courseUnit]! - .sections.isNotEmpty, - ); - }, - ); + final sheet = context + .read() + .courseUnitsSheets[widget.courseUnit]; + + if (sheet == null || sheet.sections.isEmpty) { + return Center( + child: Text( + S.of(context).no_info, + textAlign: TextAlign.center, + ), + ); + } + + return CourseUnitSheetView(sheet); } Widget _courseUnitClassesView(BuildContext context) { - return LazyConsumer( - builder: (context, courseUnitsInfoProvider) { - return RequestDependentWidgetBuilder( - onNullContent: Center( - child: Text( - S.of(context).no_class, - textAlign: TextAlign.center, - ), - ), - status: courseUnitsInfoProvider.status, - builder: () => CourseUnitClassesView( - courseUnitsInfoProvider.courseUnitsClasses[widget.courseUnit]!, - ), - hasContentPredicate: - courseUnitsInfoProvider.courseUnitsClasses[widget.courseUnit] != - null && - courseUnitsInfoProvider - .courseUnitsClasses[widget.courseUnit]!.isNotEmpty, - ); - }, - ); + final classes = context + .read() + .courseUnitsClasses[widget.courseUnit]; + + if (classes == null || classes.isEmpty) { + return Center( + child: Text( + S.of(context).no_class, + textAlign: TextAlign.center, + ), + ); + } + + return CourseUnitClassesView(classes); } } diff --git a/uni/lib/view/lazy_consumer.dart b/uni/lib/view/lazy_consumer.dart index 49b19d748..ccb2b0305 100644 --- a/uni/lib/view/lazy_consumer.dart +++ b/uni/lib/view/lazy_consumer.dart @@ -69,9 +69,7 @@ class LazyConsumer extends StatelessWidget { // Finally, complete provider initialization if (context.mounted) { - // This will fail if the session initialization failed. - // That is the expected behavior. - await sessionFuture!.then((_) async { + await sessionFuture?.then((_) async { await provider!.ensureInitializedFromRemote(context); }); }