diff --git a/uni/lib/view/bug_report/bug_report.dart b/uni/lib/view/bug_report/bug_report.dart index 413c1859e..a77fe864b 100644 --- a/uni/lib/view/bug_report/bug_report.dart +++ b/uni/lib/view/bug_report/bug_report.dart @@ -14,9 +14,13 @@ class BugReportPageView extends StatefulWidget { class BugReportPageViewState extends SecondaryPageViewState { @override Widget getBody(BuildContext context) { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 30, vertical: 20), - child: const BugReportForm(), + return ListView( + children: [ + Container( + margin: const EdgeInsets.symmetric(horizontal: 30, vertical: 20), + child: const BugReportForm(), + ), + ], ); } diff --git a/uni/lib/view/bug_report/widgets/form.dart b/uni/lib/view/bug_report/widgets/form.dart index 14d5403a8..01fb7b0e0 100644 --- a/uni/lib/view/bug_report/widgets/form.dart +++ b/uni/lib/view/bug_report/widgets/form.dart @@ -80,7 +80,7 @@ class BugReportFormState extends State { Widget build(BuildContext context) { return Form( key: _formKey, - child: ListView( + child: Column( children: [ const Padding(padding: EdgeInsets.only(bottom: 10)), PageTitle( 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 311400da7..e91bffc7b 100644 --- a/uni/lib/view/common_widgets/pages_layouts/general/general.dart +++ b/uni/lib/view/common_widgets/pages_layouts/general/general.dart @@ -84,36 +84,52 @@ abstract class GeneralPageViewState extends State { ); } + Widget? getHeader(BuildContext context) { + return null; + } + String? getTitle(); Widget getBody(BuildContext context); - Widget refreshState(BuildContext context, Widget child) { - return RefreshIndicator( - key: GlobalKey(), - onRefresh: () => ProfileProvider.fetchOrGetCachedProfilePicture( - Provider.of(context, listen: false).state!, - forceRetrieval: true, - ).then((value) => onRefresh(context)), - child: Builder( - builder: (context) => GestureDetector( - onHorizontalDragEnd: (dragDetails) { - if (dragDetails.primaryVelocity! > 2) { - Scaffold.of(context).openDrawer(); - } - }, - child: child, - ), - ), + Future buildProfileDecorationImage( + BuildContext context, { + bool forceRetrieval = false, + }) async { + final sessionProvider = + Provider.of(context, listen: false); + await sessionProvider.ensureInitialized(context); + final profilePictureFile = + await ProfileProvider.fetchOrGetCachedProfilePicture( + sessionProvider.state!, + forceRetrieval: forceRetrieval, ); + return getProfileDecorationImage(profilePictureFile); + } + + /// Returns the current user image. + /// + /// If the image is not found / doesn't exist returns a generic placeholder. + DecorationImage getProfileDecorationImage(File? profilePicture) { + const fallbackPicture = AssetImage('assets/images/profile_placeholder.png'); + final image = + profilePicture == null ? fallbackPicture : FileImage(profilePicture); + + final result = + DecorationImage(fit: BoxFit.cover, image: image as ImageProvider); + return result; } Widget getScaffold(BuildContext context, Widget body) { return Scaffold( backgroundColor: Theme.of(context).colorScheme.surface, - bottomNavigationBar: const AppBottomNavbar(), appBar: getTopNavbar(context), - body: RefreshState(onRefresh: onRefresh, child: body), + bottomNavigationBar: const AppBottomNavbar(), + body: RefreshState( + onRefresh: onRefresh, + header: getHeader(context), + body: body, + ), ); } diff --git a/uni/lib/view/common_widgets/pages_layouts/general/widgets/refresh_state.dart b/uni/lib/view/common_widgets/pages_layouts/general/widgets/refresh_state.dart index 199579429..95ca95af8 100644 --- a/uni/lib/view/common_widgets/pages_layouts/general/widgets/refresh_state.dart +++ b/uni/lib/view/common_widgets/pages_layouts/general/widgets/refresh_state.dart @@ -4,20 +4,58 @@ import 'package:uni/model/providers/startup/profile_provider.dart'; import 'package:uni/model/providers/startup/session_provider.dart'; class RefreshState extends StatelessWidget { - const RefreshState({required this.onRefresh, required this.child, super.key}); + const RefreshState({ + required this.onRefresh, + required this.header, + required this.body, + super.key, + }); final Future Function(BuildContext) onRefresh; - final Widget child; + final Widget? header; + final Widget body; @override Widget build(BuildContext context) { - return RefreshIndicator( - key: GlobalKey(), - onRefresh: () => ProfileProvider.fetchOrGetCachedProfilePicture( - Provider.of(context, listen: false).state!, - forceRetrieval: true, - ).then((value) => onRefresh(context)), - child: child, + return Column( + children: [ + if (header != null) header!, + Expanded( + child: LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: RefreshIndicator( + key: GlobalKey(), + notificationPredicate: (notification) => + notification.metrics.axisDirection == AxisDirection.down, + onRefresh: () => + ProfileProvider.fetchOrGetCachedProfilePicture( + Provider.of(context, listen: false).state!, + forceRetrieval: true, + ).then((value) => onRefresh(context)), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + maxHeight: viewportConstraints.maxHeight, + ), + child: Builder( + builder: (context) => GestureDetector( + onHorizontalDragEnd: (dragDetails) { + if (dragDetails.primaryVelocity! > 2) { + Scaffold.of(context).openDrawer(); + } + }, + child: body, + ), + ), + ), + ), + ); + }, + ), + ), + ], ); } } diff --git a/uni/lib/view/common_widgets/pages_layouts/secondary/secondary.dart b/uni/lib/view/common_widgets/pages_layouts/secondary/secondary.dart index 5f4a3379f..8ef0fcd48 100644 --- a/uni/lib/view/common_widgets/pages_layouts/secondary/secondary.dart +++ b/uni/lib/view/common_widgets/pages_layouts/secondary/secondary.dart @@ -14,7 +14,11 @@ abstract class SecondaryPageViewState backgroundColor: Theme.of(context).colorScheme.surface, appBar: getTopNavbar(context), bottomNavigationBar: const AppBottomNavbar(), - body: RefreshState(onRefresh: onRefresh, child: body), + body: RefreshState( + onRefresh: onRefresh, + header: getHeader(context), + body: body, + ), ); } 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 80c5ba0e3..1e52ee7cf 100644 --- a/uni/lib/view/course_unit_info/course_unit_info.dart +++ b/uni/lib/view/course_unit_info/course_unit_info.dart @@ -67,6 +67,14 @@ class CourseUnitDetailPageViewState await loadInfo(force: false); } + @override + Widget? getHeader(BuildContext context) { + return PageTitle( + center: false, + name: widget.courseUnit.name, + ); + } + @override Widget getBody(BuildContext context) { return DefaultTabController( @@ -74,10 +82,6 @@ class CourseUnitDetailPageViewState child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - PageTitle( - center: false, - name: widget.courseUnit.name, - ), TabBar( tabs: [ Tab(text: S.of(context).course_info), diff --git a/uni/lib/view/course_units/course_units.dart b/uni/lib/view/course_units/course_units.dart index 92a439f3b..4ab198253 100644 --- a/uni/lib/view/course_units/course_units.dart +++ b/uni/lib/view/course_units/course_units.dart @@ -27,7 +27,7 @@ class CourseUnitsPageViewState String? selectedSemester; @override - Widget getBody(BuildContext context) { + Widget? getHeader(BuildContext context) { return LazyConsumer( builder: (context, profile) { final courseUnits = profile.courseUnits; @@ -50,11 +50,41 @@ class CourseUnitsPageViewState } } - return Column( - children: [ - _getFilters(availableYears, availableSemesters), - _getPageView(courseUnits, availableYears, availableSemesters), - ], + return _getFilters(availableYears, availableSemesters); + }, + hasContent: (profile) => profile.courseUnits.isNotEmpty, + onNullContent: Column( + children: [ + _getFilters([], []), + Center( + heightFactor: 10, + child: Text( + S.of(context).no_selected_courses, + style: Theme.of(context).textTheme.titleLarge, + ), + ), + ], + ), + ); + } + + @override + Widget getBody(BuildContext context) { + return LazyConsumer( + builder: (context, profile) { + final courseUnits = profile.courseUnits; + var availableYears = []; + var availableSemesters = []; + + if (courseUnits.isNotEmpty) { + availableYears = _getAvailableYears(courseUnits); + availableSemesters = _getAvailableSemesters(courseUnits); + } + + return _getPageView( + courseUnits, + availableYears, + availableSemesters, ); }, hasContent: (profile) => profile.courseUnits.isNotEmpty, @@ -153,13 +183,10 @@ class CourseUnitsPageViewState ), ); } - return Expanded( - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 20), - child: ListView( - shrinkWrap: true, - children: _generateCourseUnitsGridView(courseUnits), - ), + return Container( + margin: const EdgeInsets.only(left: 20, right: 20, bottom: 20), + child: ListView( + children: _generateCourseUnitsGridView(courseUnits), ), ); } diff --git a/uni/lib/view/home/home.dart b/uni/lib/view/home/home.dart index 6d105466a..8b6177a6e 100644 --- a/uni/lib/view/home/home.dart +++ b/uni/lib/view/home/home.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:uni/controller/local_storage/preferences_controller.dart'; +import 'package:uni/generated/l10n.dart'; import 'package:uni/utils/favorite_widget_type.dart'; +import 'package:uni/view/common_widgets/page_title.dart'; import 'package:uni/view/common_widgets/pages_layouts/general/general.dart'; import 'package:uni/view/common_widgets/pages_layouts/general/widgets/profile_button.dart'; import 'package:uni/view/common_widgets/pages_layouts/general/widgets/top_navigation_bar.dart'; @@ -45,6 +47,41 @@ class HomePageViewState extends GeneralPageViewState { PreferencesController.saveFavoriteCards(favorites); } + @override + Widget? getHeader(BuildContext context) { + return Container( + padding: const EdgeInsets.fromLTRB(20, 10, 20, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + PageTitle( + name: S.of(context).nav_title('area'), + center: false, + pad: false, + ), + if (isEditing) + ElevatedButton( + onPressed: () => setState(() { + isEditing = false; + }), + child: Text( + S.of(context).edit_on, + ), + ) + else + OutlinedButton( + onPressed: () => setState(() { + isEditing = true; + }), + child: Text( + S.of(context).edit_off, + ), + ), + ], + ), + ); + } + void toggleEditing() { setState(() { isEditing = !isEditing; diff --git a/uni/lib/view/home/widgets/main_cards_list.dart b/uni/lib/view/home/widgets/main_cards_list.dart index ed7fedb50..2098fb294 100644 --- a/uni/lib/view/home/widgets/main_cards_list.dart +++ b/uni/lib/view/home/widgets/main_cards_list.dart @@ -4,7 +4,6 @@ import 'package:uni/generated/l10n.dart'; import 'package:uni/model/providers/startup/session_provider.dart'; import 'package:uni/utils/favorite_widget_type.dart'; import 'package:uni/view/common_widgets/generic_card.dart'; -import 'package:uni/view/common_widgets/page_title.dart'; import 'package:uni/view/home/widgets/bus_stop_card.dart'; import 'package:uni/view/home/widgets/exam_card.dart'; import 'package:uni/view/home/widgets/exit_app_dialog.dart'; @@ -55,42 +54,31 @@ class MainCardsListState extends State { @override Widget build(BuildContext context) { // ignore: deprecated_member_use, see #1209 - return WillPopScope( - onWillPop: () async { - if (widget.isEditing) { - widget.toggleEditing(); - return false; - } - return true; - }, - child: Scaffold( - backgroundColor: Theme.of(context).colorScheme.surface, - body: BackButtonExitWrapper( - child: SizedBox( - height: MediaQuery.of(context).size.height, - child: widget.isEditing - ? ReorderableListView( - onReorder: reorderCard, - header: createTopBar(context), - children: favoriteCardsFromTypes( + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.surface, + body: BackButtonExitWrapper( + child: SizedBox( + height: MediaQuery.of(context).size.height, + child: widget.isEditing + ? ReorderableListView( + onReorder: reorderCard, + children: favoriteCardsFromTypes( + widget.favoriteCardTypes, + context, + ), + ) + : ListView( + children: [ + ...favoriteCardsFromTypes( widget.favoriteCardTypes, context, ), - ) - : ListView( - children: [ - createTopBar(context), - ...favoriteCardsFromTypes( - widget.favoriteCardTypes, - context, - ), - ], - ), - ), + ], + ), ), - floatingActionButton: - widget.isEditing ? createActionButton(context) : null, ), + floatingActionButton: + widget.isEditing ? createActionButton(context) : null, ); } @@ -157,38 +145,6 @@ class MainCardsListState extends State { : possibleCardAdditions; } - Widget createTopBar( - BuildContext context, - ) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - PageTitle( - name: S.of(context).nav_title('area'), - center: false, - pad: false, - ), - if (widget.isEditing) - ElevatedButton( - onPressed: widget.toggleEditing, - child: Text( - S.of(context).edit_on, - ), - ) - else - OutlinedButton( - onPressed: widget.toggleEditing, - child: Text( - S.of(context).edit_off, - ), - ), - ], - ), - ); - } - List favoriteCardsFromTypes( List cardTypes, BuildContext context, diff --git a/uni/lib/view/restaurant/restaurant_page_view.dart b/uni/lib/view/restaurant/restaurant_page_view.dart index 2d36b4e20..f78a06d07 100644 --- a/uni/lib/view/restaurant/restaurant_page_view.dart +++ b/uni/lib/view/restaurant/restaurant_page_view.dart @@ -39,29 +39,29 @@ class _RestaurantPageViewState extends GeneralPageViewState String? getTitle() => S.of(context).nav_title(NavigationItem.navRestaurants.route); + @override + Widget? getHeader(BuildContext context) { + return TabBar( + controller: tabController, + isScrollable: true, + tabs: createTabs(context), + ); + } + @override Widget getBody(BuildContext context) { - return Column( - children: [ - TabBar( - controller: tabController, - isScrollable: true, - tabs: createTabs(context), - ), - LazyConsumer>( - builder: (context, restaurants) => createTabViewBuilder( - restaurants, - context, - ), - onNullContent: Center( - child: Text( - S.of(context).no_menus, - style: const TextStyle(fontSize: 18), - ), - ), - hasContent: (restaurants) => restaurants.isNotEmpty, + return LazyConsumer>( + builder: (context, restaurants) => createTabViewBuilder( + restaurants, + context, + ), + onNullContent: Center( + child: Text( + S.of(context).no_menus, + style: const TextStyle(fontSize: 18), ), - ], + ), + hasContent: (restaurants) => restaurants.isNotEmpty, ); } @@ -84,14 +84,15 @@ class _RestaurantPageViewState extends GeneralPageViewState ), ); } - return ListView(padding: EdgeInsets.zero, children: restaurantsWidgets); + return ListView( + padding: const EdgeInsets.only(top: 10), + children: restaurantsWidgets, + ); }).toList(); - return Expanded( - child: TabBarView( - controller: tabController, - children: dayContents, - ), + return TabBarView( + controller: tabController, + children: dayContents, ); } diff --git a/uni/pubspec.lock b/uni/pubspec.lock index 5e4d9cbce..c2893a579 100644 --- a/uni/pubspec.lock +++ b/uni/pubspec.lock @@ -1479,10 +1479,10 @@ packages: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.4.2" web_socket_channel: dependency: transitive description: