From b17509f9db563ae5633046a5e67db15e02c47204 Mon Sep 17 00:00:00 2001 From: checkedear <271323618+checkedear@users.noreply.github.com> Date: Tue, 21 Apr 2026 22:41:48 +0200 Subject: [PATCH] fix: timetable monthly view design --- .../phone/pages/home/home_timetable_mo.dart | 957 ++++++++---------- 1 file changed, 450 insertions(+), 507 deletions(-) diff --git a/firka/lib/ui/phone/pages/home/home_timetable_mo.dart b/firka/lib/ui/phone/pages/home/home_timetable_mo.dart index e438f2f..0e38b9c 100644 --- a/firka/lib/ui/phone/pages/home/home_timetable_mo.dart +++ b/firka/lib/ui/phone/pages/home/home_timetable_mo.dart @@ -63,9 +63,7 @@ class _HomeTimetableMonthlyScreen forceCache: forceCache, ); var testsResp = await widget.data.client.getTests(forceCache: forceCache); - var omissionsResp = await widget.data.client.getOmissions( - forceCache: forceCache, - ); + var omissionsResp = await widget.data.client.getOmissions(forceCache: true); List dates = List.empty(growable: true); for (var i = 0; i < days; i++) { @@ -119,496 +117,10 @@ class _HomeTimetableMonthlyScreen } Widget _buildContent(BuildContext context) { - if (lessons != null && - omissions != null && - tests != null && - dates != null) { - List ttDays = []; - - final meow = dates![20]; - final currentMonthStart = DateTime.utc(meow.year, meow.month, 1); - final currentMonthEnd = DateTime.utc( - meow.year, - meow.month + 1, - ).subtract(Duration(days: 1)); - - // column-major -> row-major - for (var day = 0; day < 7; day++) { - for (var week = 0; week < 7; week++) { - final d = dates![week * 7 + day]; - - if (d.isBefore(currentMonthStart) || d.isAfter(currentMonthEnd)) { - ttDays.add( - Column( - children: [ - Container( - width: 40, - height: 40, - clipBehavior: Clip.antiAlias, - decoration: ShapeDecoration( - color: appStyle.colors.cardTranslucent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - ), - SizedBox(height: 4), - Text( - d.format(widget.data.l10n, FormatMode.d), - style: appStyle.fonts.B_16R.apply( - color: - (d.weekday == DateTime.saturday || - d.weekday == DateTime.sunday) - ? appStyle.colors.errorText - : appStyle.colors.textTertiary, - ), - ), - ], - ), - ); - } else { - Widget body = SizedBox(); - Color bodyBgColor = appStyle.colors.a15p; - - var lessonsToday = lessons!.where( - (lesson) => - lesson.start.isAfter(d.getMidnight()) && - lesson.end.isBefore( - d.getMidnight().add(Duration(hours: 23, minutes: 59)), - ), - ); - - var omissionType = lessonsToday.firstWhereOrNull( - (lesson) => - lesson.studentPresence != null && - lesson.studentPresence?.name != OmissionConsts.na && - lesson.studentPresence?.name != OmissionConsts.present, - ); - - switch (activeFilter) { - case ActiveFilter.lessonNo: - if (lessonsToday.isNotEmpty) { - body = Center( - child: Text( - lessonsToday.length.toString(), - style: appStyle.fonts.H_16px.apply( - color: - omissionType != null && - (omissionType.studentPresence!.name == - OmissionConsts.absence || - omissionType.studentPresence!.name == - OmissionConsts.na) - ? appStyle.colors.errorText - : timeNow().day == d.day && - timeNow().month == d.month - ? appStyle.colors.accent - : appStyle.colors.secondary, - ), - ), - ); - - if (omissionType != null && - (omissionType.studentPresence!.name == - OmissionConsts.absence || - omissionType.studentPresence!.name == - OmissionConsts.na)) { - bodyBgColor = appStyle.colors.error15p; - } - } - break; - case ActiveFilter.tests: - if (lessonsToday.firstWhereOrNull( - (lesson) => tests!.any( - (test) => - test.lessonNumber == lesson.lessonNumber && - lesson.start.isAfter(test.date.getMidnight()) && - lesson.end.isBefore( - test.date.getMidnight().add( - Duration(hours: 23, minutes: 59), - ), - ), - ), - ) != - null) { - body = Center( - child: FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.editPen4Solid, - size: 20.0, - color: appStyle.colors.accent, - ), - ); - } - break; - case ActiveFilter.omissions: - if (omissionType != null) { - switch (omissionType.studentPresence!.name) { - case OmissionConsts.absence: - final omission = omissions!.firstWhereOrNull((omission) { - return omission.date - .getMidnight() - .millisecondsSinceEpoch == - omissionType.start - .getMidnight() - .millisecondsSinceEpoch && - omission.subject.uid == omissionType.subject?.uid; - }); - if (omission != null) { - switch (omission.state) { - case "Igazolando": - body = Center( - child: FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.restrictedSolid, - size: 20.0, - color: appStyle.colors.warningAccent, - ), - ); - bodyBgColor = appStyle.colors.warning15p; - break; - default: - body = Center( - child: FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.multiplySolid, - size: 20.0, - color: appStyle.colors.accent, - ), - ); - } - } else { - body = Center( - child: FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.multiplySolid, - size: 20.0, - color: appStyle.colors.accent, - ), - ); - } - break; - default: - logger.fine( - "omission: ${omissionType.studentPresence!.name}", - ); - body = Center( - child: FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.timerLine, - size: 20.0, - color: appStyle.colors.accent, - ), - ); - } - } - break; - } - - ttDays.add( - Column( - children: [ - Container( - width: 40, - height: 40, - clipBehavior: Clip.antiAlias, - decoration: ShapeDecoration( - color: bodyBgColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: body, - ), - SizedBox(height: 4), - Text( - d.format(widget.data.l10n, FormatMode.d), - style: appStyle.fonts.B_16R.apply( - color: - (d.weekday == DateTime.saturday || - d.weekday == DateTime.sunday) && - lessonsToday.isEmpty - ? appStyle.colors.errorText - : appStyle.colors.textSecondary, - ), - ), - SizedBox(height: 12), - ], - ), - ); - - if (timeNow().getMidnight().millisecondsSinceEpoch == - d.toLocal().getMidnight().millisecondsSinceEpoch) { - bodyBgColor = appStyle.colors.buttonSecondaryFill; - } - } - } - } - - return Scaffold( - backgroundColor: appStyle.colors.background, - body: Stack( - children: [ - SizedBox( - width: MediaQuery.of(context).size.width, - height: 74 + 16, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - widget.data.l10n.timetable, - style: appStyle.fonts.H_H2.apply( - color: appStyle.colors.textPrimary, - ), - ), - Row( - children: [ - GestureDetector( - child: Card( - color: appStyle.colors.buttonSecondaryFill, - child: Padding( - padding: const EdgeInsets.all(8), - child: FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.tableSolid, - size: 26.0, - color: appStyle.colors.accent, - ), - ), - ), - onTap: () { - context.pop(); - }, - ), - // Nincs elkészítve jelenleg: Dolgozat stb hozzáadása(?) - // Card( - // color: appStyle.colors.buttonSecondaryFill, - // child: Padding( - // padding: const EdgeInsets.all(4), - // child: FirkaIconWidget( - // FirkaIconType.majesticons, - // Majesticon.plusLine, - // size: 32.0, - // color: appStyle.colors.accent, - // ), - // ), - // ), - GestureDetector( - child: Card( - color: appStyle.colors.buttonSecondaryFill, - child: Padding( - padding: const EdgeInsets.all(8), - child: FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.settingsCogSolid, - size: 26.0, - color: appStyle.colors.accent, - ), - ), - ), - onTap: () { - showSettingsSheet( - context, - MediaQuery.of(context).size.height * 0.4, - widget.data, - widget.data.settings - .group("settings") - .subGroup("timetable_toast"), - ); - }, - ), - ], - ), - ], - ), - SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - behavior: HitTestBehavior.translucent, - child: SizedBox( - width: 24, - height: 24, - child: FirkaIconWidget( - FirkaIconType.icons, - "dropdownLeft", - size: 24, - color: appStyle.colors.accent, - ), - ), - onTap: () async { - var newNow = DateTime(now!.year, now!.month - 1); - setState(() { - now = newNow; - lessons = null; - dates = null; - }); - await initForMonth(newNow); - setState(() { - now = newNow; - }); - }, - ), - Text( - now! - .format(widget.data.l10n, FormatMode.yyyymmmm) - .toLowerCase(), - style: appStyle.fonts.B_16R.apply( - color: appStyle.colors.textPrimary, - ), - ), - GestureDetector( - child: FirkaIconWidget( - FirkaIconType.icons, - "dropdownRight", - size: 24, - color: appStyle.colors.accent, - ), - onTap: () async { - var newNow = DateTime(now!.year, now!.month + 1); - setState(() { - now = newNow; - lessons = null; - dates = null; - }); - await initForMonth(newNow); - setState(() { - now = newNow; - }); - }, - ), - ], - ), - ], - ), - ), - ), - TransparentPointer( - child: Padding( - padding: const EdgeInsets.only(top: 74 + 16 + 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - StaggeredGrid.count( - crossAxisCount: 7, - mainAxisSpacing: 8, - children: ttDays, - ), - ], - ), - ), - ), - TransparentPointer( - child: Column( - children: [ - SizedBox(height: MediaQuery.of(context).size.height / 1.3), - SizedBox( - width: MediaQuery.of(context).size.width, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(), - Row( - children: [ - _StatusToast( - FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.clockSolid, - color: appStyle.colors.accent, - size: 16, - ), - lessons! - .where( - (lesson) => - lesson.start.isAfter( - currentMonthStart, - ) && - lesson.end.isBefore(currentMonthEnd), - ) - .length, - activeFilter == ActiveFilter.lessonNo, - () { - setState(() { - activeFilter = ActiveFilter.lessonNo; - }); - }, - ), - _StatusToast( - FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.editPen4Solid, - color: appStyle.colors.accent, - size: 16, - ), - lessons! - .where( - (lesson) => tests!.any( - (test) => - test.lessonNumber == - lesson.lessonNumber && - lesson.start.isAfter( - test.date.getMidnight(), - ) && - lesson.end.isBefore( - test.date.getMidnight().add( - Duration(hours: 23, minutes: 59), - ), - ), - ), - ) - .length, - activeFilter == ActiveFilter.tests, - () { - setState(() { - activeFilter = ActiveFilter.tests; - }); - }, - ), - _StatusToast( - FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.timerLine, - color: appStyle.colors.accent, - size: 16, - ), - lessons! - .where( - (lesson) => - lesson.start.isAfter( - currentMonthStart, - ) && - lesson.end.isBefore(currentMonthEnd) && - lesson.studentPresence != null && - lesson.studentPresence?.name != - OmissionConsts.na && - lesson.studentPresence?.name != - OmissionConsts.present, - ) - .length, - activeFilter == ActiveFilter.omissions, - () { - setState(() { - activeFilter = ActiveFilter.omissions; - }); - }, - ), - ], - ), - SizedBox(), - ], - ), - ), - ], - ), - ), - ], - ), - ); - } else { + if (lessons == null || + omissions == null || + tests == null || + dates == null) { return Scaffold( backgroundColor: appStyle.colors.background, body: Column( @@ -622,6 +134,437 @@ class _HomeTimetableMonthlyScreen ), ); } + List ttDays = []; + + final meow = dates![20]; + final currentMonthStart = DateTime.utc(meow.year, meow.month, 1); + final currentMonthEnd = DateTime.utc( + meow.year, + meow.month + 1, + ).subtract(Duration(days: 1)); + + // column-major -> row-major + for (var week = 0; week < 7; week++) { + for (var day = 0; day < 7; day++) { + final d = dates![week * 7 + day]; + + bool outOfRange = + d.isBefore(currentMonthStart) || d.isAfter(currentMonthEnd); + bool isToday = + !outOfRange && timeNow().day == d.day && timeNow().month == d.month; + + Widget body = SizedBox(); + Color? todayAccent = isToday ? appStyle.colors.textPrimaryLight : null; + Color bodyBgColor = appStyle.colors.a15p; + + var lessonsToday = lessons!.where( + (lesson) => + lesson.start.isAfter(d.getMidnight()) && + lesson.end.isBefore( + d.getMidnight().add(Duration(hours: 23, minutes: 59)), + ), + ); + + var omittedLesson = lessonsToday.firstWhereOrNull( + (lesson) => + lesson.studentPresence != null && + lesson.studentPresence?.name != OmissionConsts.na && + lesson.studentPresence?.name != OmissionConsts.present, + ); + + if (lessonsToday.isNotEmpty) { + switch (activeFilter) { + case ActiveFilter.lessonNo: + body = Center( + child: Text( + lessonsToday.length.toString(), + style: appStyle.fonts.H_16px.apply( + color: + todayAccent ?? + (omittedLesson != null + ? appStyle.colors.errorText + : appStyle.colors.secondary), + ), + ), + ); + + if (omittedLesson != null) { + bodyBgColor = appStyle.colors.error15p; + } + break; + case ActiveFilter.tests: + if (lessonsToday.firstWhereOrNull( + (lesson) => tests!.any( + (test) => + test.lessonNumber == lesson.lessonNumber && + lesson.start.isAfter(test.date.getMidnight()) && + lesson.end.isBefore( + test.date.getMidnight().add( + Duration(hours: 23, minutes: 59), + ), + ), + ), + ) != + null) { + body = Center( + child: FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.editPen4Solid, + size: 20.0, + color: todayAccent ?? appStyle.colors.accent, + ), + ); + } + break; + case ActiveFilter.omissions: + if (omittedLesson == null) { + break; + } + + // TODO: Nézze meg a többi órát is + final omission = omissions!.firstWhereOrNull((omission) { + return omission.date.getMidnight().millisecondsSinceEpoch == + omittedLesson.start + .getMidnight() + .millisecondsSinceEpoch && + omission.subject.uid == omittedLesson.subject?.uid; + }); + + if (omission?.state == "Igazolt") { + body = Center( + child: FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.multiplySolid, + size: 20.0, + color: todayAccent ?? appStyle.colors.accent, + ), + ); + break; + } + + if (omission?.lateForMin != null) { + body = Center( + child: FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.timerLine, + size: 20.0, + color: todayAccent ?? appStyle.colors.warningAccent, + ), + ); + bodyBgColor = appStyle.colors.warning15p; + break; + } + + body = Center( + child: FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.restrictedSolid, + size: 20.0, + color: todayAccent ?? appStyle.colors.errorAccent, + ), + ); + bodyBgColor = appStyle.colors.error15p; + + break; + } + } + + if (isToday) { + bodyBgColor = appStyle.colors.accent; + } + + if (outOfRange) { + bodyBgColor = appStyle.colors.cardTranslucent; + } + + bool isWeekend = d.weekday > 5 && lessonsToday.isEmpty; + Color textColor = (isWeekend + ? appStyle.colors.errorText + : isToday + ? appStyle.colors.textPrimary + : appStyle.colors.textTertiary); + + if (outOfRange) { + textColor = textColor.withAlpha(77); + } else if (isWeekend) { + textColor = textColor.withAlpha(128); + } + + ttDays.add( + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 40, + height: 40, + clipBehavior: Clip.antiAlias, + decoration: ShapeDecoration( + color: bodyBgColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: body, + ), + SizedBox(height: 4), + Text( + d.format(widget.data.l10n, FormatMode.d), + style: (isToday ? appStyle.fonts.B_14SB : appStyle.fonts.B_14R) + .apply(color: textColor), + ), + ], + ), + ); + } + } + + return Scaffold( + backgroundColor: appStyle.colors.background, + body: Stack( + children: [ + SizedBox( + width: MediaQuery.of(context).size.width, + height: 74 + 16, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.data.l10n.timetable, + style: appStyle.fonts.H_H2.apply( + color: appStyle.colors.textPrimary, + ), + ), + Row( + children: [ + GestureDetector( + child: Card( + color: appStyle.colors.buttonSecondaryFill, + child: Padding( + padding: const EdgeInsets.all(8), + child: FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.tableSolid, + size: 26.0, + color: appStyle.colors.accent, + ), + ), + ), + onTap: () { + context.pop(); + }, + ), + // Nincs elkészítve jelenleg: Dolgozat stb hozzáadása(?) + // Card( + // color: appStyle.colors.buttonSecondaryFill, + // child: Padding( + // padding: const EdgeInsets.all(4), + // child: FirkaIconWidget( + // FirkaIconType.majesticons, + // Majesticon.plusLine, + // size: 32.0, + // color: appStyle.colors.accent, + // ), + // ), + // ), + GestureDetector( + child: Card( + color: appStyle.colors.buttonSecondaryFill, + child: Padding( + padding: const EdgeInsets.all(8), + child: FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.settingsCogSolid, + size: 26.0, + color: appStyle.colors.accent, + ), + ), + ), + onTap: () { + showSettingsSheet( + context, + MediaQuery.of(context).size.height * 0.4, + widget.data, + widget.data.settings + .group("settings") + .subGroup("timetable_toast"), + ); + }, + ), + ], + ), + ], + ), + SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + behavior: HitTestBehavior.translucent, + child: SizedBox( + width: 24, + height: 24, + child: FirkaIconWidget( + FirkaIconType.icons, + "dropdownLeft", + size: 24, + color: appStyle.colors.accent, + ), + ), + onTap: () async { + var newNow = DateTime(now!.year, now!.month - 1); + setState(() { + now = newNow; + lessons = null; + dates = null; + }); + await initForMonth(newNow); + setState(() { + now = newNow; + }); + }, + ), + Text( + now! + .format(widget.data.l10n, FormatMode.yyyymmmm) + .toLowerCase(), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + GestureDetector( + child: FirkaIconWidget( + FirkaIconType.icons, + "dropdownRight", + size: 24, + color: appStyle.colors.accent, + ), + onTap: () async { + var newNow = DateTime(now!.year, now!.month + 1); + setState(() { + now = newNow; + lessons = null; + dates = null; + }); + await initForMonth(newNow); + setState(() { + now = newNow; + }); + }, + ), + ], + ), + ], + ), + ), + ), + TransparentPointer( + child: Padding( + padding: const EdgeInsets.only( + top: 74 + 16 + 12, + left: 20, + right: 20, + ), + child: GridView.count( + crossAxisCount: 7, + mainAxisSpacing: 16, + mainAxisExtent: 62, + children: ttDays, + ), + ), + ), + TransparentPointer( + child: Align( + alignment: Alignment.bottomCenter, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 8, + children: [ + _StatusToast( + FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.clockSolid, + color: appStyle.colors.accent, + size: 16, + ), + lessons! + .where( + (lesson) => + lesson.start.isAfter(currentMonthStart) && + lesson.end.isBefore(currentMonthEnd), + ) + .length, + activeFilter == ActiveFilter.lessonNo, + () { + setState(() { + activeFilter = ActiveFilter.lessonNo; + }); + }, + ), + _StatusToast( + FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.editPen4Solid, + color: appStyle.colors.accent, + size: 16, + ), + lessons! + .where( + (lesson) => tests!.any( + (test) => + test.lessonNumber == lesson.lessonNumber && + lesson.start.isAfter(test.date.getMidnight()) && + lesson.end.isBefore( + test.date.getMidnight().add( + Duration(hours: 23, minutes: 59), + ), + ), + ), + ) + .length, + activeFilter == ActiveFilter.tests, + () { + setState(() { + activeFilter = ActiveFilter.tests; + }); + }, + ), + _StatusToast( + FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.timerLine, + color: appStyle.colors.accent, + size: 16, + ), + lessons! + .where( + (lesson) => + lesson.start.isAfter(currentMonthStart) && + lesson.end.isBefore(currentMonthEnd) && + lesson.studentPresence != null && + lesson.studentPresence?.name != + OmissionConsts.na && + lesson.studentPresence?.name != + OmissionConsts.present, + ) + .length, + activeFilter == ActiveFilter.omissions, + () { + setState(() { + activeFilter = ActiveFilter.omissions; + }); + }, + ), + ], + ), + ), + ), + ], + ), + ); } } @@ -638,29 +581,29 @@ class _StatusToast extends StatelessWidget { return GestureDetector( onTap: _onTap, child: Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 6), clipBehavior: Clip.antiAlias, decoration: ShapeDecoration( color: _active ? appStyle.colors.buttonSecondaryFill : appStyle.colors.cardTranslucent, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(16), ), ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), - child: Row( - children: [ - _icon, - SizedBox(width: 6), - Text( - _count.toString(), - style: appStyle.fonts.H_16px.apply( - color: appStyle.colors.textPrimary, - ), + child: Row( + spacing: 6, + children: [ + _icon, + Text( + _count.toString(), + style: appStyle.fonts.H_16px.apply( + color: _active + ? appStyle.colors.textPrimary + : appStyle.colors.textSecondary, ), - ], - ), + ), + ], ), ), );