diff --git a/firka/lib/core/extensions.dart b/firka/lib/core/extensions.dart index 570ddf0..efcea41 100644 --- a/firka/lib/core/extensions.dart +++ b/firka/lib/core/extensions.dart @@ -14,10 +14,9 @@ extension TimetableExtension on Iterable { for (var lesson in this) { if (lesson.lessonNumber == null) continue; - if (lessons.firstWhereOrNull( - (lesson2) => lesson.lessonNumber == lesson2.lessonNumber, - ) == - null) { + if (!lessons.any( + (lesson2) => lesson.lessonNumber == lesson2.lessonNumber, + )) { final ref = reference.start; final newStart = DateTime( ref.year, @@ -69,6 +68,14 @@ extension DurationExtension on Duration { String seconds = inSeconds.remainder(60).toString().padLeft(2, '0'); return "$hours:$minutes:$seconds"; } + + String timeLeft(AppLocalizations l10n) { + return inMinutes > 1 + ? "$inMinutes ${inMinutes == 1 ? l10n.starting_min : l10n.starting_min_plural}" + : inSeconds > 0 + ? "$inSeconds ${inSeconds == 1 ? l10n.starting_sec : l10n.starting_sec_plural}" + : "- ${l10n.starting_sec}"; + } } enum FormatMode { @@ -89,6 +96,20 @@ enum FormatMode { enum Cycle { morning, day, afternoon, night } +extension ComparableExtension> on Comparable { + T min(T other) { + return compareTo(other) < 0 ? this as T : other; + } + + bool isBetween(T from, T to) { + return compareTo(from) > 0 && compareTo(to) < 0; + } + + T max(T other) { + return compareTo(other) > 0 ? this as T : other; + } +} + extension DateExtension on DateTime { String? translatedDay(AppLocalizations l10n) { var today = timeNow().getMidnight(); diff --git a/firka/lib/ui/phone/widgets/lesson.dart b/firka/lib/ui/phone/widgets/lesson.dart index de28b7e..bbc0e03 100644 --- a/firka/lib/ui/phone/widgets/lesson.dart +++ b/firka/lib/ui/phone/widgets/lesson.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:firka/core/extensions.dart'; import 'package:firka/core/settings.dart'; import 'package:firka/ui/components/firka_card.dart'; @@ -19,6 +21,8 @@ class LessonWidget extends StatelessWidget { final AppInitialization data; final int? lessonNo; final Lesson lesson; + final bool expanded; + final bool active; final Test? test; const LessonWidget( @@ -26,6 +30,8 @@ class LessonWidget extends StatelessWidget { this.lessonNo, this.lesson, this.test, { + this.active = false, + this.expanded = false, super.key, }); @@ -72,6 +78,8 @@ class LessonWidget extends StatelessWidget { var roomName = lesson.roomName ?? 'N/A'; + final spacing = expanded ? 8.0 : 12.0; + elements.add( GestureDetector( onTap: () { @@ -88,140 +96,222 @@ class LessonWidget extends StatelessWidget { ); }, child: FirkaCard.single( - height: 64, + height: expanded ? 104 : 64, + borderColor: active ? appStyle.colors.accent : null, margin: EdgeInsets.all(0), - padding: EdgeInsets.only(left: 14, right: 16), + padding: EdgeInsets.only(left: expanded ? 16 : 14, right: 16), color: isDismissed ? appStyle.colors.cardTranslucent : appStyle.colors.card, - attached: showTests && test != null ? Attach.bottom : Attach.none, + attached: !expanded && showTests && test != null + ? Attach.bottom + : Attach.none, shadow: !isDismissed, - child: Row( + child: Column( + spacing: 12, + mainAxisAlignment: MainAxisAlignment.center, children: [ - !showLessonNos - ? SizedBox() - : SizedBox( - width: 18, - height: 18, - child: Stack( - alignment: Alignment.center, - children: [ - SvgPicture.asset( - "assets/icons/subtract.svg", - color: bgColor, - width: 18, - height: 18, + Row( + children: [ + !showLessonNos + ? SizedBox() + : SizedBox( + width: 18, + height: 18, + child: Stack( + alignment: Alignment.center, + children: [ + SvgPicture.asset( + "assets/icons/subtract.svg", + color: bgColor, + width: 18, + height: 18, + ), + Text( + lessonNo.toString(), + style: appStyle.fonts.B_12R.apply( + color: secondary, + ), + textAlign: TextAlign.center, + ), + ], ), - Text( - lessonNo.toString(), - style: appStyle.fonts.B_12R.apply(color: secondary), - textAlign: TextAlign.center, - ), - ], - ), + ), + FilledCircle( + diameter: expanded ? 32 : 36, + color: bgColor, + child: ClassIconWidget( + uid: lesson.uid, + className: lesson.name, + category: subjectName, + color: accent, + size: expanded ? 20 : 24, ), - FilledCircle( - diameter: 36, - color: bgColor, - child: ClassIconWidget( - uid: lesson.uid, - className: lesson.name, - category: subjectName, - color: accent, - size: 24, - ), + ), + SizedOverflowBox( + size: Size(spacing, 0), + child: !expanded && !showTests && test != null + ? Transform.translate( + offset: Offset(4, -20), + child: BubbleTest(), + ) + : SizedBox(), + ), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + spacing: spacing, + children: [ + LimitedBox( + maxWidth: 155, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + subjectName, + style: appStyle.fonts.B_16SB.apply( + color: !isDismissed + ? appStyle.colors.textPrimary + : appStyle.colors.textSecondary, + ), + overflow: TextOverflow.ellipsis, + ), + showSubstitutions + ? Text( + lesson.substituteTeacher!, + style: appStyle.fonts.B_14R.apply( + color: appStyle.colors.textSecondary, + ), + ) + : SizedBox(), + ], + ), + ), + Flexible( + fit: FlexFit.loose, + child: Card( + shadowColor: Colors.transparent, + color: appStyle.colors.a15p, + margin: EdgeInsets.all(0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 6), + child: Text( + roomName, + style: appStyle.fonts.B_12R.apply( + color: appStyle.colors.textSecondary, + ), + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + ), + ), + ), + ), + ], + ), + ), + SizedBox(width: 8), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + lesson.start.toLocal().format( + data.l10n, + FormatMode.hmm, + ), + style: appStyle.fonts.B_14R.apply( + color: appStyle.colors.textPrimary, + ), + ), + if (!expanded) + Text( + lesson.end.toLocal().format( + data.l10n, + FormatMode.hmm, + ), + style: appStyle.fonts.B_14R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), + ], ), - SizedOverflowBox( - size: Size(12, 0), - child: !showTests && test != null - ? Transform.translate( - offset: Offset(4, -20), - child: BubbleTest(), - ) - : SizedBox(), - ), - Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - spacing: 12, + if (expanded && test == null) + Column( + spacing: 4, children: [ - LimitedBox( - maxWidth: 155, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - subjectName, - style: appStyle.fonts.B_16SB.apply( - color: !isDismissed - ? appStyle.colors.textPrimary - : appStyle.colors.textSecondary, - ), - overflow: TextOverflow.ellipsis, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + lesson.end + .difference(timeNow().max(lesson.start)) + .timeLeft(initData.l10n), + style: appStyle.fonts.B_14R.apply( + color: appStyle.colors.textSecondary, ), - showSubstitutions - ? Text( - lesson.substituteTeacher!, - style: appStyle.fonts.B_14R.apply( - color: appStyle.colors.textSecondary, - ), - ) - : SizedBox(), - ], - ), + ), + Text( + lesson.end.format(initData.l10n, FormatMode.hmm), + style: appStyle.fonts.B_14R.apply( + color: appStyle.colors.textSecondary, + ), + ), + ], ), - Flexible( - fit: FlexFit.loose, - child: Card( - shadowColor: Colors.transparent, - color: appStyle.colors.a15p, - margin: EdgeInsets.all(0), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 6), - child: Text( - roomName, - style: appStyle.fonts.B_12R.apply( - color: appStyle.colors.textSecondary, - ), - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.center, - ), - ), + ClipRRect( + borderRadius: BorderRadius.circular(16), + child: LinearProgressIndicator( + value: + timeNow().difference(lesson.start).inMilliseconds / + lesson.end.difference(lesson.start).inMilliseconds, + backgroundColor: appStyle.colors.a15p, + color: appStyle.colors.accent, + minHeight: 8, ), ), ], ), - ), - SizedBox(width: 8), - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - lesson.start.toLocal().format(data.l10n, FormatMode.hmm), - style: appStyle.fonts.B_14R.apply( - color: appStyle.colors.textPrimary, + if (expanded && test != null) + Container( + height: 28, + padding: EdgeInsets.symmetric(horizontal: 6), + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), ), + color: appStyle.colors.background, ), - Text( - lesson.end.toLocal().format(data.l10n, FormatMode.hmm), - style: appStyle.fonts.B_14R.apply( - color: appStyle.colors.textPrimary, - ), + child: Row( + children: [ + FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.editPen4Solid, + size: 12, + color: appStyle.colors.accent, + ), + SizedBox(width: 8), + Text( + test!.theme, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], ), - ], - ), + ), ], ), ), ), ); - if (test != null && showTests) { + if (!expanded && test != null && showTests) { var theme = test!.theme.firstUpper(); var method = test!.method.description.firstUpper(); diff --git a/firka/lib/ui/phone/widgets/tt_day.dart b/firka/lib/ui/phone/widgets/tt_day.dart index 11319b9..f4ff971 100644 --- a/firka/lib/ui/phone/widgets/tt_day.dart +++ b/firka/lib/ui/phone/widgets/tt_day.dart @@ -1,4 +1,5 @@ import 'package:firka/core/settings.dart'; +import 'package:firka_common/firka_common.dart'; import 'package:kreta_api/kreta_api.dart'; import 'package:firka/core/extensions.dart'; import 'package:firka/ui/components/firka_card.dart'; @@ -104,6 +105,10 @@ class TimeTableDayWidget extends StatelessWidget { tests.firstWhereOrNull( (test) => test.lessonNumber == lesson.lessonNumber, ), + active: timeNow().isBetween( + i > 0 ? lessons[i - 1].end : lesson.start, + lesson.end, + ), ), );