diff --git a/filcnaplo/lib/ui/filter/sort.dart b/filcnaplo/lib/ui/filter/sort.dart index 0d9591c..dee8ba3 100644 --- a/filcnaplo/lib/ui/filter/sort.dart +++ b/filcnaplo/lib/ui/filter/sort.dart @@ -12,7 +12,8 @@ import 'package:filcnaplo_mobile_ui/common/widgets/lesson/changed_lesson_tile.da import 'package:filcnaplo/utils/format.dart'; // difference.inDays is not reliable -bool _sameDate(DateTime a, DateTime b) => (a.year == b.year && a.month == b.month && a.day == b.day); +bool _sameDate(DateTime a, DateTime b) => + (a.year == b.year && a.month == b.month && a.day == b.day); List sortDateWidgets( BuildContext context, { @@ -35,13 +36,16 @@ List sortDateWidgets( if (message.conversationId != null) { convMessages.add(w); - Conversation conv = conversations.firstWhere((e) => e.id == message.conversationId, orElse: () => Conversation(id: message.conversationId!)); + Conversation conv = conversations.firstWhere( + (e) => e.id == message.conversationId, + orElse: () => Conversation(id: message.conversationId!)); conv.add(message); if (conv.messages.length == 1) conversations.add(conv); } if (conversations.any((c) => c.id == message.messageId)) { - Conversation conv = conversations.firstWhere((e) => e.id == message.messageId); + Conversation conv = + conversations.firstWhere((e) => e.id == message.messageId); convMessages.add(w); conv.add(message); } @@ -87,26 +91,41 @@ List sortDateWidgets( // Group Absence Tiles List absenceTileWidgets = elements.where((element) { - return element.widget is AbsenceViewable && (element.widget as AbsenceViewable).absence.delay == 0; + return element.widget is AbsenceViewable && + (element.widget as AbsenceViewable).absence.delay == 0; }).toList(); - List absenceTiles = absenceTileWidgets.map((e) => e.widget as AbsenceViewable).toList(); + List absenceTiles = + absenceTileWidgets.map((e) => e.widget as AbsenceViewable).toList(); if (absenceTiles.length > 1) { - elements.removeWhere((element) => element.widget.runtimeType == AbsenceViewable && (element.widget as AbsenceViewable).absence.delay == 0); + elements.removeWhere((element) => + element.widget.runtimeType == AbsenceViewable && + (element.widget as AbsenceViewable).absence.delay == 0); if (elements.isEmpty) { cst = false; } - elements.add(DateWidget( - widget: AbsenceGroupTile(absenceTiles, showDate: !cst), - date: absenceTileWidgets.first.date, - key: "${absenceTileWidgets.first.date.millisecondsSinceEpoch}-absence-group")); + elements.add( + DateWidget( + widget: AbsenceGroupTile( + absenceTiles, + showDate: !cst, + padding: const EdgeInsets.symmetric(horizontal: 6.0), + ), + date: absenceTileWidgets.first.date, + key: + "${absenceTileWidgets.first.date.millisecondsSinceEpoch}-absence-group"), + ); } // Bring Lesson Tiles to front & sort by index asc List lessonTiles = elements.where((element) { return element.widget.runtimeType == ChangedLessonTile; }).toList(); - lessonTiles.sort((a, b) => (a.widget as ChangedLessonTile).lesson.lessonIndex.compareTo((b.widget as ChangedLessonTile).lesson.lessonIndex)); - elements.removeWhere((element) => element.widget.runtimeType == ChangedLessonTile); + lessonTiles.sort((a, b) => (a.widget as ChangedLessonTile) + .lesson + .lessonIndex + .compareTo((b.widget as ChangedLessonTile).lesson.lessonIndex)); + elements.removeWhere( + (element) => element.widget.runtimeType == ChangedLessonTile); elements.insertAll(0, lessonTiles); final date = (elements + absenceTileWidgets).first.date; @@ -122,7 +141,8 @@ List sortDateWidgets( spawnIsolate: false, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - itemBuilder: (context, animation, item, index) => filterItemBuilder(context, animation, item.widget, index), + itemBuilder: (context, animation, item, index) => + filterItemBuilder(context, animation, item.widget, index), items: elements, ), ), @@ -131,9 +151,12 @@ List sortDateWidgets( } final nh = DateTime.now(); - final now = DateTime(nh.year, nh.month, nh.day).subtract(const Duration(seconds: 1)); + final now = + DateTime(nh.year, nh.month, nh.day).subtract(const Duration(seconds: 1)); - if (showDivider && items.any((i) => i.date.isBefore(now)) && items.any((i) => i.date.isAfter(now))) { + if (showDivider && + items.any((i) => i.date.isBefore(now)) && + items.any((i) => i.date.isAfter(now))) { items.add( DateWidget( date: now, @@ -153,7 +176,9 @@ List sortDateWidgets( } // Sort future dates asc, past dates desc - items.sort((a, b) => (a.date.isAfter(now) && b.date.isAfter(now) ? 1 : -1) * a.date.compareTo(b.date)); + items.sort((a, b) => + (a.date.isAfter(now) && b.date.isAfter(now) ? 1 : -1) * + a.date.compareTo(b.date)); return items.map((e) => e.widget).toList(); } diff --git a/filcnaplo_desktop_ui/lib/pages/absences/absences_page.dart b/filcnaplo_desktop_ui/lib/pages/absences/absences_page.dart index 26ac74a..f9270b5 100644 --- a/filcnaplo_desktop_ui/lib/pages/absences/absences_page.dart +++ b/filcnaplo_desktop_ui/lib/pages/absences/absences_page.dart @@ -35,7 +35,8 @@ class SubjectAbsence { List absences; double percentage; - SubjectAbsence({required this.subject, this.absences = const [], this.percentage = 0.0}); + SubjectAbsence( + {required this.subject, this.absences = const [], this.percentage = 0.0}); } class AbsencesPage extends StatefulWidget { @@ -45,7 +46,8 @@ class AbsencesPage extends StatefulWidget { _AbsencesPageState createState() => _AbsencesPageState(); } -class _AbsencesPageState extends State with TickerProviderStateMixin { +class _AbsencesPageState extends State + with TickerProviderStateMixin { late UserProvider user; late AbsenceProvider absenceProvider; late TimetableProvider timetableProvider; @@ -65,7 +67,9 @@ class _AbsencesPageState extends State with TickerProviderStateMix WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { for (final lesson in timetableProvider.getWeek(Week.current()) ?? []) { - if (!lesson.isEmpty && lesson.subject.id != '' && lesson.lessonYearIndex != null) { + if (!lesson.isEmpty && + lesson.subject.id != '' && + lesson.lessonYearIndex != null) { _lessonCount.update( lesson.subject, (value) { @@ -89,25 +93,30 @@ class _AbsencesPageState extends State with TickerProviderStateMix if (absence.delay != 0) continue; if (!_absences.containsKey(absence.subject)) { - _absences[absence.subject] = SubjectAbsence(subject: absence.subject, absences: [absence]); + _absences[absence.subject] = + SubjectAbsence(subject: absence.subject, absences: [absence]); } else { _absences[absence.subject]?.absences.add(absence); } } _absences.forEach((subject, absence) { - final absentLessonsOfSubject = absenceProvider.absences.where((e) => e.subject == subject && e.delay == 0).length; + final absentLessonsOfSubject = absenceProvider.absences + .where((e) => e.subject == subject && e.delay == 0) + .length; final totalLessonsOfSubject = _lessonCount[subject]?.lessonYearIndex ?? 0; double absentLessonsOfSubjectPercentage; if (absentLessonsOfSubject <= totalLessonsOfSubject) { - absentLessonsOfSubjectPercentage = absentLessonsOfSubject / totalLessonsOfSubject * 100; + absentLessonsOfSubjectPercentage = + absentLessonsOfSubject / totalLessonsOfSubject * 100; } else { absentLessonsOfSubjectPercentage = -1; } - _absences[subject]?.percentage = absentLessonsOfSubjectPercentage.clamp(-1, 100.0); + _absences[subject]?.percentage = + absentLessonsOfSubjectPercentage.clamp(-1, 100.0); }); absences = _absences.values.toList(); @@ -131,7 +140,8 @@ class _AbsencesPageState extends State with TickerProviderStateMix body: Padding( padding: const EdgeInsets.only(top: 12.0), child: NestedScrollView( - physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), + physics: const BouncingScrollPhysics( + parent: AlwaysScrollableScrollPhysics()), headerSliverBuilder: (context, _) => [ SliverAppBar( pinned: true, @@ -145,7 +155,10 @@ class _AbsencesPageState extends State with TickerProviderStateMix padding: const EdgeInsets.only(left: 8.0), child: Text( "Absences".i18n, - style: TextStyle(color: AppColors.of(context).text, fontSize: 32.0, fontWeight: FontWeight.bold), + style: TextStyle( + color: AppColors.of(context).text, + fontSize: 32.0, + fontWeight: FontWeight.bold), ), ), bottom: FilterBar(items: [ @@ -158,7 +171,8 @@ class _AbsencesPageState extends State with TickerProviderStateMix body: TabBarView( physics: const BouncingScrollPhysics(), controller: _tabController, - children: List.generate(3, (index) => filterViewBuilder(context, index))), + children: List.generate( + 3, (index) => filterViewBuilder(context, index))), ), ), ); @@ -174,10 +188,17 @@ class _AbsencesPageState extends State with TickerProviderStateMix widget: AbsenceSubjectTile( a.subject, percentage: a.percentage, - excused: a.absences.where((a) => a.state == Justification.excused).length, - unexcused: a.absences.where((a) => a.state == Justification.unexcused).length, - pending: a.absences.where((a) => a.state == Justification.pending).length, - onTap: () => AbsenceSubjectView.show(a.subject, a.absences, context: context), + excused: a.absences + .where((a) => a.state == Justification.excused) + .length, + unexcused: a.absences + .where((a) => a.state == Justification.unexcused) + .length, + pending: a.absences + .where((a) => a.state == Justification.pending) + .length, + onTap: () => AbsenceSubjectView.show(a.subject, a.absences, + context: context), ), )); } @@ -186,15 +207,18 @@ class _AbsencesPageState extends State with TickerProviderStateMix for (var absence in absenceProvider.absences) { if (absence.delay != 0) { items.add(DateWidget( - date: absence.date, - widget: AbsenceViewable(absence, padding: EdgeInsets.zero), - )); + date: absence.date, + widget: AbsenceViewable( + absence, + padding: EdgeInsets.zero, + ))); } } break; case AbsenceFilter.misses: for (var note in noteProvider.notes) { - if (note.type?.name == "HaziFeladatHiany" || note.type?.name == "Felszereleshiany") { + if (note.type?.name == "HaziFeladatHiany" || + note.type?.name == "Felszereleshiany") { items.add(DateWidget( date: note.date, widget: MissTile(note), @@ -232,10 +256,15 @@ class _AbsencesPageState extends State with TickerProviderStateMix showDialog( context: context, builder: (context) => AlertDialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0)), title: Text("attention".i18n), content: Text("attention_body".i18n), - actions: [ActionButton(label: "Ok", onTap: () => Navigator.of(context).pop())], + actions: [ + ActionButton( + label: "Ok", + onTap: () => Navigator.of(context).pop()) + ], ), ); }, @@ -262,7 +291,10 @@ class _AbsencesPageState extends State with TickerProviderStateMix ); }, child: Column( - children: getFilterWidgets(AbsenceFilter.values[activeData]).map((e) => e.widget).cast().toList(), + children: getFilterWidgets(AbsenceFilter.values[activeData]) + .map((e) => e.widget) + .cast() + .toList(), ), ), ), @@ -284,7 +316,8 @@ class _AbsencesPageState extends State with TickerProviderStateMix itemCount: max(filterWidgets.length + (activeData <= 1 ? 1 : 0), 1), itemBuilder: (context, index) { if (filterWidgets.isNotEmpty) { - if ((index == 0 && activeData == 1) || (index == 0 && activeData == 0)) { + if ((index == 0 && activeData == 1) || + (index == 0 && activeData == 0)) { int value1 = 0; int value2 = 0; String title1 = ""; @@ -292,18 +325,26 @@ class _AbsencesPageState extends State with TickerProviderStateMix String suffix = ""; if (activeData == AbsenceFilter.absences.index) { - value1 = absenceProvider.absences.where((e) => e.delay == 0 && e.state == Justification.excused).length; - value2 = absenceProvider.absences.where((e) => e.delay == 0 && e.state == Justification.unexcused).length; + value1 = absenceProvider.absences + .where((e) => + e.delay == 0 && e.state == Justification.excused) + .length; + value2 = absenceProvider.absences + .where((e) => + e.delay == 0 && e.state == Justification.unexcused) + .length; title1 = "stat_1".i18n; title2 = "stat_2".i18n; suffix = " " + "hr".i18n; } else if (activeData == AbsenceFilter.delays.index) { value1 = absenceProvider.absences - .where((e) => e.delay != 0 && e.state == Justification.excused) + .where((e) => + e.delay != 0 && e.state == Justification.excused) .map((e) => e.delay) .fold(0, (a, b) => a + b); value2 = absenceProvider.absences - .where((e) => e.delay != 0 && e.state == Justification.unexcused) + .where((e) => + e.delay != 0 && e.state == Justification.unexcused) .map((e) => e.delay) .fold(0, (a, b) => a + b); title1 = "stat_3".i18n; @@ -312,7 +353,8 @@ class _AbsencesPageState extends State with TickerProviderStateMix } return Padding( - padding: const EdgeInsets.only(bottom: 24.0, left: 24.0, right: 24.0), + padding: const EdgeInsets.only( + bottom: 24.0, left: 24.0, right: 24.0), child: Row(children: [ Expanded( child: StatisticsTile( @@ -348,7 +390,8 @@ class _AbsencesPageState extends State with TickerProviderStateMix } return Padding( - padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 6.0), + padding: + const EdgeInsets.symmetric(horizontal: 24.0, vertical: 6.0), child: filterWidgets[index - (activeData <= 1 ? 1 : 0)], ); } else { diff --git a/filcnaplo_mobile_ui/lib/common/widgets/absence/absence_tile.dart b/filcnaplo_mobile_ui/lib/common/widgets/absence/absence_tile.dart index 3186b21..b01e8e7 100755 --- a/filcnaplo_mobile_ui/lib/common/widgets/absence/absence_tile.dart +++ b/filcnaplo_mobile_ui/lib/common/widgets/absence/absence_tile.dart @@ -9,7 +9,9 @@ import 'package:provider/provider.dart'; import 'absence_tile.i18n.dart'; class AbsenceTile extends StatelessWidget { - const AbsenceTile(this.absence, {Key? key, this.onTap, this.elevation = 0.0, this.padding}) : super(key: key); + const AbsenceTile(this.absence, + {Key? key, this.onTap, this.elevation = 0.0, this.padding}) + : super(key: key); final Absence absence; final void Function()? onTap; @@ -37,39 +39,61 @@ class AbsenceTile extends StatelessWidget { child: Material( type: MaterialType.transparency, child: Padding( - padding: padding ?? (group ? EdgeInsets.zero : const EdgeInsets.symmetric(horizontal: 8.0)), + padding: padding ?? + (group + ? EdgeInsets.zero + : const EdgeInsets.symmetric( + horizontal: 8.0, + )), child: ListTile( onTap: onTap, visualDensity: VisualDensity.compact, dense: group, - contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(!group ? 14.0 : 12.0)), + contentPadding: const EdgeInsets.only( + left: 14.0, right: 12.0, top: 2.0, bottom: 2.0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(!group ? 14.0 : 12.0)), leading: Container( width: 44.0, decoration: BoxDecoration( shape: BoxShape.circle, color: !group ? color.withOpacity(.25) : null, ), - child: Center(child: Icon(justificationIcon(absence.state), color: color)), + child: Center( + child: Icon(justificationIcon(absence.state), color: color)), ), title: !group ? Text.rich(TextSpan( text: "${absence.delay == 0 ? "" : absence.delay}", - style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 15.5), + style: const TextStyle( + fontWeight: FontWeight.w700, fontSize: 15.5), children: [ TextSpan( text: absence.delay == 0 - ? justificationName(absence.state).fill(["absence".i18n]).capital() - : 'minute'.plural(absence.delay) + justificationName(absence.state).fill(["delay".i18n]), + ? justificationName(absence.state) + .fill(["absence".i18n]).capital() + : 'minute'.plural(absence.delay) + + justificationName(absence.state) + .fill(["delay".i18n]), style: const TextStyle(fontWeight: FontWeight.w600), ), ], )) : Text( - (absence.lessonIndex != null ? "${absence.lessonIndex}. " : "") + (absence.subject.renamedTo ?? absence.subject.name.capital()), + (absence.lessonIndex != null + ? "${absence.lessonIndex}. " + : "") + + (absence.subject.renamedTo ?? + absence.subject.name.capital()), maxLines: 2, overflow: TextOverflow.ellipsis, - style: TextStyle(fontWeight: FontWeight.w500, fontSize: 14.0, fontStyle: absence.subject.isRenamed && settingsProvider.renamedSubjectsItalics ? FontStyle.italic : null), + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14.0, + fontStyle: absence.subject.isRenamed && + settingsProvider.renamedSubjectsItalics + ? FontStyle.italic + : null), ), subtitle: !group ? Text( @@ -77,7 +101,12 @@ class AbsenceTile extends StatelessWidget { maxLines: 2, overflow: TextOverflow.ellipsis, // DateFormat("MM. dd. (EEEEE)", I18n.of(context).locale.toString()).format(absence.date), - style: TextStyle(fontWeight: FontWeight.w500, fontStyle: absence.subject.isRenamed && settingsProvider.renamedSubjectsItalics ? FontStyle.italic : null), + style: TextStyle( + fontWeight: FontWeight.w500, + fontStyle: absence.subject.isRenamed && + settingsProvider.renamedSubjectsItalics + ? FontStyle.italic + : null), ) : null, ), @@ -97,7 +126,8 @@ class AbsenceTile extends StatelessWidget { } } - static Color justificationColor(Justification state, {required BuildContext context}) { + static Color justificationColor(Justification state, + {required BuildContext context}) { switch (state) { case Justification.excused: return AppColors.of(context).green; diff --git a/filcnaplo_mobile_ui/lib/common/widgets/absence_group/absence_group_tile.dart b/filcnaplo_mobile_ui/lib/common/widgets/absence_group/absence_group_tile.dart index 8ba3974..62e8b45 100755 --- a/filcnaplo_mobile_ui/lib/common/widgets/absence_group/absence_group_tile.dart +++ b/filcnaplo_mobile_ui/lib/common/widgets/absence_group/absence_group_tile.dart @@ -5,6 +5,8 @@ import 'package:filcnaplo_mobile_ui/common/widgets/absence_group/absence_group_c import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_tile.dart'; import 'package:filcnaplo/utils/format.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:rounded_expansion_tile/rounded_expansion_tile.dart'; import 'absence_group_tile.i18n.dart'; class AbsenceGroupTile extends StatelessWidget { @@ -30,13 +32,17 @@ class AbsenceGroupTile extends StatelessWidget { child: Material( type: MaterialType.transparency, child: Padding( - padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0), + padding: padding ?? + const EdgeInsets.symmetric(horizontal: 0.0, vertical: 0.0), child: AbsenceGroupContainer( - child: ExpansionTile( + child: RoundedExpansionTile( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10)), - tilePadding: const EdgeInsets.symmetric(horizontal: 8.0), - backgroundColor: Colors.transparent, + childrenPadding: const EdgeInsets.symmetric(horizontal: 8.0), + tileColor: Colors.transparent, + duration: const Duration(milliseconds: 250), + trailingDuration: 0.5, + trailing: const Icon(FeatherIcons.chevronDown), leading: Container( width: 44.0, height: 44.0, diff --git a/filcnaplo_mobile_ui/lib/pages/absences/absence_subject_view.dart b/filcnaplo_mobile_ui/lib/pages/absences/absence_subject_view.dart index 6c8060f..dcf162f 100755 --- a/filcnaplo_mobile_ui/lib/pages/absences/absence_subject_view.dart +++ b/filcnaplo_mobile_ui/lib/pages/absences/absence_subject_view.dart @@ -19,14 +19,18 @@ import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_view.i18n.dar import 'package:provider/provider.dart'; class AbsenceSubjectView extends StatelessWidget { - const AbsenceSubjectView(this.subject, {Key? key, this.absences = const []}) : super(key: key); + const AbsenceSubjectView(this.subject, {Key? key, this.absences = const []}) + : super(key: key); final Subject subject; final List absences; - static void show(Subject subject, List absences, {required BuildContext context}) { + static void show(Subject subject, List absences, + {required BuildContext context}) { Navigator.of(context, rootNavigator: true) - .push(CupertinoPageRoute(builder: (context) => AbsenceSubjectView(subject, absences: absences))) + .push(CupertinoPageRoute( + builder: (context) => + AbsenceSubjectView(subject, absences: absences))) .then((value) { if (value == null) return; @@ -36,7 +40,8 @@ class AbsenceSubjectView extends StatelessWidget { TimetablePage.jump(context, lesson: lesson); } else { ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( - content: Text("Cannot find lesson".i18n, style: const TextStyle(color: Colors.white)), + content: Text("Cannot find lesson".i18n, + style: const TextStyle(color: Colors.white)), backgroundColor: AppColors.of(context).red, context: context, )); @@ -54,7 +59,10 @@ class AbsenceSubjectView extends StatelessWidget { date: a.date, )) .toList(); - List absenceTiles = sortDateWidgets(context, dateWidgets: dateWidgets, padding: EdgeInsets.zero, hasShadow: true); + List absenceTiles = sortDateWidgets(context, + dateWidgets: dateWidgets, + padding: const EdgeInsets.symmetric(vertical: 6.0), + hasShadow: true); SettingsProvider settingsProvider = Provider.of(context); diff --git a/filcnaplo_mobile_ui/pubspec.yaml b/filcnaplo_mobile_ui/pubspec.yaml index ced8ee0..adcf153 100755 --- a/filcnaplo_mobile_ui/pubspec.yaml +++ b/filcnaplo_mobile_ui/pubspec.yaml @@ -43,6 +43,9 @@ dependencies: dotted_border: ^2.0.0+3 screenshot: ^2.1.0 image_gallery_saver: ^2.0.2 + rounded_expansion_tile: + git: + url: https://github.com/kimaah/rounded_expansion_tile.git dev_dependencies: flutter_lints: ^1.0.0