diff --git a/packages/uni_app/lib/controller/local_storage/preferences_controller.dart b/packages/uni_app/lib/controller/local_storage/preferences_controller.dart index 0e3a855ee..835337500 100644 --- a/packages/uni_app/lib/controller/local_storage/preferences_controller.dart +++ b/packages/uni_app/lib/controller/local_storage/preferences_controller.dart @@ -30,10 +30,9 @@ class PreferencesController { static const String _locale = 'app_locale'; static const String _lastCacheCleanUpDate = 'last_clean'; static const String _favoriteCards = 'favorite_cards'; - static final List _defaultFavoriteCards = [ + static final List _homeDefaultcards = [ FavoriteWidgetType.schedule, FavoriteWidgetType.exams, - FavoriteWidgetType.busStops, ]; static const String _hiddenExams = 'hidden_exams'; static const String _favoriteRestaurants = 'favorite_restaurants'; @@ -173,31 +172,26 @@ class PreferencesController { await _secureStorage.delete(key: _userSession); } - /// Replaces the user's favorite widgets with [newFavorites]. static Future saveFavoriteCards( List newFavorites, ) async { await prefs.setStringList( _favoriteCards, - newFavorites.map((a) => a.index.toString()).toList(), + newFavorites.map((elem) => elem.name).toList(), ); } - /// Returns a list containing the user's favorite widgets. static List getFavoriteCards() { - final storedFavorites = prefs - .getStringList(_favoriteCards) - ?.where( - (element) => int.parse(element) < FavoriteWidgetType.values.length, - ) - .toList(); + final storedFavorites = prefs.getStringList(_favoriteCards); if (storedFavorites == null) { - return _defaultFavoriteCards; + return _homeDefaultcards; } return storedFavorites - .map((i) => FavoriteWidgetType.values[int.parse(i)]) + .map( + (elem) => FavoriteWidgetType.values.firstWhere((e) => e.name == elem), + ) .toList(); } diff --git a/packages/uni_app/lib/controller/parsers/schedule/api/parser.dart b/packages/uni_app/lib/controller/parsers/schedule/api/parser.dart index 69b76c16c..ba51333f1 100644 --- a/packages/uni_app/lib/controller/parsers/schedule/api/parser.dart +++ b/packages/uni_app/lib/controller/parsers/schedule/api/parser.dart @@ -63,7 +63,7 @@ Future> parseSchedule( final lec = Lecture.fromApi( '', - subject, + _filterSubjectName(subject), typeClass, startTime, blocks, @@ -78,3 +78,8 @@ Future> parseSchedule( return lectures.toList()..sort((a, b) => a.compare(b)); } + +String _filterSubjectName(String subject) { + return RegExp(r' - ([^()]*)(?: \(|$)').firstMatch(subject)?.group(1) ?? + subject; +} diff --git a/packages/uni_app/lib/controller/parsers/schedule/new_api/parser.dart b/packages/uni_app/lib/controller/parsers/schedule/new_api/parser.dart index 2246514ea..f07cff78a 100644 --- a/packages/uni_app/lib/controller/parsers/schedule/new_api/parser.dart +++ b/packages/uni_app/lib/controller/parsers/schedule/new_api/parser.dart @@ -31,7 +31,7 @@ List getLecturesFromApiResponse( .map( (lecture) => Lecture( lecture.units.first.acronym, - lecture.units.first.name, + _filterSubjectName(lecture.units.first.name), lecture.typology.acronym, lecture.start, lecture.end, @@ -45,3 +45,8 @@ List getLecturesFromApiResponse( ) .toList(); } + +String _filterSubjectName(String subject) { + return RegExp(r' - ([^()]*)(?: \(|$)').firstMatch(subject)?.group(1) ?? + subject; +} diff --git a/packages/uni_app/lib/main.dart b/packages/uni_app/lib/main.dart index 16e5215c0..f6f4e0fea 100644 --- a/packages/uni_app/lib/main.dart +++ b/packages/uni_app/lib/main.dart @@ -40,6 +40,7 @@ import 'package:uni/view/common_widgets/page_transition.dart'; import 'package:uni/view/course_units/course_units.dart'; import 'package:uni/view/exams/exams.dart'; import 'package:uni/view/faculty/faculty.dart'; +import 'package:uni/view/home/edit_home.dart'; import 'package:uni/view/home/home.dart'; import 'package:uni/view/library/library.dart'; import 'package:uni/view/locale_notifier.dart'; @@ -247,6 +248,11 @@ class ApplicationState extends State { page: const SplashScreenView(), settings: settings, ), + '/${NavigationItem.navEditPersonalArea.route}': + PageTransition.splashTransitionRoute( + page: const EditHomeView(), + settings: settings, + ), '/${NavigationItem.navLogin.route}': PageTransition.splashTransitionRoute( page: const LoginPageView(), diff --git a/packages/uni_app/lib/utils/favorite_widget_type.dart b/packages/uni_app/lib/utils/favorite_widget_type.dart index 9e27fe865..0202e0caa 100644 --- a/packages/uni_app/lib/utils/favorite_widget_type.dart +++ b/packages/uni_app/lib/utils/favorite_widget_type.dart @@ -1,21 +1 @@ -enum FavoriteWidgetType { - exams, - schedule, - printBalance, - account, - libraryOccupation(faculties: {'feup'}), - busStops, - restaurant; - - const FavoriteWidgetType({this.faculties}); - - final Set? faculties; - - bool isVisible(List userFaculties) { - if (faculties == null) { - return true; - } - - return userFaculties.any((element) => faculties!.contains(element)); - } -} +enum FavoriteWidgetType { schedule, exams, library, restaurants } diff --git a/packages/uni_app/lib/utils/navigation_items.dart b/packages/uni_app/lib/utils/navigation_items.dart index 6c6dd720d..4b2482502 100644 --- a/packages/uni_app/lib/utils/navigation_items.dart +++ b/packages/uni_app/lib/utils/navigation_items.dart @@ -1,4 +1,5 @@ enum NavigationItem { + navEditPersonalArea('edit'), navPersonalArea('area'), navSchedule('horario'), navExams('exames'), diff --git a/packages/uni_app/lib/view/academic_path/exam_page.dart b/packages/uni_app/lib/view/academic_path/exam_page.dart index 63839264c..ab7c89150 100644 --- a/packages/uni_app/lib/view/academic_path/exam_page.dart +++ b/packages/uni_app/lib/view/academic_path/exam_page.dart @@ -10,7 +10,6 @@ import 'package:uni/view/lazy_consumer.dart'; import 'package:uni/view/locale_notifier.dart'; import 'package:uni_ui/cards/exam_card.dart'; import 'package:uni_ui/cards/timeline_card.dart'; -import 'package:uni_ui/theme.dart'; import 'package:uni_ui/timeline/timeline.dart'; class ExamsPage extends StatefulWidget { @@ -85,7 +84,7 @@ class _ExamsPageState extends State { Provider.of(context).getLocale(), ) .capitalize(), - style: lightTheme.textTheme.headlineLarge, + style: Theme.of(context).textTheme.headlineLarge, ), ), ListView.builder( diff --git a/packages/uni_app/lib/view/academic_path/widgets/schedule_day_timeline.dart b/packages/uni_app/lib/view/academic_path/widgets/schedule_day_timeline.dart index 2c0bc381d..6f2681d7a 100644 --- a/packages/uni_app/lib/view/academic_path/widgets/schedule_day_timeline.dart +++ b/packages/uni_app/lib/view/academic_path/widgets/schedule_day_timeline.dart @@ -48,7 +48,7 @@ class ScheduleDayTimeline extends StatelessWidget { subtitle: DateFormat('HH:mm').format(lecture.endTime), card: ScheduleCard( isActive: _isLectureActive(lecture), - name: _filterSubjectName(lecture.subject), + name: lecture.subject, acronym: lecture.acronym, room: lecture.room, type: lecture.typeClass, @@ -59,12 +59,6 @@ class ScheduleDayTimeline extends StatelessWidget { .toList(); } - // maybe should be changed to the backend, just extract the filtered name directly - String _filterSubjectName(String subject) { - return RegExp(r' - ([^()]*)(?: \(|$)').firstMatch(subject)?.group(1) ?? - subject; - } - bool _isLectureActive(Lecture lecture) { return now.isAfter(lecture.startTime) && now.isBefore(lecture.endTime); } diff --git a/packages/uni_app/lib/view/common_widgets/pages_layouts/general/general.dart b/packages/uni_app/lib/view/common_widgets/pages_layouts/general/general.dart index 7a8cff235..ea9b3242a 100644 --- a/packages/uni_app/lib/view/common_widgets/pages_layouts/general/general.dart +++ b/packages/uni_app/lib/view/common_widgets/pages_layouts/general/general.dart @@ -112,13 +112,12 @@ abstract class GeneralPageViewState extends State { Widget getScaffold(BuildContext context, Widget body) { return MediaQuery.removePadding( - // Prevent misalignment of navbar icons context: context, removeBottom: true, child: Scaffold( backgroundColor: Theme.of(context).colorScheme.surface, appBar: getTopNavbar(context), - extendBody: true, // Allow body to appear behind the bottom navbar + extendBody: true, bottomNavigationBar: const AppBottomNavbar(), body: RefreshState( onRefresh: onRefresh, diff --git a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart index 67c32ddf9..ce1036cfa 100644 --- a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart +++ b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_classes.dart @@ -4,7 +4,6 @@ import 'package:uni/model/entities/course_units/course_unit_class.dart'; import 'package:uni/model/providers/startup/session_provider.dart'; import 'package:uni/utils/student_number_getter.dart'; import 'package:uni/view/course_unit_info/widgets/course_unit_student_tile.dart'; -import 'package:uni_ui/theme.dart'; class CourseUnitClassesView extends StatefulWidget { const CourseUnitClassesView(this.classes, {super.key}); @@ -108,8 +107,8 @@ class _CourseUnitClassesViewState extends State { const EdgeInsets.symmetric(horizontal: 8, vertical: 8), decoration: BoxDecoration( color: isSelected - ? lightTheme.colorScheme.primary - : lightTheme.colorScheme.secondary, + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.secondary, borderRadius: BorderRadius.circular(25), boxShadow: [ BoxShadow( @@ -130,10 +129,10 @@ class _CourseUnitClassesViewState extends State { ? '${courseUnitClass.className} *' : courseUnitClass.className, style: isSelected - ? lightTheme.textTheme.labelMedium?.copyWith( - color: lightTheme.colorScheme.onPrimary, - ) - : lightTheme.textTheme.labelMedium, + ? Theme.of(context).textTheme.labelMedium?.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ) + : Theme.of(context).textTheme.labelMedium, textAlign: TextAlign.center, maxLines: 1, overflow: TextOverflow.ellipsis, diff --git a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_tile.dart b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_tile.dart index 1554b18e0..4f6a77137 100644 --- a/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_tile.dart +++ b/packages/uni_app/lib/view/course_unit_info/widgets/course_unit_student_tile.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:uni/model/entities/course_units/course_unit_class.dart'; import 'package:uni/model/providers/startup/profile_provider.dart'; import 'package:uni/session/flows/base/session.dart'; -import 'package:uni_ui/theme.dart'; class CourseUnitStudentTile extends StatelessWidget { const CourseUnitStudentTile(this.student, this.session, {super.key}); @@ -59,17 +58,17 @@ class CourseUnitStudentTile extends StatelessWidget { Text( firstName, overflow: TextOverflow.fade, - style: lightTheme.textTheme.titleLarge?.copyWith( - color: grayText, - ), + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: const Color.fromARGB(255, 48, 48, 48), + ), textAlign: TextAlign.center, ), Text( lastName, overflow: TextOverflow.ellipsis, - style: lightTheme.textTheme.titleLarge?.copyWith( - color: grayText, - ), + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: const Color.fromARGB(255, 48, 48, 48), + ), textAlign: TextAlign.center, ), ], diff --git a/packages/uni_app/lib/view/home/edit_home.dart b/packages/uni_app/lib/view/home/edit_home.dart new file mode 100644 index 000000000..6724357d7 --- /dev/null +++ b/packages/uni_app/lib/view/home/edit_home.dart @@ -0,0 +1,269 @@ +import 'package:figma_squircle/figma_squircle.dart'; +import 'package:flutter/material.dart'; +import 'package:uni/controller/local_storage/preferences_controller.dart'; +import 'package:uni/utils/favorite_widget_type.dart'; +import 'package:uni/utils/navigation_items.dart'; +import 'package:uni/view/home/widgets/edit/draggable_square.dart'; +import 'package:uni/view/home/widgets/edit/draggable_tile.dart'; +import 'package:uni_ui/icons.dart'; + +class EditHomeView extends StatefulWidget { + const EditHomeView({super.key}); + + @override + State createState() => EditHomeViewState(); +} + +class EditHomeViewState extends State { + List activeCards = []; + List listlessCards = []; + + (String, Icon) formatDraggableTile(FavoriteWidgetType favorite) { + String title; + Icon icon; + + switch (favorite.name) { + case 'schedule': + title = 'Schedule'; + icon = const UniIcon(UniIcons.lecture); + case 'exams': + title = 'Exams'; + icon = const UniIcon(UniIcons.exam); + case 'library': + title = 'Library'; + icon = const UniIcon(UniIcons.library); + case 'restaurants': + title = 'Restaurants'; + icon = const UniIcon(UniIcons.restaurant); + case 'calendar': + title = 'Calendar'; + icon = const UniIcon(UniIcons.calendar); + case 'ucs': + title = 'UCS'; + icon = const UniIcon(UniIcons.graduationCap); + default: + title = ''; + icon = const UniIcon(UniIcons.graduationCap); + } + return (title, icon); + } + + void removeActiveWhileDragging(DraggableTile draggable) { + setState(() { + activeCards.remove(draggable); + }); + } + + void removeListlessWhileDragging(DraggableSquare draggable) { + setState(() { + listlessCards.remove(draggable); + }); + } + + @override + void initState() { + super.initState(); + + const allCards = FavoriteWidgetType.values; + final favoriteCards = PreferencesController.getFavoriteCards(); + + activeCards = favoriteCards.map((favorite) { + final data = formatDraggableTile(favorite); + return DraggableTile( + icon: data.$2, + title: data.$1, + callback: removeActiveWhileDragging, + ); + }).toList(); + + listlessCards = + allCards.where((card) => !favoriteCards.contains(card)).map((favorite) { + final data = formatDraggableTile(favorite); + return DraggableSquare( + icon: data.$2, + title: data.$1, + callback: removeListlessWhileDragging, + ); + }).toList(); + } + + void saveCards() { + PreferencesController.saveFavoriteCards( + activeCards + .map( + (tile) => FavoriteWidgetType.values.firstWhere( + (favorite) => favorite.name == tile.title.toLowerCase(), + ), + ) + .toList(), + ); + } + + void addCard((String, Icon) card) { + setState(() { + activeCards.add( + DraggableTile( + icon: card.$2, + title: card.$1, + callback: removeActiveWhileDragging, + ), + ); + }); + + saveCards(); + } + + void removeCard((String, Icon) card) { + setState(() { + listlessCards.add( + DraggableSquare( + icon: card.$2, + title: card.$1, + callback: removeListlessWhileDragging, + ), + ); + }); + + saveCards(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.surface, + appBar: PreferredSize( + preferredSize: const Size.fromHeight(125), + child: Container( + height: 90, + decoration: const BoxDecoration( + gradient: RadialGradient( + colors: [ + Color(0xFF280709), + Color(0xFF511515), + ], + center: Alignment.topLeft, + radius: 1.5, + stops: [0, 1], + ), + ), + child: SafeArea( + child: Center( + child: Text( + 'Drag and drop elements', + style: Theme.of(context) + .textTheme + .titleLarge, // titleMedium as in figma is with the wrong colors + ), + ), + ), + ), + ), + body: DragTarget<(String, Icon)>( + builder: (context, candidate, rejected) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), + child: ListView.separated( + itemBuilder: (_, index) { + if (index < activeCards.length) { + return activeCards[index]; + } else if (candidate.isNotEmpty) { + return ClipSmoothRect( + radius: SmoothBorderRadius( + cornerRadius: 15, + cornerSmoothing: 1, + ), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .secondary + .withOpacity(0.75), + ), + child: const ListTile(), + ), + ); + } + return Container(); + }, + itemCount: activeCards.length + (candidate.isNotEmpty ? 1 : 0), + separatorBuilder: (_, __) => const SizedBox( + height: 15, + ), + ), + ); + }, + onAcceptWithDetails: (details) => addCard(details.data), + ), + bottomNavigationBar: DragTarget<(String, Icon)>( + builder: (context, candidate, rejected) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 35), + height: 350, + decoration: const BoxDecoration( + gradient: RadialGradient( + colors: [ + Color(0xFF280709), + Color(0xFF511515), + ], + center: Alignment.topLeft, + radius: 1.5, + stops: [0, 1], + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Available elements', + style: Theme.of(context) + .textTheme + .titleLarge, // TODO: titleMedium not working + ), + SizedBox( + width: double.infinity, + child: Wrap( + alignment: WrapAlignment.center, + spacing: 20, + runSpacing: 10, + children: candidate.isEmpty + ? [...listlessCards] + : [ + ...listlessCards, + ClipSmoothRect( + radius: SmoothBorderRadius( + cornerRadius: 15, + cornerSmoothing: 1, + ), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .secondary + .withOpacity(0.25), + ), + width: 75, + height: 75, + ), + ), + ], + ), + ), + TextButton( + onPressed: () { + Navigator.of(context) + .pushNamed('/${NavigationItem.navPersonalArea.route}'); + }, + child: Text( + 'Save', + style: Theme.of(context).textTheme.titleLarge, + ), + ), + ], + ), + ); + }, + onAcceptWithDetails: (details) => removeCard(details.data), + ), + ); + } +} diff --git a/packages/uni_app/lib/view/home/home.dart b/packages/uni_app/lib/view/home/home.dart index b6bed34ab..d4affdba3 100644 --- a/packages/uni_app/lib/view/home/home.dart +++ b/packages/uni_app/lib/view/home/home.dart @@ -1,14 +1,21 @@ import 'package:flutter/material.dart'; import 'package:uni/controller/local_storage/preferences_controller.dart'; -import 'package:uni/generated/l10n.dart'; +import 'package:uni/model/entities/lecture.dart'; +import 'package:uni/model/providers/lazy/lecture_provider.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/utils/navigation_items.dart'; +import 'package:uni/view/common_widgets/pages_layouts/general/widgets/bottom_navigation_bar.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'; -import 'package:uni/view/home/widgets/main_cards_list.dart'; +import 'package:uni/view/home/widgets/exams/exam_home_card.dart'; +import 'package:uni/view/home/widgets/generic_home_card.dart'; +import 'package:uni/view/home/widgets/library/library_home_card.dart'; +import 'package:uni/view/home/widgets/restaurants/restaurant_home_card.dart'; +import 'package:uni/view/home/widgets/schedule/schedule_home_card.dart'; import 'package:uni/view/home/widgets/tracking_banner.dart'; import 'package:uni/view/home/widgets/uni_logo.dart'; +import 'package:uni/view/lazy_consumer.dart'; +import 'package:uni_ui/cards/schedule_card.dart'; +import 'package:uni_ui/icons.dart'; class HomePageView extends StatefulWidget { const HomePageView({super.key}); @@ -17,12 +24,22 @@ class HomePageView extends StatefulWidget { State createState() => HomePageViewState(); } -class HomePageViewState extends GeneralPageViewState { - bool isBannerViewed = true; - bool isEditing = false; - List favoriteCardTypes = +class HomePageViewState extends State { + List favoriteCards = PreferencesController.getFavoriteCards(); + bool isBannerViewed = true; + + double appBarSize = 100; + + static Map typeToCard = { + FavoriteWidgetType.schedule: const ScheduleHomeCard(), + FavoriteWidgetType.exams: const ExamHomeCard(), + FavoriteWidgetType.library: const LibraryHomeCard(), + FavoriteWidgetType.restaurants: const RestaurantHomeCard(), + // FavoriteWidgetType.calendar: const CalendarHomeCard(), TODO: enable this when dates are properly formatted + }; + @override void initState() { super.initState(); @@ -40,99 +57,109 @@ class HomePageViewState extends GeneralPageViewState { await checkBannerViewed(); } - void setFavoriteCards(List favorites) { - setState(() { - favoriteCardTypes = favorites; - }); - 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, + Widget build(BuildContext context) { + return Scaffold( + extendBody: true, + floatingActionButtonLocation: FloatingActionButtonLocation.endTop, + floatingActionButton: FloatingActionButton( + backgroundColor: Theme.of(context).primaryColor, + foregroundColor: Colors.white, + shape: const CircleBorder(), + onPressed: () => { + Navigator.pushNamed( + context, + '/${NavigationItem.navEditPersonalArea.route}', ), - 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, - ), - ), - ], + }, + child: const UniIcon(UniIcons.edit), + ), + backgroundColor: Theme.of(context).colorScheme.surface, + appBar: homeAppBar(context), + bottomNavigationBar: const AppBottomNavbar(), + body: Padding( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), + child: ListView.separated( + itemCount: favoriteCards.length + 1, + separatorBuilder: (_, __) => const SizedBox( + height: 10, + ), + itemBuilder: (_, index) { + if (index == 0) { + return Visibility( + visible: !isBannerViewed, + child: TrackingBanner(setBannerViewed), + ); + } else { + return typeToCard[favoriteCards[index - 1]]; + } + }, + ), ), ); } - void toggleEditing() { - setState(() { - isEditing = !isEditing; - }); - } - - @override - Widget getBody(BuildContext context) { - return Column( - children: [ - Visibility( - visible: !isBannerViewed, - child: TrackingBanner(setBannerViewed), + PreferredSize homeAppBar(BuildContext context) { + return PreferredSize( + preferredSize: Size.fromHeight(appBarSize), + child: Container( + decoration: const BoxDecoration( + gradient: RadialGradient( + colors: [ + Color(0xFF280709), + Color(0xFF511515), + ], + center: Alignment.topLeft, + radius: 1.5, + stops: [0, 1], + ), ), - Expanded( - child: MainCardsList( - favoriteCardTypes, - saveFavoriteCards: setFavoriteCards, - isEditing: isEditing, - toggleEditing: toggleEditing, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 25), + child: SingleChildScrollView( + child: Column( + children: [ + const SafeArea( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + UniLogo(iconColor: Colors.white), // TODO: #1450 + ProfileButton(), + ], + ), + ), + LazyConsumer>( + builder: (context, lectures) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (lectures.isNotEmpty) { + setState(() { + appBarSize = 200; + }); + } + }); + return Padding( + padding: const EdgeInsets.symmetric(vertical: 25), + child: ScheduleCard( + name: lectures[0].subject, + acronym: lectures[0].acronym, + room: lectures[0].room, + type: lectures[0].typeClass, + ), + ); + }, + hasContent: (lectures) => lectures.isNotEmpty, + onNullContent: const SizedBox.shrink(), + mapper: (lectures) => lectures + .where( + (lecture) => lecture.endTime.isAfter(DateTime.now()), + ) + .toList(), + ), + ], + ), ), ), - ], - ); - } - - @override - Future onRefresh(BuildContext context) async { - final cards = favoriteCardTypes - .map( - (e) => - MainCardsList.cardCreators[e]!(const Key(''), editingMode: false), - ) - .toList(); - - for (final card in cards) { - card.onRefresh(context); - } - } - - @override - String? getTitle() => null; - - @override - AppTopNavbar? getTopNavbar(BuildContext context) { - return const AppTopNavbar( - leftButton: Padding( - padding: EdgeInsets.symmetric(horizontal: 8), - child: UniLogo(), ), - rightButton: ProfileButton(), ); } } diff --git a/packages/uni_app/lib/view/home/widgets/bus_stop_card.dart b/packages/uni_app/lib/view/home/widgets/bus_stop_card.dart deleted file mode 100644 index 2f0895560..000000000 --- a/packages/uni_app/lib/view/home/widgets/bus_stop_card.dart +++ /dev/null @@ -1,143 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:uni/generated/l10n.dart'; -import 'package:uni/model/entities/bus_stop.dart'; -import 'package:uni/model/providers/lazy/bus_stop_provider.dart'; -import 'package:uni/utils/navigation_items.dart'; -import 'package:uni/view/bus_stop_next_arrivals/widgets/bus_stop_row.dart'; -import 'package:uni/view/bus_stop_selection/bus_stop_selection.dart'; -import 'package:uni/view/common_widgets/generic_card.dart'; -import 'package:uni/view/common_widgets/last_update_timestamp.dart'; -import 'package:uni/view/lazy_consumer.dart'; - -/// Manages the bus stops card displayed on the user's personal area -class BusStopCard extends GenericCard { - BusStopCard({super.key}); - - const BusStopCard.fromEditingInformation( - super.key, { - required super.editingMode, - super.onDelete, - }) : super.fromEditingInformation(); - - @override - String getTitle(BuildContext context) => - S.of(context).nav_title(NavigationItem.navStops.route); - - @override - Future onClick(BuildContext context) => - Navigator.pushNamed(context, '/${NavigationItem.navStops.route}'); - - @override - Widget buildCardContent(BuildContext context) { - return LazyConsumer>( - contentLoadingWidget: Column( - children: [ - getCardTitle(context), - Container( - padding: const EdgeInsets.all(22), - child: const Center(child: CircularProgressIndicator()), - ), - ], - ), - builder: (context, stopData) => Column( - children: [ - getCardTitle(context), - getBusStopsInfo(context, stopData), - ], - ), - hasContent: (busStops) => busStops.isNotEmpty, - onNullContent: Container( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - S.of(context).buses_personalize, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.titleSmall!.apply(), - ), - IconButton( - icon: const Icon(Icons.settings), - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const BusStopSelectionPage(), - ), - ), - ), - ], - ), - ), - ); - } - - @override - void onRefresh(BuildContext context) { - Provider.of(context, listen: false).forceRefresh(context); - } -} - -/// Returns a widget for the title of the bus stops card -Widget getCardTitle(BuildContext context) { - return Row( - children: [ - const Icon(Icons.directions_bus), - Text( - S.of(context).stcp_stops, - style: Theme.of(context).textTheme.titleMedium, - ), - ], - ); -} - -/// Returns a widget for all the bus stops info -Widget getBusStopsInfo( - BuildContext context, - Map stopData, -) { - if (stopData.isNotEmpty) { - return Container( - padding: const EdgeInsets.all(4), - child: Column( - children: getEachBusStopInfo(context, stopData), - ), - ); - } else { - return Center( - child: Text( - S.of(context).no_data, - maxLines: 2, - overflow: TextOverflow.fade, - ), - ); - } -} - -/// Returns a list of widgets for each bus stop info that exists -List getEachBusStopInfo( - BuildContext context, - Map stopData, -) { - final rows = [ - const LastUpdateTimeStamp(), - ]; - - stopData.forEach((stopCode, stopInfo) { - if (stopInfo.trips.isNotEmpty && stopInfo.favorited) { - rows.add( - Container( - padding: const EdgeInsets.only(top: 12), - child: BusStopRow( - stopCode: stopCode, - trips: stopInfo.trips, - singleTrip: true, - ), - ), - ); - } - }); - - return rows; -} diff --git a/packages/uni_app/lib/view/home/widgets/calendar/calendar_home_card.dart b/packages/uni_app/lib/view/home/widgets/calendar/calendar_home_card.dart new file mode 100644 index 000000000..ba670ddfc --- /dev/null +++ b/packages/uni_app/lib/view/home/widgets/calendar/calendar_home_card.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:uni/model/entities/calendar_event.dart'; +import 'package:uni/model/providers/lazy/calendar_provider.dart'; +import 'package:uni/utils/navigation_items.dart'; +import 'package:uni/view/home/widgets/generic_home_card.dart'; +import 'package:uni/view/lazy_consumer.dart'; +import 'package:uni_ui/calendar/calendar.dart'; +import 'package:uni_ui/calendar/calendar_item.dart'; + +class CalendarHomeCard extends GenericHomecard { + const CalendarHomeCard({ + super.key, + super.title = 'Calendar', + super.externalInfo = true, + }); + + @override + void onClick(BuildContext context) => + Navigator.pushNamed(context, '/${NavigationItem.navCalendar.route}'); + + @override + Widget buildCardContent(BuildContext context) { + return LazyConsumer>( + builder: (context, events) { + return Calendar( + items: buildCalendarItems(events), + ); + }, + hasContent: (events) => events.isNotEmpty, + onNullContent: const Center( + child: Text( + 'Nenhum evento encontrado', + style: TextStyle(fontSize: 18), + ), + ), + ); + } + + List buildCalendarItems(List events) { + final items = events + .map( + (event) => CalendarItem( + eventPeriod: event.date, + eventName: event.name, + ), + ) + .toList(); + + return items; // TODO: wait for calendar events date regex + } +} + +/* +const CalendarItem({ + super.key, + required this.eventName, + this.eventPeriod, + this.endYear, + this.onTap, + }); +*/ diff --git a/packages/uni_app/lib/view/home/widgets/edit/draggable_element.dart b/packages/uni_app/lib/view/home/widgets/edit/draggable_element.dart new file mode 100644 index 000000000..e57379a28 --- /dev/null +++ b/packages/uni_app/lib/view/home/widgets/edit/draggable_element.dart @@ -0,0 +1,44 @@ +import 'package:figma_squircle/figma_squircle.dart'; +import 'package:flutter/material.dart'; + +class DraggableElement extends StatelessWidget { + const DraggableElement({ + super.key, + required this.child, + required this.feedback, + required this.data, + this.callback, + }); + + final Widget child; + final Widget feedback; + final Object data; + final void Function()? callback; + + @override + Widget build(BuildContext context) { + return LongPressDraggable( + // dragAnchorStrategy: pointerDragAnchorStrategy, + data: data, + feedback: ClipSmoothRect( + radius: SmoothBorderRadius( + cornerRadius: 15, + cornerSmoothing: 1, + ), + child: feedback, + ), + onDragStarted: () { + if (callback != null) { + callback!.call(); + } + }, + child: ClipSmoothRect( + radius: SmoothBorderRadius( + cornerRadius: 15, + cornerSmoothing: 1, + ), + child: child, + ), + ); + } +} diff --git a/packages/uni_app/lib/view/home/widgets/edit/draggable_square.dart b/packages/uni_app/lib/view/home/widgets/edit/draggable_square.dart new file mode 100644 index 000000000..733dcbc7f --- /dev/null +++ b/packages/uni_app/lib/view/home/widgets/edit/draggable_square.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:uni/view/home/widgets/edit/draggable_element.dart'; + +class DraggableSquare extends StatelessWidget { + const DraggableSquare({ + super.key, + required this.icon, + required this.title, + this.callback, + }); + + final Icon icon; + final String title; + final void Function(DraggableSquare)? callback; + + void activeCallback() { + if (callback != null) { + callback!.call(this); + } + } + + @override + Widget build(BuildContext context) { + return DraggableElement( + callback: activeCallback, + data: (title, icon), + feedback: Container( + decoration: + BoxDecoration(color: Theme.of(context).colorScheme.secondary), + width: 75, + height: 75, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + icon, + const SizedBox( + height: 5, + ), + Text( + title, + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + ), + child: Container( + decoration: + BoxDecoration(color: Theme.of(context).colorScheme.secondary), + width: 75, + height: 75, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + icon, + const SizedBox( + height: 5, + ), + Text( + title, + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + ), + ); + } +} diff --git a/packages/uni_app/lib/view/home/widgets/edit/draggable_tile.dart b/packages/uni_app/lib/view/home/widgets/edit/draggable_tile.dart new file mode 100644 index 000000000..c18601720 --- /dev/null +++ b/packages/uni_app/lib/view/home/widgets/edit/draggable_tile.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:uni/view/home/widgets/edit/draggable_element.dart'; +import 'package:uni_ui/icons.dart'; + +class DraggableTile extends StatelessWidget { + const DraggableTile({ + super.key, + required this.icon, + required this.title, + this.callback, + }); + + final Icon icon; + final String title; + final void Function(DraggableTile)? callback; + + void activeCallback() { + if (callback != null) { + callback!.call(this); + } + } + + @override + Widget build(BuildContext context) { + return DraggableElement( + callback: activeCallback, + data: (title, icon), + feedback: Container( + decoration: + BoxDecoration(color: Theme.of(context).colorScheme.secondary), + width: 75, + height: 75, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + icon, + const SizedBox( + height: 5, + ), + Text( + title, + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + ), // TODO: maybe list tile as well + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.secondary, + ), + child: ListTile( + trailing: const UniIcon(UniIcons.more), + title: Text( + title, + style: Theme.of(context).textTheme.headlineSmall, + ), + leading: icon, + ), + ), + ); + } +} diff --git a/packages/uni_app/lib/view/home/widgets/exam_card.dart b/packages/uni_app/lib/view/home/widgets/exam_card.dart deleted file mode 100644 index aa5295613..000000000 --- a/packages/uni_app/lib/view/home/widgets/exam_card.dart +++ /dev/null @@ -1,123 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:uni/controller/local_storage/preferences_controller.dart'; -import 'package:uni/generated/l10n.dart'; -import 'package:uni/model/entities/exam.dart'; -import 'package:uni/model/providers/lazy/exam_provider.dart'; -import 'package:uni/utils/navigation_items.dart'; -import 'package:uni/view/common_widgets/generic_card.dart'; -import 'package:uni/view/home/widgets/exam_card_shimmer.dart'; -import 'package:uni/view/home/widgets/next_exams_card.dart'; -import 'package:uni/view/home/widgets/remaining_exams_card.dart'; -import 'package:uni/view/lazy_consumer.dart'; - -/// Manages the exam card section inside the personal area. -class ExamCard extends GenericCard { - ExamCard({super.key}); - - const ExamCard.fromEditingInformation( - super.key, { - required super.editingMode, - super.onDelete, - }) : super.fromEditingInformation(); - - static const int maxExamsToDisplay = 4; - - @override - String getTitle(BuildContext context) => - S.of(context).nav_title(NavigationItem.navExams.route); - - @override - Future onClick(BuildContext context) => - Navigator.pushNamed(context, '/${NavigationItem.navExams.route}'); - - @override - void onRefresh(BuildContext context) { - Provider.of(context, listen: false).forceRefresh(context); - } - - @override - Widget buildCardContent(BuildContext context) { - return StreamBuilder( - stream: PreferencesController.onHiddenExamsChange, - initialData: PreferencesController.getHiddenExams(), - builder: (context, snapshot) { - final hiddenExams = snapshot.data ?? []; - - return LazyConsumer>( - builder: (context, allExams) { - final visibleExams = - getVisibleExams(allExams, hiddenExams).toList(); - - final nextExams = getPrimaryExams( - visibleExams, - ); - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - NextExamsWidget(exams: nextExams), - if (nextExams.length < maxExamsToDisplay && - visibleExams.length > nextExams.length) - Column( - children: [ - Container( - margin: const EdgeInsets.only( - right: 80, - left: 80, - top: 7, - bottom: 7, - ), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Theme.of(context).dividerColor, - ), - ), - ), - ), - RemainingExamsWidget( - exams: visibleExams - .where((exam) => !nextExams.contains(exam)) - .take(maxExamsToDisplay - nextExams.length) - .toList(), - ), - ], - ), - ], - ); - }, - hasContent: (allExams) => - getVisibleExams(allExams, hiddenExams).isNotEmpty, - onNullContent: Center( - child: Text( - S.of(context).no_selected_exams, - style: Theme.of(context).textTheme.titleLarge, - ), - ), - contentLoadingWidget: const ExamCardShimmer(), - ); - }, - ); - } - - Iterable getVisibleExams( - List allExams, - List hiddenExams, - ) { - final hiddenExamsSet = Set.from(hiddenExams); - return allExams.where((exam) => !hiddenExamsSet.contains(exam.id)); - } - - List getPrimaryExams(List visibleExams) { - return visibleExams - .where((exam) => isSameDay(visibleExams[0].start, exam.start)) - .toList(); - } - - bool isSameDay(DateTime? dateA, DateTime? dateB) { - return dateA?.year == dateB?.year && - dateA?.month == dateB?.month && - dateA?.day == dateB?.day; - } -} diff --git a/packages/uni_app/lib/view/home/widgets/exam_card_shimmer.dart b/packages/uni_app/lib/view/home/widgets/exam_card_shimmer.dart deleted file mode 100644 index 837ffe4ca..000000000 --- a/packages/uni_app/lib/view/home/widgets/exam_card_shimmer.dart +++ /dev/null @@ -1,93 +0,0 @@ -import 'package:flutter/material.dart'; - -class ExamCardShimmer extends StatelessWidget { - const ExamCardShimmer({super.key}); - - @override - Widget build(BuildContext context) { - return Center( - child: Container( - padding: const EdgeInsets.only(left: 12, bottom: 8, right: 12), - margin: const EdgeInsets.only(top: 8), - child: Column( - children: [ - Container( - margin: const EdgeInsets.only(top: 8, bottom: 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - // timestamp section - Container( - height: 15, - width: 40, - color: Colors.black, - ), - const SizedBox( - height: 2.5, - ), - Container( - height: 15, - width: 40, - color: Colors.black, - ), - ], - ), - Container( - height: 30, - width: 100, - color: Colors.black, - ), // UC section - Container( - height: 40, - width: 40, - color: Colors.black, - ), // Calender add section - ], - ), - ), - const SizedBox( - height: 10, - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: 15, - width: 40, - color: Colors.black, - ), - const SizedBox( - width: 10, - ), - Container( - height: 15, - width: 40, - color: Colors.black, - ), - const SizedBox( - width: 10, - ), - Container( - height: 15, - width: 40, - color: Colors.black, - ), - const SizedBox( - width: 10, - ), - Container( - height: 15, - width: 40, - color: Colors.black, - ), - ], - ), - ], - ), - ), - ); - } -} diff --git a/packages/uni_app/lib/view/home/widgets/exams/exam_home_card.dart b/packages/uni_app/lib/view/home/widgets/exams/exam_home_card.dart new file mode 100644 index 000000000..fd25e1d8f --- /dev/null +++ b/packages/uni_app/lib/view/home/widgets/exams/exam_home_card.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:uni/controller/local_storage/preferences_controller.dart'; +import 'package:uni/model/entities/exam.dart'; +import 'package:uni/model/providers/lazy/exam_provider.dart'; +import 'package:uni/utils/date_time_formatter.dart'; +import 'package:uni/utils/string_formatter.dart'; +import 'package:uni/view/home/widgets/generic_home_card.dart'; +import 'package:uni/view/home/widgets/schedule/timeline_shimmer.dart'; +import 'package:uni/view/lazy_consumer.dart'; +import 'package:uni/view/locale_notifier.dart'; +import 'package:uni_ui/cards/exam_card.dart'; +import 'package:uni_ui/cards/timeline_card.dart'; + +class ExamHomeCard extends GenericHomecard { + const ExamHomeCard({ + super.title = 'Exams', + super.key, + }); + + @override + Widget buildCardContent(BuildContext context) { + return StreamBuilder( + stream: PreferencesController.onHiddenExamsChange, + initialData: PreferencesController.getHiddenExams(), + builder: (context, snapshot) { + final hiddenExams = snapshot.data ?? []; + + return LazyConsumer>( + builder: (context, allExams) { + final visibleExams = + getVisibleExams(allExams, hiddenExams).toList(); + final items = + buildTimelineItems(context, visibleExams).sublist(0, 2); + + return CardTimeline(items: items); + }, + hasContent: (allExams) => + getVisibleExams(allExams, hiddenExams).isNotEmpty, + onNullContent: const Center( + child: Text('Sem exames'), + ), + contentLoadingWidget: const ShimmerCardTimeline(), + ); + }, + ); + } + + Iterable getVisibleExams( + List allExams, + List hiddenExams, + ) { + final hiddenExamsSet = Set.from(hiddenExams); + return allExams.where((exam) => !hiddenExamsSet.contains(exam.id)); + } + + List buildTimelineItems( + BuildContext context, + List exams, + ) { + final items = exams + .map( + (exam) => TimelineItem( + title: exam.start.day.toString(), + subtitle: exam.start + .shortMonth( + Provider.of(context).getLocale(), + ) + .capitalize(), + card: ExamCard( + showIcon: false, + name: exam.subject, + acronym: exam.subjectAcronym, + rooms: exam.rooms, + type: exam.examType, + ), + ), + ) + .toList(); + + return items; + } + + @override + void onClick(BuildContext context) => {}; +} diff --git a/packages/uni_app/lib/view/home/widgets/generic_home_card.dart b/packages/uni_app/lib/view/home/widgets/generic_home_card.dart new file mode 100644 index 000000000..7453ca704 --- /dev/null +++ b/packages/uni_app/lib/view/home/widgets/generic_home_card.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; + +abstract class GenericHomecard extends StatelessWidget { + const GenericHomecard({ + required this.title, + this.externalInfo = false, + super.key, + }); + + final String title; + final bool externalInfo; + + Widget buildCardContent(BuildContext context); + + void onClick(BuildContext context); + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: const BoxConstraints( + minHeight: 60, + ), + child: SizedBox( + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: Theme.of(context).textTheme.headlineLarge, + ), + if (externalInfo) + TextButton( + style: ButtonStyle( + textStyle: WidgetStateProperty.all( + Theme.of(context).textTheme.headlineSmall, + ), + splashFactory: NoSplash.splashFactory, + ), + onPressed: () => onClick(context), + child: const Text('Ver mais'), + ), + ], + ), + Container( + margin: const EdgeInsets.only(top: 10), + child: buildCardContent(context), + ), + ], + ), + ), + ); + } +} diff --git a/packages/uni_app/lib/view/home/widgets/library/library_card_shimmer.dart b/packages/uni_app/lib/view/home/widgets/library/library_card_shimmer.dart new file mode 100644 index 000000000..9a703f2fc --- /dev/null +++ b/packages/uni_app/lib/view/home/widgets/library/library_card_shimmer.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; +import 'package:uni_ui/common/generic_squircle.dart'; + +class ShimmerLibraryHomeCard extends StatelessWidget { + const ShimmerLibraryHomeCard({super.key}); + + @override + Widget build(BuildContext context) { + return Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: GenericSquircle( + child: Container( + height: 200, + decoration: const BoxDecoration(color: Colors.white), + ), + ), + ); + } +} diff --git a/packages/uni_app/lib/view/home/widgets/library/library_home_card.dart b/packages/uni_app/lib/view/home/widgets/library/library_home_card.dart new file mode 100644 index 000000000..516f109be --- /dev/null +++ b/packages/uni_app/lib/view/home/widgets/library/library_home_card.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:uni/generated/l10n.dart'; +import 'package:uni/model/entities/library_occupation.dart'; +import 'package:uni/model/providers/lazy/library_occupation_provider.dart'; +import 'package:uni/view/home/widgets/generic_home_card.dart'; +import 'package:uni/view/home/widgets/library/library_card_shimmer.dart'; +import 'package:uni/view/lazy_consumer.dart'; +import 'package:uni_ui/cards/library_occupation_card.dart'; + +class LibraryHomeCard extends GenericHomecard { + const LibraryHomeCard({ + super.key, + super.title = 'Library Occupation', + }); + + @override + void onClick(BuildContext context) => {}; + + @override + Widget buildCardContent(BuildContext context) { + return LazyConsumer( + builder: (context, libraryOccupation) => LibraryOccupationCard( + capacity: libraryOccupation.capacity, + occupation: libraryOccupation.occupation, + occupationWidgetsList: + buildFloorOccupation(context, libraryOccupation.floors), + ), + hasContent: (libraryOccupation) => true, + onNullContent: const CircularProgressIndicator(), + contentLoadingWidget: const ShimmerLibraryHomeCard(), + ); + } +} + +List buildFloorOccupation( + BuildContext context, + List floors, +) { + final items = floors + .map( + (floor) => FloorOccupationWidget( + capacity: floor.capacity, + occupation: floor.occupation, + floorText: S.of(context).floor, + floorNumber: floor.number, + ), + ) + .toList(); + + return items; +} + +/* + +class FloorOccupationWidget extends StatelessWidget { + final int capacity; + final int occupation; + final String floorText; + final int floorNumber; + +*/ diff --git a/packages/uni_app/lib/view/home/widgets/main_cards_list.dart b/packages/uni_app/lib/view/home/widgets/main_cards_list.dart deleted file mode 100644 index 95b1b7674..000000000 --- a/packages/uni_app/lib/view/home/widgets/main_cards_list.dart +++ /dev/null @@ -1,189 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -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/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'; -import 'package:uni/view/home/widgets/restaurant_card.dart'; -import 'package:uni/view/home/widgets/schedule_card.dart'; -import 'package:uni/view/library/widgets/library_occupation_card.dart'; - -typedef CardCreator = GenericCard Function( - Key key, { - required bool editingMode, - void Function()? onDelete, -}); - -class MainCardsList extends StatefulWidget { - const MainCardsList( - this.favoriteCardTypes, { - required this.saveFavoriteCards, - required this.isEditing, - required this.toggleEditing, - super.key, - }); - - final List favoriteCardTypes; - final void Function(List) saveFavoriteCards; - final bool isEditing; - final void Function() toggleEditing; - - static Map cardCreators = { - FavoriteWidgetType.schedule: ScheduleCard.fromEditingInformation, - FavoriteWidgetType.exams: ExamCard.fromEditingInformation, - FavoriteWidgetType.busStops: BusStopCard.fromEditingInformation, - FavoriteWidgetType.restaurant: RestaurantCard.fromEditingInformation, - FavoriteWidgetType.libraryOccupation: - LibraryOccupationCard.fromEditingInformation, - }; - - @override - State createState() { - return MainCardsListState(); - } -} - -class MainCardsListState extends State { - @override - Widget build(BuildContext context) { - // ignore: deprecated_member_use, see #1209 - 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, - ), - ], - ), - ), - ), - floatingActionButton: - widget.isEditing ? createActionButton(context) : null, - ); - } - - Widget createActionButton(BuildContext context) { - return FloatingActionButton( - backgroundColor: Theme.of(context).colorScheme.secondary, - onPressed: () => showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text( - S.of(context).widget_prompt, - style: Theme.of(context).textTheme.headlineSmall, - ), - content: SizedBox( - height: 200, - width: 100, - child: ListView(children: getCardAdders(context)), - ), - actions: [ - TextButton( - child: Text( - S.of(context).cancel, - style: Theme.of(context).textTheme.bodyMedium, - ), - onPressed: () => Navigator.pop(context), - ), - ], - ); - }, - ), // Add FAB functionality here - tooltip: S.of(context).add_widget, - child: Icon(Icons.add, color: Theme.of(context).colorScheme.onPrimary), - ); - } - - List getCardAdders(BuildContext context) { - final session = Provider.of(context, listen: false).state!; - - final possibleCardAdditions = MainCardsList.cardCreators.entries - .where((e) => e.key.isVisible(session.faculties)) - .where((e) => !widget.favoriteCardTypes.contains(e.key)) - .map( - (e) => DecoratedBox( - decoration: const BoxDecoration(), - child: ListTile( - title: Text( - e - .value(Key(e.key.index.toString()), editingMode: false) - .getTitle(context), - textAlign: TextAlign.center, - ), - onTap: () { - addCardToFavorites(e.key, context); - Navigator.pop(context); - }, - ), - ), - ) - .toList(); - - return possibleCardAdditions.isEmpty - ? [Text(S.of(context).all_widgets_added)] - : possibleCardAdditions; - } - - List favoriteCardsFromTypes( - List cardTypes, - BuildContext context, - ) { - final userSession = - Provider.of(context, listen: false).state; - return cardTypes - .where((type) => type.isVisible(userSession?.faculties ?? [])) - .where((type) => MainCardsList.cardCreators.containsKey(type)) - .map((type) { - final i = cardTypes.indexOf(type); - return MainCardsList.cardCreators[type]!( - Key(i.toString()), - editingMode: widget.isEditing, - onDelete: () => removeCardIndexFromFavorites(i, context), - ); - }).toList(); - } - - void reorderCard( - int oldIndex, - int newIndex, - ) { - final newFavorites = - List.from(widget.favoriteCardTypes); - final tmp = newFavorites[oldIndex]; - newFavorites - ..removeAt(oldIndex) - ..insert(oldIndex < newIndex ? newIndex - 1 : newIndex, tmp); - widget.saveFavoriteCards(newFavorites); - } - - void removeCardIndexFromFavorites(int i, BuildContext context) { - final favorites = List.from(widget.favoriteCardTypes) - ..removeAt(i); - widget.saveFavoriteCards(favorites); - } - - void addCardToFavorites(FavoriteWidgetType type, BuildContext context) { - final favorites = List.from(widget.favoriteCardTypes); - if (!favorites.contains(type)) { - favorites.add(type); - } - widget.saveFavoriteCards(favorites); - } -} diff --git a/packages/uni_app/lib/view/home/widgets/next_exams_card.dart b/packages/uni_app/lib/view/home/widgets/next_exams_card.dart deleted file mode 100644 index 45d8d3259..000000000 --- a/packages/uni_app/lib/view/home/widgets/next_exams_card.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:uni/model/entities/exam.dart'; -import 'package:uni/utils/date_time_formatter.dart'; -import 'package:uni/view/common_widgets/date_rectangle.dart'; -import 'package:uni/view/common_widgets/row_container.dart'; -import 'package:uni/view/exams/widgets/exam_row.dart'; -import 'package:uni/view/locale_notifier.dart'; - -class NextExamsWidget extends StatelessWidget { - const NextExamsWidget({required this.exams, super.key}); - - final List exams; - - @override - Widget build(BuildContext context) { - final locale = Provider.of(context).getLocale(); - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - DateRectangle( - date: exams.isNotEmpty ? exams.first.start.formattedDate(locale) : '', - ), - Column( - children: exams.map((exam) { - return Padding( - padding: const EdgeInsets.only(bottom: 8), - child: RowContainer( - child: ExamRow( - exam: exam, - teacher: '', - mainPage: true, - onChangeVisibility: () {}, - ), - ), - ); - }).toList(), - ), - ], - ); - } -} diff --git a/packages/uni_app/lib/view/home/widgets/remaining_exams_card.dart b/packages/uni_app/lib/view/home/widgets/remaining_exams_card.dart deleted file mode 100644 index 7e5f32ef9..000000000 --- a/packages/uni_app/lib/view/home/widgets/remaining_exams_card.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:uni/model/entities/exam.dart'; -import 'package:uni/utils/date_time_formatter.dart'; -import 'package:uni/view/common_widgets/row_container.dart'; -import 'package:uni/view/exams/widgets/exam_title.dart'; -import 'package:uni/view/locale_notifier.dart'; - -class RemainingExamsWidget extends StatelessWidget { - const RemainingExamsWidget({required this.exams, super.key}); - - final List exams; - - @override - Widget build(BuildContext context) { - return Column( - children: exams.map((exam) { - final locale = Provider.of(context).getLocale(); - return Container( - margin: const EdgeInsets.only(top: 8), - child: RowContainer( - color: Theme.of(context).colorScheme.surface, - child: Container( - padding: const EdgeInsets.all(11), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - exam.start.formattedDate(locale), - style: Theme.of(context).textTheme.bodyLarge, - ), - ExamTitle( - subject: exam.subjectAcronym, - subjectName: exam.subject, - type: exam.examType, - reverseOrder: true, - ), - ], - ), - ), - ), - ); - }).toList(), - ); - } -} diff --git a/packages/uni_app/lib/view/home/widgets/restaurant_card.dart b/packages/uni_app/lib/view/home/widgets/restaurant_card.dart deleted file mode 100644 index c9ce793c7..000000000 --- a/packages/uni_app/lib/view/home/widgets/restaurant_card.dart +++ /dev/null @@ -1,179 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:uni/controller/local_storage/preferences_controller.dart'; -import 'package:uni/generated/l10n.dart'; -import 'package:uni/model/entities/app_locale.dart'; -import 'package:uni/model/entities/meal.dart'; -import 'package:uni/model/entities/restaurant.dart'; -import 'package:uni/model/providers/lazy/restaurant_provider.dart'; -import 'package:uni/model/utils/day_of_week.dart'; -import 'package:uni/utils/navigation_items.dart'; -import 'package:uni/view/common_widgets/generic_card.dart'; -import 'package:uni/view/lazy_consumer.dart'; -import 'package:uni/view/locale_notifier.dart'; -import 'package:uni/view/restaurant/widgets/restaurant_slot.dart'; - -class RestaurantCard extends GenericCard { - RestaurantCard({super.key}); - - const RestaurantCard.fromEditingInformation( - super.key, { - required super.editingMode, - super.onDelete, - }) : super.fromEditingInformation(); - - @override - String getTitle(BuildContext context) => - S.of(context).nav_title(NavigationItem.navRestaurants.route); - - @override - Future onClick(BuildContext context) => - Navigator.pushNamed(context, '/${NavigationItem.navRestaurants.route}'); - - @override - void onRefresh(BuildContext context) { - Provider.of(context, listen: false) - .forceRefresh(context); - } - - @override - Widget buildCardContent(BuildContext context) { - return LazyConsumer>( - builder: (context, restaurants) { - final favoriteRestaurants = restaurants - .where( - (restaurant) => PreferencesController.getFavoriteRestaurants() - .contains(restaurant.namePt + restaurant.period), - ) - .toList(); - return generateRestaurants(favoriteRestaurants, context); - }, - hasContent: (restaurants) => - PreferencesController.getFavoriteRestaurants().isNotEmpty, - onNullContent: Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 10, bottom: 10), - child: Center( - child: Text( - S.of(context).no_favorite_restaurants, - style: Theme.of(context).textTheme.titleMedium, - ), - ), - ), - OutlinedButton( - onPressed: () => Navigator.pushNamed( - context, - '/${NavigationItem.navRestaurants.route}', - ), - child: Text(S.of(context).add), - ), - ], - ), - ); - } - - Widget generateRestaurants( - List restaurants, - BuildContext context, - ) { - final weekDay = DateTime.now().weekday; - final offset = (weekDay - 1) % 7; - - if (restaurants - .map((e) => e.meals[DayOfWeek.values[offset]]) - .every((element) => element?.isEmpty ?? true)) { - return Column( - children: [ - const SizedBox( - height: 15, - ), - Text( - S.of(context).no_menus, - style: Theme.of(context).textTheme.titleSmall, - ), - const SizedBox( - height: 15, - ), - ], - ); - } - - return ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: restaurants.length, - itemBuilder: (context, index) { - return createRowFromRestaurant( - context, - restaurants[index], - DayOfWeek.values[offset], - ); - }, - ); - } - - Widget createRowFromRestaurant( - BuildContext context, - Restaurant restaurant, - DayOfWeek day, - ) { - final meals = restaurant.getMealsOfDay(day); - var period = ''; - switch (restaurant.period) { - case 'lunch': - period = S.of(context).lunch; - case 'dinner': - period = S.of(context).dinner; - case 'breakfast': - period = S.of(context).breakfast; - case 'snackbar': - period = S.of(context).snackbar; - default: - period = ''; - } - final locale = Provider.of(context).getLocale(); - return Column( - children: [ - Center( - child: Container( - alignment: Alignment.centerLeft, - padding: const EdgeInsets.fromLTRB(10, 15, 5, 10), - child: Text( - '${locale == AppLocale.pt ? restaurant.namePt : restaurant.nameEn} - $period', - style: TextStyle( - fontSize: 16, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w400, - ), - ), - ), - ), - if (meals.isNotEmpty) - Column( - mainAxisSize: MainAxisSize.min, - children: createRestaurantRows(meals, context), - ) - else - Container( - padding: const EdgeInsets.fromLTRB(9, 0, 0, 0), - width: 400, - child: Text(S.of(context).no_menu_info), - ), - const SizedBox(height: 10), - ], - ); - } -} - -List createRestaurantRows(List meals, BuildContext context) { - return meals - .map( - (meal) => RestaurantSlot( - type: meal.type, - namePt: meal.namePt, - nameEn: meal.nameEn, - ), - ) - .toList(); -} diff --git a/packages/uni_app/lib/view/home/widgets/restaurant_row.dart b/packages/uni_app/lib/view/home/widgets/restaurant_row.dart deleted file mode 100644 index 49735a185..000000000 --- a/packages/uni_app/lib/view/home/widgets/restaurant_row.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; - -class RestaurantRow extends StatelessWidget { - const RestaurantRow({ - required this.local, - required this.meatMenu, - required this.fishMenu, - required this.vegetarianMenu, - required this.dietMenu, - super.key, - this.iconSize = 20.0, - }); - final String local; - final String meatMenu; - final String fishMenu; - final String vegetarianMenu; - final String dietMenu; - final double iconSize; - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.only(left: 12, bottom: 8, right: 12), - margin: const EdgeInsets.only(top: 8), - child: Expanded( - child: Column( - children: getMenuRows(context), - ), - ), - ); - } - - List getMenuRows(BuildContext context) { - final widgets = []; - final meals = [meatMenu, fishMenu, vegetarianMenu, dietMenu]; - final mealIcon = { - meatMenu: MdiIcons.foodDrumstickOutline, - fishMenu: MdiIcons.fish, - vegetarianMenu: MdiIcons.corn, - dietMenu: MdiIcons.nutrition, - }; - - for (final element in meals) { - widgets.add( - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: 0.7, - color: Theme.of(context).colorScheme.secondary, - ), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(mealIcon[element], size: iconSize), - Expanded(child: Text(element, textAlign: TextAlign.center)), - ], - ), - ), - ); - } - - return widgets; - } -} diff --git a/packages/uni_app/lib/view/home/widgets/restaurants/restaurant_home_card.dart b/packages/uni_app/lib/view/home/widgets/restaurants/restaurant_home_card.dart new file mode 100644 index 000000000..b1ddef36b --- /dev/null +++ b/packages/uni_app/lib/view/home/widgets/restaurants/restaurant_home_card.dart @@ -0,0 +1,164 @@ +import 'package:expandable_page_view/expandable_page_view.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_page_indicator/smooth_page_indicator.dart'; +import 'package:uni/controller/local_storage/preferences_controller.dart'; +import 'package:uni/model/entities/app_locale.dart'; +import 'package:uni/model/entities/restaurant.dart'; +import 'package:uni/model/providers/lazy/restaurant_provider.dart'; +import 'package:uni/model/utils/day_of_week.dart'; +import 'package:uni/utils/navigation_items.dart'; +import 'package:uni/view/home/widgets/generic_home_card.dart'; +import 'package:uni/view/home/widgets/restaurants/restaurants_card_shimmer.dart'; +import 'package:uni/view/lazy_consumer.dart'; +import 'package:uni/view/locale_notifier.dart'; +import 'package:uni/view/restaurant/widgets/restaurant_utils.dart'; +import 'package:uni_ui/cards/restaurant_card.dart'; +import 'package:uni_ui/cards/widgets/restaurant_menu_item.dart'; + +class RestaurantHomeCard extends GenericHomecard { + const RestaurantHomeCard({ + super.key, + super.title = 'Restaurants', + super.externalInfo = true, + }); + + @override + void onClick(BuildContext context) => + Navigator.pushNamed(context, '/${NavigationItem.navRestaurants.route}'); + + @override + Widget buildCardContent(BuildContext context) => const RestaurantSlider(); +} + +class RestaurantSlider extends StatefulWidget { + const RestaurantSlider({super.key}); + + @override + RestaurantSliderState createState() => RestaurantSliderState(); +} + +class RestaurantSliderState extends State { + int _currentIndex = 0; + + @override + Widget build(BuildContext context) { + return LazyConsumer>( + builder: (context, restaurants) { + final favoriteRestaurants = restaurants + .where( + (restaurant) => PreferencesController.getFavoriteRestaurants() + .contains(restaurant.namePt + restaurant.period), + ) + .toList(); + + final dailyRestaurants = + getRestaurantInformation(context, favoriteRestaurants); + + return Column( + children: [ + ExpandablePageView( + children: dailyRestaurants, + onPageChanged: (value) => setState(() { + _currentIndex = value; + }), + ), + const SizedBox( + height: 5, + ), + AnimatedSmoothIndicator( + activeIndex: _currentIndex, + count: dailyRestaurants.length, + effect: WormEffect( + dotHeight: 4, + dotWidth: 4, + activeDotColor: Theme.of(context).colorScheme.primary, + ), + ), + ], + ); + }, + hasContent: (restaurants) { + final favoriteRestaurants = restaurants + .where( + (restaurant) => PreferencesController.getFavoriteRestaurants() + .contains(restaurant.namePt + restaurant.period), + ) + .toList(); + return getRestaurantInformation(context, favoriteRestaurants) + .isNotEmpty; + }, + onNullContent: const Text('Nenhum restaurante favorito aberto'), + contentLoadingWidget: const ShimmerRestaurantsHomeCard(), + ); + } +} + +List getRestaurantInformation( + BuildContext context, + List favoriteRestaurants, +) { + final locale = Provider.of(context).getLocale(); + + final today = parseDateTime(DateTime.now()); + + final restaurantsWidgets = favoriteRestaurants + .where((element) => element.meals[today]?.isNotEmpty ?? false) + .map((restaurant) { + final menuItems = getMainMenus(today, restaurant, locale); + return RestaurantCard( + name: RestaurantUtils.getRestaurantName( + context, + locale, + restaurant.namePt, + restaurant.namePt, + restaurant.period, + ), + icon: RestaurantUtils.getIcon( + restaurant.typeEn ?? restaurant.typePt, + ), + isFavorite: PreferencesController.getFavoriteRestaurants() + .contains(restaurant.namePt + restaurant.period), + onFavoriteToggle: () => {}, + menuItems: menuItems, + ); + }).toList(); + + return restaurantsWidgets; +} + +List getMainMenus( + DayOfWeek dayOfWeek, + Restaurant restaurant, + AppLocale locale, +) { + final meals = restaurant.meals[dayOfWeek]; + + if (meals == null || meals.isEmpty) { + return []; + } + + final mainMeals = meals + .where( + (meal) => ['Carne', 'Vegetariano', 'Peixe', 'Pescado'] + .any((keyword) => meal.type.contains(keyword)), + ) + .toList(); + + final filteredMeals = mainMeals.isEmpty ? meals.take(2) : mainMeals; + + final menuItems = []; + for (final meal in filteredMeals) { + menuItems.add( + RestaurantMenuItem( + name: RestaurantUtils.getLocaleTranslation( + locale, + meal.namePt, + meal.nameEn, + ), + icon: RestaurantUtils.getIcon(meal.type), + ), + ); + } + return menuItems; +} diff --git a/packages/uni_app/lib/view/home/widgets/restaurants/restaurants_card_shimmer.dart b/packages/uni_app/lib/view/home/widgets/restaurants/restaurants_card_shimmer.dart new file mode 100644 index 000000000..011720605 --- /dev/null +++ b/packages/uni_app/lib/view/home/widgets/restaurants/restaurants_card_shimmer.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; +import 'package:uni_ui/common/generic_squircle.dart'; + +class ShimmerRestaurantsHomeCard extends StatelessWidget { + const ShimmerRestaurantsHomeCard({super.key}); + + @override + Widget build(BuildContext context) { + return Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: GenericSquircle( + child: Container( + height: 150, + decoration: const BoxDecoration(color: Colors.white), + ), + ), + ); + } +} diff --git a/packages/uni_app/lib/view/home/widgets/schedule/schedule_home_card.dart b/packages/uni_app/lib/view/home/widgets/schedule/schedule_home_card.dart new file mode 100644 index 000000000..c38615659 --- /dev/null +++ b/packages/uni_app/lib/view/home/widgets/schedule/schedule_home_card.dart @@ -0,0 +1,71 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:uni/model/entities/lecture.dart'; +import 'package:uni/model/providers/lazy/lecture_provider.dart'; +import 'package:uni/model/utils/time/week.dart'; +import 'package:uni/view/home/widgets/generic_home_card.dart'; +import 'package:uni/view/home/widgets/schedule/timeline_shimmer.dart'; +import 'package:uni/view/lazy_consumer.dart'; +import 'package:uni_ui/cards/schedule_card.dart'; +import 'package:uni_ui/cards/timeline_card.dart'; + +class ScheduleHomeCard extends GenericHomecard { + const ScheduleHomeCard({ + super.key, + super.title = 'Schedule', + }); + + @override + Widget buildCardContent(BuildContext context) { + return LazyConsumer>( + builder: (context, lectures) => CardTimeline( + items: buildTimelineItems(lectures).sublist(0, 2), + ), + hasContent: (lectures) => lectures.isNotEmpty, + onNullContent: Text( + 'Sem aulas', + style: Theme.of(context).textTheme.titleLarge, + textAlign: TextAlign.center, + ), + mapper: (lectures) => lectures + .where((lecture) => lecture.endTime.isAfter(DateTime.now())) + .toList(), + contentLoadingWidget: const ShimmerCardTimeline(), + ); + } + + @override + void onClick(BuildContext context) => {}; + + List buildTimelineItems(List lectures) { + final now = DateTime.now(); + final week = Week(start: now); + + final sortedLectures = lectures + .where((lecture) => week.contains(lecture.startTime)) + .toList() + .sortedBy((lecture) => week.getWeekday(lecture.startTime.weekday)); + + final items = sortedLectures + .map( + (element) => TimelineItem( + isActive: + now.isAfter(element.startTime) && now.isBefore(element.endTime), + title: DateFormat('HH:mm').format(element.startTime), + subtitle: DateFormat('HH:mm').format(element.endTime), + card: ScheduleCard( + isActive: now.isAfter(element.startTime) && + now.isBefore(element.endTime), + name: element.subject, + acronym: element.acronym, // TODO once schedule is merged + room: element.room, + type: element.typeClass, + ), + ), + ) + .toList(); + + return items; + } +} diff --git a/packages/uni_app/lib/view/home/widgets/schedule/timeline_shimmer.dart b/packages/uni_app/lib/view/home/widgets/schedule/timeline_shimmer.dart new file mode 100644 index 000000000..a9dbd6a2d --- /dev/null +++ b/packages/uni_app/lib/view/home/widgets/schedule/timeline_shimmer.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; +import 'package:uni_ui/common/generic_squircle.dart'; + +class ShimmerTimelineItem extends StatelessWidget { + const ShimmerTimelineItem({super.key}); + + @override + Widget build(BuildContext context) { + return Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 50, + child: Column( + children: [ + Container( + width: 40, + height: 16, + color: Colors.white, + ), + const SizedBox(height: 4), + Container( + width: 30, + height: 12, + color: Colors.white, + ), + ], + ), + ), + Column( + children: [ + Container( + margin: const EdgeInsets.only(bottom: 5, left: 10, right: 10), + width: 20, + height: 20, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.white, + ), + ), + Container( + margin: const EdgeInsets.only(bottom: 5, left: 10, right: 10), + height: 60, + width: 3, + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10)), + color: Colors.white, + ), + ), + ], + ), + Expanded( + child: GenericSquircle( + borderRadius: 10, + child: Container( + height: 70, + width: double.infinity, + color: Colors.white, + ), + ), + ), + ], + ), + ); + } +} + +class ShimmerCardTimeline extends StatelessWidget { + const ShimmerCardTimeline({super.key}); + + @override + Widget build(BuildContext context) { + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: 2, + itemBuilder: (context, index) => const ShimmerTimelineItem(), + ); + } +} diff --git a/packages/uni_app/lib/view/home/widgets/schedule_card.dart b/packages/uni_app/lib/view/home/widgets/schedule_card.dart deleted file mode 100644 index 32edc2752..000000000 --- a/packages/uni_app/lib/view/home/widgets/schedule_card.dart +++ /dev/null @@ -1,124 +0,0 @@ -import 'dart:math'; - -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:uni/generated/l10n.dart'; -import 'package:uni/model/entities/lecture.dart'; -import 'package:uni/model/providers/lazy/lecture_provider.dart'; -import 'package:uni/model/utils/time/week.dart'; -import 'package:uni/utils/navigation_items.dart'; -import 'package:uni/view/common_widgets/date_rectangle.dart'; -import 'package:uni/view/common_widgets/generic_card.dart'; -import 'package:uni/view/home/widgets/schedule_card_shimmer.dart'; -import 'package:uni/view/lazy_consumer.dart'; -import 'package:uni/view/locale_notifier.dart'; -import 'package:uni/view/schedule/widgets/schedule_slot.dart'; - -class ScheduleCard extends GenericCard { - ScheduleCard({super.key}); - - ScheduleCard.fromEditingInformation( - super.key, { - required super.editingMode, - super.onDelete, - }) : super.fromEditingInformation(); - - final double borderRadius = 12; - final double leftPadding = 12; - final List lectures = []; - - @override - void onRefresh(BuildContext context) { - Provider.of(context, listen: false).forceRefresh(context); - } - - @override - Widget buildCardContent(BuildContext context) { - return LazyConsumer>( - builder: (context, lectures) => Column( - mainAxisSize: MainAxisSize.min, - children: getScheduleRows(context, lectures), - ), - hasContent: (lectures) => lectures.isNotEmpty, - onNullContent: Center( - child: Text( - S.of(context).no_classes, - style: Theme.of(context).textTheme.titleLarge, - textAlign: TextAlign.center, - ), - ), - contentLoadingWidget: const ScheduleCardShimmer().build(context), - mapper: (lectures) => lectures - .where((lecture) => lecture.endTime.isAfter(DateTime.now())) - .toList(), - ); - } - - List getScheduleRows(BuildContext context, List lectures) { - final now = DateTime.now(); - final week = Week(start: now); - - final rows = []; - - final lecturesByDay = lectures - .where((lecture) => week.contains(lecture.startTime)) - .groupListsBy( - (lecture) => lecture.startTime.weekday, - ) - .entries - .toList() - .sortedBy((element) => week.getWeekday(element.key)) - .toList(); - - for (final dayLectures - in lecturesByDay.sublist(0, min(2, lecturesByDay.length))) { - final day = dayLectures.key; - - if (dayLectures.value.isEmpty) { - continue; - } - - rows.add( - DateRectangle( - date: Provider.of(context) - .getWeekdaysWithLocale()[(day - 1) % 7], - ), - ); - - for (final lecture in dayLectures.value) { - rows.add(createRowFromLecture(context, lecture)); - } - - if (dayLectures.value.length >= 2) { - break; - } - } - - return rows; - } - - Widget createRowFromLecture(BuildContext context, Lecture lecture) { - return Container( - margin: const EdgeInsets.only(bottom: 10), - child: ScheduleSlot( - subject: lecture.subject, - rooms: lecture.room, - begin: lecture.startTime, - end: lecture.endTime, - teacher: lecture.teacher, - typeClass: lecture.typeClass, - classNumber: lecture.classNumber, - occurrId: lecture.occurrId, - ), - ); - } - - @override - String getTitle(BuildContext context) => - S.of(context).nav_title(NavigationItem.navSchedule.route); - - @override - Future onClick(BuildContext context) => - Navigator.pushNamed(context, '/${NavigationItem.navSchedule.route}'); -} diff --git a/packages/uni_app/lib/view/home/widgets/schedule_card_shimmer.dart b/packages/uni_app/lib/view/home/widgets/schedule_card_shimmer.dart deleted file mode 100644 index 91728fd9d..000000000 --- a/packages/uni_app/lib/view/home/widgets/schedule_card_shimmer.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'package:flutter/material.dart'; - -class ScheduleCardShimmer extends StatelessWidget { - const ScheduleCardShimmer({super.key}); - - Widget _getSingleScheduleWidget(BuildContext context) { - return Center( - child: Container( - padding: const EdgeInsets.only(left: 12, bottom: 8, right: 12), - margin: const EdgeInsets.only(top: 8), - child: Container( - margin: const EdgeInsets.only(top: 8, bottom: 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - // timestamp section - Container( - height: 15, - width: 40, - color: Colors.black, - ), - const SizedBox( - height: 2.5, - ), - Container( - height: 15, - width: 40, - color: Colors.black, - ), - ], - ), - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - height: 25, - width: 100, - color: Colors.black, - ), // UC section - const SizedBox( - height: 10, - ), - Container( - height: 15, - width: 150, - color: Colors.black, - ), // UC section - ], - ), - Container( - height: 15, - width: 40, - color: Colors.black, - ), // Room section - ], - ), - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - height: 15, - width: 80, - color: Colors.black, - ), // Day of the week - const SizedBox( - height: 10, - ), - _getSingleScheduleWidget(context), - _getSingleScheduleWidget(context), - ], - ); - } -} diff --git a/packages/uni_app/lib/view/home/widgets/uni_logo.dart b/packages/uni_app/lib/view/home/widgets/uni_logo.dart index 74c0ee48d..af44d5b6c 100644 --- a/packages/uni_app/lib/view/home/widgets/uni_logo.dart +++ b/packages/uni_app/lib/view/home/widgets/uni_logo.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; class UniLogo extends StatelessWidget { - const UniLogo({super.key}); + const UniLogo({super.key, this.iconColor}); + + final Color? iconColor; @override Widget build(BuildContext context) { @@ -11,7 +13,7 @@ class UniLogo extends StatelessWidget { shape: const RoundedRectangleBorder(), child: SvgPicture.asset( colorFilter: ColorFilter.mode( - Theme.of(context).primaryColor, + iconColor ?? Theme.of(context).primaryColor, BlendMode.srcIn, ), 'assets/images/logo_dark.svg', diff --git a/packages/uni_app/lib/view/login/login.dart b/packages/uni_app/lib/view/login/login.dart index 37e95adeb..52260200c 100644 --- a/packages/uni_app/lib/view/login/login.dart +++ b/packages/uni_app/lib/view/login/login.dart @@ -23,7 +23,6 @@ import 'package:uni/view/login/widgets/f_login_button.dart'; import 'package:uni/view/login/widgets/inputs.dart'; import 'package:uni/view/login/widgets/remember_me_checkbox.dart'; import 'package:uni/view/login/widgets/terms_and_conditions_button.dart'; -import 'package:uni_ui/theme.dart'; import 'package:url_launcher/url_launcher.dart'; class LoginPageView extends StatefulWidget { @@ -215,7 +214,7 @@ class LoginPageViewState extends State @override Widget build(BuildContext context) { return Theme( - data: lightTheme, + data: Theme.of(context), child: Builder( builder: (context) => Scaffold( resizeToAvoidBottomInset: false, @@ -314,9 +313,9 @@ class LoginPageViewState extends State }); }, padding: const EdgeInsets.symmetric(horizontal: 37), - theme: lightTheme.textTheme.bodyLarge?.copyWith( - color: const Color(0xFFFFFFFF), - ), + theme: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: const Color(0xFFFFFFFF), + ), ), ), Align( @@ -351,9 +350,9 @@ class LoginPageViewState extends State return AlertDialog( title: Text( S.of(context).login_with_credentials, - style: lightTheme.textTheme.headlineLarge?.copyWith( - color: const Color(0xFF280709), - ), + style: Theme.of(context).textTheme.headlineLarge?.copyWith( + color: const Color(0xFF280709), + ), textAlign: TextAlign.center, ), shape: RoundedRectangleBorder( @@ -398,9 +397,9 @@ class LoginPageViewState extends State }, textColor: const Color(0xFF280709), padding: EdgeInsets.zero, - theme: lightTheme.textTheme.bodyLarge?.copyWith( - color: const Color(0xFF280709), - ), + theme: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: const Color(0xFF280709), + ), ), ], ), diff --git a/packages/uni_app/lib/view/login/widgets/create_link.dart b/packages/uni_app/lib/view/login/widgets/create_link.dart index 4c9cb945c..c138893a3 100644 --- a/packages/uni_app/lib/view/login/widgets/create_link.dart +++ b/packages/uni_app/lib/view/login/widgets/create_link.dart @@ -1,6 +1,5 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:uni_ui/theme.dart'; class LinkWidget extends StatelessWidget { const LinkWidget({ @@ -20,19 +19,19 @@ class LinkWidget extends StatelessWidget { textAlign: TextAlign.center, text: TextSpan( text: textStart, - style: lightTheme.textTheme.bodyMedium?.copyWith( - color: Colors.white, - ), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.white, + ), children: [ const TextSpan(text: ' '), TextSpan( text: textEnd, - style: lightTheme.textTheme.bodyMedium?.copyWith( - color: Colors.white, - fontWeight: FontWeight.w700, - decoration: TextDecoration.underline, - decorationColor: Colors.white, - ), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.white, + fontWeight: FontWeight.w700, + decoration: TextDecoration.underline, + decorationColor: Colors.white, + ), recognizer: recognizer, ), ], diff --git a/packages/uni_app/lib/view/login/widgets/f_login_button.dart b/packages/uni_app/lib/view/login/widgets/f_login_button.dart index 9ecc97a58..219d872ab 100644 --- a/packages/uni_app/lib/view/login/widgets/f_login_button.dart +++ b/packages/uni_app/lib/view/login/widgets/f_login_button.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:uni/generated/l10n.dart'; -import 'package:uni_ui/theme.dart'; class FLoginButton extends StatelessWidget { const FLoginButton({ @@ -31,9 +30,9 @@ class FLoginButton extends StatelessWidget { const SizedBox(width: 16), Text( S.of(context).login, - style: lightTheme.textTheme.headlineSmall?.copyWith( - color: const Color(0xFF303030), - ), + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + color: const Color(0xFF303030), + ), textAlign: TextAlign.left, ), ], diff --git a/packages/uni_app/lib/view/login/widgets/inputs.dart b/packages/uni_app/lib/view/login/widgets/inputs.dart index a4601b706..62a8529b1 100644 --- a/packages/uni_app/lib/view/login/widgets/inputs.dart +++ b/packages/uni_app/lib/view/login/widgets/inputs.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:uni/generated/l10n.dart'; -import 'package:uni_ui/theme.dart'; /// Creates the widget for the username input. Widget createUsernameInput( @@ -10,7 +9,7 @@ Widget createUsernameInput( FocusNode passwordFocus, ) { return TextFormField( - style: lightTheme.textTheme.titleMedium, + style: Theme.of(context).textTheme.titleMedium, enableSuggestions: false, autocorrect: false, controller: usernameController, @@ -22,6 +21,7 @@ Widget createUsernameInput( textInputAction: TextInputAction.next, textAlign: TextAlign.left, decoration: textFieldDecoration( + context, S.of(context).student_number, textColor: Theme.of(context).indicatorColor, ), @@ -38,7 +38,7 @@ Widget createPasswordInput( required bool obscurePasswordInput, }) { return TextFormField( - style: lightTheme.textTheme.titleMedium, + style: Theme.of(context).textTheme.titleMedium, enableSuggestions: false, autocorrect: false, controller: passwordController, @@ -63,13 +63,14 @@ Widget createPasswordInput( /// Decoration for the username field. InputDecoration textFieldDecoration( + BuildContext context, String placeholder, { required Color textColor, }) { return InputDecoration( - hintStyle: lightTheme.textTheme.titleMedium?.copyWith( - color: const Color(0xFF3C0A0E), - ), + hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith( + color: const Color(0xFF3C0A0E), + ), hintText: placeholder, contentPadding: const EdgeInsets.fromLTRB(10, 20, 0, 0), border: const UnderlineInputBorder(), @@ -87,9 +88,9 @@ InputDecoration passwordFieldDecoration( required bool obscurePasswordInput, }) { return InputDecoration( - hintStyle: lightTheme.textTheme.titleMedium?.copyWith( - color: const Color(0xFF3C0A0E), - ), + hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith( + color: const Color(0xFF3C0A0E), + ), hintText: placeholder, contentPadding: const EdgeInsets.fromLTRB(10, 25, 0, 0), border: const UnderlineInputBorder(), diff --git a/packages/uni_app/lib/view/login/widgets/terms_and_conditions_button.dart b/packages/uni_app/lib/view/login/widgets/terms_and_conditions_button.dart index 302666adb..8fb9a005e 100644 --- a/packages/uni_app/lib/view/login/widgets/terms_and_conditions_button.dart +++ b/packages/uni_app/lib/view/login/widgets/terms_and_conditions_button.dart @@ -2,7 +2,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:uni/generated/l10n.dart'; import 'package:uni/view/about/widgets/terms_and_conditions.dart'; -import 'package:uni_ui/theme.dart'; class TermsAndConditionsButton extends StatelessWidget { const TermsAndConditionsButton({super.key}); @@ -17,22 +16,22 @@ class TermsAndConditionsButton extends StatelessWidget { child: RichText( text: TextSpan( text: S.of(context).agree_terms, - style: lightTheme.textTheme.bodyMedium?.copyWith( - color: Colors.white, - decorationColor: Colors.white, - ), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.white, + decorationColor: Colors.white, + ), children: [ const TextSpan( text: ' ', ), TextSpan( text: S.of(context).terms, - style: lightTheme.textTheme.bodyMedium?.copyWith( - color: Colors.white, - fontWeight: FontWeight.w700, - decoration: TextDecoration.underline, - decorationColor: Colors.white, - ), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.white, + fontWeight: FontWeight.w700, + decoration: TextDecoration.underline, + decorationColor: Colors.white, + ), recognizer: TapGestureRecognizer() ..onTap = () { _showTermsAndConditions(context); diff --git a/packages/uni_app/lib/view/restaurant/restaurant_page_view.dart b/packages/uni_app/lib/view/restaurant/restaurant_page_view.dart index 7ee26bf32..19703c10e 100644 --- a/packages/uni_app/lib/view/restaurant/restaurant_page_view.dart +++ b/packages/uni_app/lib/view/restaurant/restaurant_page_view.dart @@ -73,7 +73,7 @@ class _RestaurantPageViewState extends GeneralPageViewState final favoriteCardTypes = PreferencesController.getFavoriteCards(); if (context.mounted && - !favoriteCardTypes.contains(FavoriteWidgetType.restaurant)) { + !favoriteCardTypes.contains(FavoriteWidgetType.restaurants)) { showRestaurantCardHomeDialog( context, favoriteCardTypes, @@ -335,7 +335,7 @@ class _RestaurantPageViewState extends GeneralPageViewState ElevatedButton( onPressed: () { updateHomePage( - favoriteCardTypes + [FavoriteWidgetType.restaurant], + favoriteCardTypes + [FavoriteWidgetType.restaurants], ); Navigator.of(context).pop(); }, diff --git a/packages/uni_app/pubspec.lock b/packages/uni_app/pubspec.lock index 26889d843..463fb4cbd 100644 --- a/packages/uni_app/pubspec.lock +++ b/packages/uni_app/pubspec.lock @@ -414,6 +414,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.1" + expandable_page_view: + dependency: "direct main" + description: + name: expandable_page_view + sha256: "210dc6961cfc29f7ed42867824eb699c9a4b9b198a7c04b8bdc1c05844969dc6" + url: "https://pub.dev" + source: hosted + version: "1.0.17" expansion_tile_card: dependency: "direct main" description: @@ -1296,6 +1304,14 @@ packages: description: flutter source: sdk version: "0.0.99" + smooth_page_indicator: + dependency: "direct main" + description: + name: smooth_page_indicator + sha256: b21ebb8bc39cf72d11c7cfd809162a48c3800668ced1c9da3aade13a32cf6c1c + url: "https://pub.dev" + source: hosted + version: "1.2.1" source_gen: dependency: transitive description: diff --git a/packages/uni_app/pubspec.yaml b/packages/uni_app/pubspec.yaml index 727a81008..7360c0878 100644 --- a/packages/uni_app/pubspec.yaml +++ b/packages/uni_app/pubspec.yaml @@ -26,6 +26,7 @@ dependencies: diacritic: ^0.1.5 email_validator: ^2.0.1 expandable: ^5.0.1 + expandable_page_view: ^1.0.17 expansion_tile_card: ^3.0.0 figma_squircle: ^0.5.3 flutter: @@ -64,6 +65,7 @@ dependencies: sentry_flutter: ^8.9.0 shared_preferences: ^2.2.2 shimmer: ^3.0.0 + smooth_page_indicator: ^1.2.0+3 sqflite: ^2.0.3 synchronized: ^3.0.0 timelines: ^0.1.0 diff --git a/packages/uni_ui/lib/cards/schedule_card.dart b/packages/uni_ui/lib/cards/schedule_card.dart index d58ffb7a8..cbfee34c3 100644 --- a/packages/uni_ui/lib/cards/schedule_card.dart +++ b/packages/uni_ui/lib/cards/schedule_card.dart @@ -88,7 +88,7 @@ class ScheduleCard extends StatelessWidget { 'assets/images/profile_placeholder.png', // to change )), const SizedBox(width: 8), //TODO: create gap()? - Text(teacherName!, + Text(teacherName ?? 'Teacher X', style: Theme.of(context).textTheme.titleSmall), ]) ], diff --git a/packages/uni_ui/lib/cards/timeline_card.dart b/packages/uni_ui/lib/cards/timeline_card.dart index 229cc45e2..640e1e9bd 100644 --- a/packages/uni_ui/lib/cards/timeline_card.dart +++ b/packages/uni_ui/lib/cards/timeline_card.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:uni_ui/theme.dart'; class TimelineItem extends StatelessWidget { const TimelineItem( @@ -29,8 +28,8 @@ class TimelineItem extends StatelessWidget { Column(children: [ Container( margin: EdgeInsets.only(bottom: 5, left: 10, right: 10), - width: 25, - height: 25, + width: 20, + height: 20, decoration: BoxDecoration( shape: BoxShape.circle, color: isActive ? Theme.of(context).primaryColor : Colors.white, diff --git a/packages/uni_ui/lib/courses/average_bar.dart b/packages/uni_ui/lib/courses/average_bar.dart index 618a13d99..f07636197 100644 --- a/packages/uni_ui/lib/courses/average_bar.dart +++ b/packages/uni_ui/lib/courses/average_bar.dart @@ -26,7 +26,8 @@ class AverageBar extends StatelessWidget { Widget build(BuildContext context) { return LayoutBuilder(builder: (context, constraints) { return Row(children: [ - GenericSquircle(child: Container( + GenericSquircle( + child: Container( decoration: BoxDecoration( color: Theme.of(context).colorScheme.secondary, borderRadius: BorderRadius.all(Radius.circular(5))), diff --git a/packages/uni_ui/lib/icons.dart b/packages/uni_ui/lib/icons.dart index 4d1c537ac..5f31af306 100644 --- a/packages/uni_ui/lib/icons.dart +++ b/packages/uni_ui/lib/icons.dart @@ -6,7 +6,8 @@ class UniIcons { static const lecture = PhosphorIconsDuotone.lectern; static const exam = PhosphorIconsDuotone.exam; static const course = PhosphorIconsDuotone.certificate; - static const calendar = PhosphorIconsDuotone.calendarDots; + static const library = PhosphorIconsDuotone.books; + static const calendar = PhosphorIconsDuotone.calendar; static const courses = PhosphorIconsDuotone.certificate; static const classes = PhosphorIconsDuotone.usersThree; static const files = PhosphorIconsDuotone.folderOpen; @@ -36,6 +37,8 @@ class UniIcons { static const restaurant = PhosphorIconsDuotone.forkKnife; static const faculty = PhosphorIconsDuotone.buildings; static const map = PhosphorIconsDuotone.mapTrifold; + static const edit = PhosphorIconsDuotone.pencilSimple; + static const more = PhosphorIconsDuotone.dotsThreeOutlineVertical; // Locations pins icons static const money = PhosphorIconsDuotone.money; diff --git a/packages/uni_ui/lib/modal/modal.dart b/packages/uni_ui/lib/modal/modal.dart index ed8df1da4..89c5535f4 100644 --- a/packages/uni_ui/lib/modal/modal.dart +++ b/packages/uni_ui/lib/modal/modal.dart @@ -12,7 +12,9 @@ class ModalDialog extends StatelessWidget { Widget build(BuildContext context) { return Dialog( backgroundColor: Colors.transparent, - child: GenericSquircle(borderRadius: 30, child: Container( + child: GenericSquircle( + borderRadius: 30, + child: Container( padding: const EdgeInsets.all(20.0), color: Theme.of(context).colorScheme.surface, child: Column( diff --git a/packages/uni_ui/lib/modal/widgets/enrollment_info.dart b/packages/uni_ui/lib/modal/widgets/enrollment_info.dart index a301f33d0..8980bb918 100644 --- a/packages/uni_ui/lib/modal/widgets/enrollment_info.dart +++ b/packages/uni_ui/lib/modal/widgets/enrollment_info.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:uni_ui/theme.dart'; class ModalEnrollementInfo extends StatelessWidget { const ModalEnrollementInfo({required this.enrollements}); diff --git a/packages/uni_ui/lib/modal/widgets/service_info.dart b/packages/uni_ui/lib/modal/widgets/service_info.dart index 067379af4..cb07a63fc 100644 --- a/packages/uni_ui/lib/modal/widgets/service_info.dart +++ b/packages/uni_ui/lib/modal/widgets/service_info.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:phosphor_flutter/phosphor_flutter.dart'; -import 'package:uni_ui/theme.dart'; class ModalServiceInfo extends StatelessWidget { const ModalServiceInfo({required this.name, required this.durations}); diff --git a/packages/uni_ui/lib/navbar/bottom_navbar.dart b/packages/uni_ui/lib/navbar/bottom_navbar.dart index 322b094c0..350b6cf4a 100644 --- a/packages/uni_ui/lib/navbar/bottom_navbar.dart +++ b/packages/uni_ui/lib/navbar/bottom_navbar.dart @@ -1,4 +1,3 @@ - import 'package:flutter/material.dart'; import 'package:uni_ui/navbar/bottom_navbar_item.dart'; import 'package:uni_ui/common/generic_squircle.dart'; @@ -15,8 +14,7 @@ class _BottomNavbarContainer extends StatelessWidget { margin: EdgeInsets.only(left: 20, right: 20, bottom: 20), decoration: ShapeDecoration( color: Theme.of(context).colorScheme.primary, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20)), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), shadows: [ BoxShadow( color: Theme.of(context).colorScheme.shadow.withAlpha(0x7f), diff --git a/packages/uni_ui/lib/timeline/timeline.dart b/packages/uni_ui/lib/timeline/timeline.dart index 69906db51..8b48e3388 100644 --- a/packages/uni_ui/lib/timeline/timeline.dart +++ b/packages/uni_ui/lib/timeline/timeline.dart @@ -111,7 +111,9 @@ class _TimelineState extends State { child: Padding( padding: const EdgeInsets.symmetric( vertical: 10.0, horizontal: 5.0), - child: GenericSquircle(borderRadius: 10, child: Container( + child: GenericSquircle( + borderRadius: 10, + child: Container( key: _tabKeys[index], padding: const EdgeInsets.symmetric( vertical: 9.0, horizontal: 8.0), diff --git a/packages/uni_ui/pubspec.lock b/packages/uni_ui/pubspec.lock new file mode 100644 index 000000000..53936bd01 --- /dev/null +++ b/packages/uni_ui/pubspec.lock @@ -0,0 +1,621 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + url: "https://pub.dev" + source: hosted + version: "67.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161" + url: "https://pub.dev" + source: hosted + version: "0.11.3" + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + ci: + dependency: transitive + description: + name: ci + sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13" + url: "https://pub.dev" + source: hosted + version: "0.1.0" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + url: "https://pub.dev" + source: hosted + version: "0.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: "3945034e86ea203af7a056d98e98e42a5518fff200d6e8e6647e1886b07e936e" + url: "https://pub.dev" + source: hosted + version: "1.8.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + custom_lint: + dependency: "direct dev" + description: + name: custom_lint + sha256: "7c0aec12df22f9082146c354692056677f1e70bc43471644d1fdb36c6fdda799" + url: "https://pub.dev" + source: hosted + version: "0.6.4" + custom_lint_builder: + dependency: transitive + description: + name: custom_lint_builder + sha256: d7dc41e709dde223806660268678be7993559e523eb3164e2a1425fd6f7615a9 + url: "https://pub.dev" + source: hosted + version: "0.6.4" + custom_lint_core: + dependency: transitive + description: + name: custom_lint_core + sha256: a85e8f78f4c52f6c63cdaf8c872eb573db0231dcdf3c3a5906d493c1f8bc20e6 + url: "https://pub.dev" + source: hosted + version: "0.6.3" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + url: "https://pub.dev" + source: hosted + version: "2.3.6" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + figma_squircle: + dependency: "direct main" + description: + name: figma_squircle + sha256: "790b91a9505e90d246f6efe2fa065ff7fffe658c7b44fe9b5b20c7b0ad3818c0" + url: "https://pub.dev" + source: hosted + version: "0.5.3" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e + url: "https://pub.dev" + source: hosted + version: "4.2.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + url: "https://pub.dev" + source: hosted + version: "0.7.1" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + leancode_lint: + dependency: "direct dev" + description: + name: leancode_lint + sha256: "85b7c09c806400083faa37304c701fd1bd5dbcb826882438dd50f0e465f30fd8" + url: "https://pub.dev" + source: hosted + version: "13.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" + source: hosted + version: "1.15.0" + mime: + dependency: transitive + description: + name: mime + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + phosphor_flutter: + dependency: "direct main" + description: + name: phosphor_flutter + sha256: "8a14f238f28a0b54842c5a4dc20676598dd4811fcba284ed828bd5a262c11fde" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" + scrollable_positioned_list: + dependency: "direct main" + description: + name: scrollable_positioned_list + sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287" + url: "https://pub.dev" + source: hosted + version: "0.3.8" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + url: "https://pub.dev" + source: hosted + version: "1.1.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test: + dependency: "direct dev" + description: + name: test + sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" + url: "https://pub.dev" + source: hosted + version: "1.25.7" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + test_core: + dependency: transitive + description: + name: test_core + sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" + url: "https://pub.dev" + source: hosted + version: "0.6.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" + url: "https://pub.dev" + source: hosted + version: "4.4.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" + source: hosted + version: "14.2.5" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" + url: "https://pub.dev" + source: hosted + version: "2.4.5" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.24.3"