From e1b571afa05f4accaa5f7b457cf373646fda49e0 Mon Sep 17 00:00:00 2001 From: checkedear <271323618+checkedear@users.noreply.github.com> Date: Thu, 4 Jun 2026 12:20:43 +0200 Subject: [PATCH] feat: test bottom sheet and design fixes --- firka/lib/api/consts.dart | 2 +- .../ui/components/common_bottom_sheets.dart | 115 ++++++--- .../lib/ui/phone/pages/home/home_grades.dart | 3 +- firka/lib/ui/phone/pages/home/home_main.dart | 180 +------------- firka/lib/ui/phone/widgets/grade_chart.dart | 28 +-- .../ui/phone/widgets/grade_summary_bar.dart | 141 +++++------ firka/lib/ui/phone/widgets/info_card.dart | 2 + firka/lib/ui/phone/widgets/lesson.dart | 28 +-- firka/lib/ui/phone/widgets/lesson_big.dart | 25 +- firka/lib/ui/phone/widgets/lesson_slider.dart | 235 ++++++++++++++++++ firka/lib/ui/phone/widgets/tt_day.dart | 5 +- .../lib/ui/components/filled_circle.dart | 8 +- 12 files changed, 420 insertions(+), 352 deletions(-) create mode 100644 firka/lib/ui/phone/widgets/lesson_slider.dart diff --git a/firka/lib/api/consts.dart b/firka/lib/api/consts.dart index d431161..864a378 100644 --- a/firka/lib/api/consts.dart +++ b/firka/lib/api/consts.dart @@ -16,7 +16,7 @@ class Constants { } static const applicationId = "hu.ekreta.student"; - static const applicationVersion = "5.7.0"; + static const applicationVersion = "5.15.0"; static String get userAgent { if (Platform.isAndroid) { diff --git a/firka/lib/ui/components/common_bottom_sheets.dart b/firka/lib/ui/components/common_bottom_sheets.dart index 60ae86e..d761401 100644 --- a/firka/lib/ui/components/common_bottom_sheets.dart +++ b/firka/lib/ui/components/common_bottom_sheets.dart @@ -3,22 +3,17 @@ import 'package:firka_common/ui/components/filled_circle.dart'; import 'package:firka_common/ui/shared/grade_small_card.dart'; import 'package:kreta_api/kreta_api.dart'; import 'package:firka/data/models/homework_cache_model.dart'; -import 'package:firka/core/debug_helper.dart'; import 'package:firka/core/extensions.dart'; import 'package:firka/core/settings.dart'; -import 'package:firka/ui/components/firka_shadow.dart'; import 'package:firka/ui/shared/firka_icon.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_svg/svg.dart'; import 'package:majesticons_flutter/majesticons_flutter.dart'; import 'package:intl/intl.dart'; import 'package:firka/app/app_state.dart'; -import 'package:firka/core/bloc/theme_cubit.dart'; import 'package:firka/ui/theme/style.dart'; -import 'package:firka/ui/phone/pages/home/home_grades.dart'; import 'package:firka/ui/phone/widgets/lesson.dart'; import 'package:go_router/go_router.dart'; import 'package:firka/ui/shared/class_icon.dart'; @@ -82,7 +77,6 @@ Future showLessonBottomSheet( BuildContext context, AppInitialization data, Lesson lesson, - int? lessonNo, Color accent, Color secondary, Color bgColor, @@ -110,26 +104,28 @@ Future showLessonBottomSheet( showFirkaBottomSheet(context, [ Row( children: [ - SizedBox( - width: 24, - height: 24, - child: Stack( - alignment: Alignment.center, - children: [ - SvgPicture.asset( - "assets/icons/subtract.svg", - color: bgColor, + lesson.lessonNumber == null + ? SizedBox() + : SizedBox( width: 24, height: 24, + child: Stack( + alignment: Alignment.center, + children: [ + SvgPicture.asset( + "assets/icons/subtract.svg", + color: bgColor, + width: 24, + height: 24, + ), + Text( + lesson.lessonNumber!.toString(), + style: appStyle.fonts.B_16R.apply(color: secondary), + textAlign: TextAlign.center, + ), + ], + ), ), - Text( - lessonNo.toString(), - style: appStyle.fonts.B_16R.apply(color: secondary), - textAlign: TextAlign.center, - ), - ], - ), - ), FilledCircle( diameter: 40, color: bgColor, @@ -230,22 +226,12 @@ Future showLessonBottomSheet( Future showTestBottomSheet( BuildContext context, AppInitialization data, - Lesson lesson, - int? lessonNo, - Color accent, - Color secondary, - Color bgColor, Test test, ) async { - final date = lesson.start; + final date = test.date; final formattedDate = "${date.format(data.l10n, FormatMode.yearly).firstUpper()}, ${DateFormat.EEEE(data.l10n.localeName).format(date).firstUpper()}"; - final statsForNerdsEnabled = data.settings - .group("settings") - .subGroup("developer") - .boolean("stats_for_nerds"); - showFirkaBottomSheet(context, [ FilledCircle( diameter: 40, @@ -264,7 +250,7 @@ Future showTestBottomSheet( spacing: 4, children: [ Text( - "${test.theme} ${statsForNerdsEnabled ? "(${lesson.classGroup?.name ?? ''})" : ""}", + "${test.theme}", style: appStyle.fonts.H_18px.apply( color: appStyle.colors.textPrimary, ), @@ -297,7 +283,64 @@ Future showTestBottomSheet( ), ), SizedBox(height: 10), - LessonWidget(data, lessonNo, lesson, null), + FirkaCard.single( + height: 64, + margin: EdgeInsets.all(0), + padding: EdgeInsets.only(left: 14, right: 16), + color: appStyle.colors.card, + child: Column( + spacing: 12, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + SizedBox( + width: 18, + height: 18, + child: Stack( + alignment: Alignment.center, + children: [ + SvgPicture.asset( + "assets/icons/subtract.svg", + color: appStyle.colors.a15p, + width: 18, + height: 18, + ), + Text( + test.lessonNumber.toString(), + style: appStyle.fonts.B_14SB.apply( + color: appStyle.colors.secondary, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + FilledCircle( + diameter: 36, + color: appStyle.colors.a15p, + child: ClassIconWidget( + uid: test.uid, + className: test.subjectName, + category: test.subject.name, + color: appStyle.colors.accent, + size: 24, + ), + ), + Expanded( + child: Text( + test.subject.name, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ], + ), + ), ]); } diff --git a/firka/lib/ui/phone/pages/home/home_grades.dart b/firka/lib/ui/phone/pages/home/home_grades.dart index 0e9441c..2753cc6 100644 --- a/firka/lib/ui/phone/pages/home/home_grades.dart +++ b/firka/lib/ui/phone/pages/home/home_grades.dart @@ -164,8 +164,9 @@ class _HomeGradesScreen extends FirkaState { ), ], ), + SizedBox(height: 20), GradeChartWithInteraction(grades: allGrades), - SizedBox(height: 2), + SizedBox(height: 10), GradeSummaryBar(grades: allGrades, l10n: widget.data.l10n), SizedBox(height: 20), Expanded( diff --git a/firka/lib/ui/phone/pages/home/home_main.dart b/firka/lib/ui/phone/pages/home/home_main.dart index 6101c91..7f6c09e 100644 --- a/firka/lib/ui/phone/pages/home/home_main.dart +++ b/firka/lib/ui/phone/pages/home/home_main.dart @@ -60,7 +60,7 @@ class _HomeMainScreen extends FirkaState { } Future fetchData({bool cacheOnly = false}) async { - final midnight = now.getMidnight(); + final midnight = timeNow().getMidnight(); var lessonsFetched = 0; var noticeBoardFetched = 0; @@ -213,6 +213,7 @@ class _HomeMainScreen extends FirkaState { Widget _buildContent(BuildContext context) { if (student != null && lessons != null) { + final now = timeNow(); final infoItems = [...(infoBoard ?? []), ...(noticeBoard ?? [])]; final gradeItems = grades ?? []; final testItems = tests ?? []; @@ -239,11 +240,7 @@ class _HomeMainScreen extends FirkaState { (item1, item2) => item2.$2.difference(item1.$2).inMilliseconds, ); - var currentLesson = lessons!.firstWhereOrNull( - (lesson) => now.isBefore(lesson.end), - ); - - Map lessonTestMap = Map.fromEntries( + LinkedHashMap lessonTestMap = LinkedHashMap.fromEntries( lessons!.indexed.map( (i) => MapEntry( i.$2, @@ -256,30 +253,13 @@ class _HomeMainScreen extends FirkaState { ), ); - int tmpIndex = lessons!.isEmpty || now.isBefore(lessons!.first.start) - ? 0 - : currentLesson == null - ? lessons!.length + 1 - : lessons!.indexOf(currentLesson) + 1; - - if (activeLessonIndex == null || tmpIndex != activeLessonIndex) { - activeLessonIndex = tmpIndex; - swipeBack = 0; - } - - centeredPageIndex ??= activeLessonIndex!; - - if (controller.ready && swipeBack == 0) { - controller.animateToPage(activeLessonIndex!); - } - int testsTomorrow = testItems .where( (test) => test.date.isAfter( now.getMidnight().add(Duration(hours: 23, minutes: 59)), ) && - test.date.isBefore(now.getMidnight().add(Duration(days: 1))), + test.date.isBefore(now.getMidnight().add(Duration(days: 2))), ) .length; @@ -290,157 +270,7 @@ class _HomeMainScreen extends FirkaState { children: [ WelcomeWidget(widget.data.l10n, now, student!, lessons!), SizedBox(height: 48), - if (lessons!.isNotEmpty) - OverflowBox( - maxWidth: MediaQuery.widthOf(context), - fit: OverflowBoxFit.deferToChild, - child: CarouselSlider( - items: [ - Padding( - padding: EdgeInsets.symmetric(horizontal: 5), - child: StartingSoonWidget( - widget.data.l10n, - now, - lessons!, - ), - ), - ...lessonTestMap.entries.map( - (entry) => Padding( - padding: EdgeInsets.symmetric(horizontal: 5), - child: LessonBigWidget( - widget.data, - lessons!.getLessonNo(entry.key), - entry.key, - entry.value, - active: currentLesson == entry.key, - ), - ), - ), - Padding( - padding: EdgeInsets.symmetric(horizontal: 5), - child: FirkaCard.single( - padding: EdgeInsets.all(16), - margin: EdgeInsets.only(bottom: 1), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - FilledCircle( - diameter: 32, - color: appStyle.colors.a15p, - child: FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.moonSolid, - size: 20, - color: appStyle.colors.accent, - ), - ), - SizedBox(width: 8), - Text( - testsTomorrow == 0 - ? widget.data.l10n.tt_no_classes_l2 - : widget.data.l10n.get_ready, - style: appStyle.fonts.B_16R.apply( - color: appStyle.colors.textPrimary, - ), - ), - ], - ), - Container( - height: 28, - padding: EdgeInsets.symmetric(horizontal: 6), - decoration: ShapeDecoration( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), - color: appStyle.colors.background, - ), - child: Row( - children: [ - FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.editPen4Solid, - size: 12, - color: appStyle.colors.accent, - ), - SizedBox(width: 8), - Text( - testsTomorrow == 0 - ? widget.data.l10n.no_tests_tomorrow - : widget.data.l10n.tests_tomorrow( - testsTomorrow.toString(), - ), - style: appStyle.fonts.B_16R.apply( - color: appStyle.colors.textPrimary, - ), - ), - ], - ), - ), - ], - ), - ), - ), - ], - carouselController: controller, - options: CarouselOptions( - initialPage: activeLessonIndex!, - height: 106, - viewportFraction: 346 / 376, - enableInfiniteScroll: false, - onPageChanged: (index, reason) { - centeredPageIndex = index; - if (index == activeLessonIndex) { - swipeBack = null; - } else if (reason == CarouselPageChangedReason.manual) { - swipeBack = 5; - } else { - return; - } - setState(() {}); - }, - ), - ), - ), - if (lessons!.isNotEmpty) SizedBox(height: 12), - if (lessons!.isNotEmpty) - SizedBox( - height: 16, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - spacing: 8, - children: [ - FirkaIconWidget( - FirkaIconType.majesticonsLocal, - "sunSolid", - color: centeredPageIndex == 0 - ? appStyle.colors.accent - : appStyle.colors.accent.withAlpha(128), - size: centeredPageIndex == 0 ? 16 : 12, - ), - ...lessons!.indexed.map( - (i) => FilledCircle( - diameter: centeredPageIndex == i.$1 + 1 ? 10 : 8, - color: centeredPageIndex == i.$1 + 1 - ? appStyle.colors.accent - : appStyle.colors.accent.withAlpha(128), - child: SizedBox(), - ), - ), - FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.moonSolid, - color: centeredPageIndex == lessons!.length + 1 - ? appStyle.colors.accent - : appStyle.colors.accent.withAlpha(128), - size: centeredPageIndex == lessons!.length + 1 ? 16 : 12, - ), - ], - ), - ), - if (lessons!.isNotEmpty) SizedBox(height: 12), + LessonSlider(lessonTestMap, testsTomorrow), Expanded( child: RefreshIndicator( onRefresh: () => fetchData(), diff --git a/firka/lib/ui/phone/widgets/grade_chart.dart b/firka/lib/ui/phone/widgets/grade_chart.dart index 3b1d089..3b6e5b3 100644 --- a/firka/lib/ui/phone/widgets/grade_chart.dart +++ b/firka/lib/ui/phone/widgets/grade_chart.dart @@ -1,7 +1,7 @@ -import 'dart:math' as math; import 'package:firka/app/app_state.dart'; import 'package:firka/core/settings.dart'; import 'package:firka_common/firka_common.dart'; +import 'package:firka_common/ui/components/filled_circle.dart'; import 'package:intl/intl.dart'; import 'package:kreta_api/kreta_api.dart'; import 'package:firka/routing/chart_interaction_scope.dart'; @@ -90,6 +90,7 @@ class _GradeChartState extends State { @override Widget build(BuildContext context) { return FirkaCard.single( + margin: EdgeInsets.all(0), child: ClipRRect( borderRadius: BorderRadius.circular(16), child: AspectRatio(aspectRatio: 1.82, child: LineChart(avgData())), @@ -147,24 +148,13 @@ class _GradeChartState extends State { required Color bgColor, required Color textColor, }) { - return SizedBox( - width: 24, - height: 24, - child: Material( - shape: const CircleBorder(), - color: bgColor, - child: Center( - child: Text( - text, - textAlign: TextAlign.center, - style: TextStyle( - color: textColor, - fontSize: 14, - fontWeight: FontWeight.bold, - fontFamily: appStyle.fonts.B_14SB.fontFamily, - ), - ), - ), + return FilledCircle( + diameter: 18, + color: bgColor, + child: Text( + text, + textAlign: TextAlign.center, + style: appStyle.fonts.H_14px.copyWith(color: textColor), ), ); } diff --git a/firka/lib/ui/phone/widgets/grade_summary_bar.dart b/firka/lib/ui/phone/widgets/grade_summary_bar.dart index 2894bb6..87a92ce 100644 --- a/firka/lib/ui/phone/widgets/grade_summary_bar.dart +++ b/firka/lib/ui/phone/widgets/grade_summary_bar.dart @@ -1,3 +1,4 @@ +import 'package:firka/ui/components/firka_card.dart'; import 'package:kreta_api/kreta_api.dart'; import 'package:firka/ui/components/grade.dart'; import 'package:firka/ui/components/grade_helpers.dart'; @@ -33,89 +34,75 @@ class _GradeSummaryBarState extends State { final averageText = widget.showAverage ? (widget.grades.getAverage() ?? 0).toStringAsFixed(2) : ''; + final bar = ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Row( + children: List.generate(5, (i) { + final flex = total > 0 ? countsByGrade[i] : 1; + return Expanded( + flex: flex, + child: Container(height: 12, color: getGradeColor(i + 1)), + ); + }), + ), + ); - return Card( - shadowColor: Colors.transparent, - color: appStyle.colors.a15p, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - child: InkWell( - onTap: () => setState(() => _expanded = !_expanded), - borderRadius: BorderRadius.circular(16), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( - children: [ - Text( - widget.showAverage - ? '${widget.l10n.gradesCount(total)} ($averageText)' - : widget.l10n.gradesCount(total), - style: appStyle.fonts.B_16SB.apply( - color: appStyle.colors.textPrimary, - ), - ), - const SizedBox(width: 12), - Expanded( - child: ClipRRect( - borderRadius: BorderRadius.circular(4), - child: Row( - children: List.generate(5, (i) { - final flex = total > 0 ? countsByGrade[i] : 1; - return Expanded( - flex: flex, - child: Container( - height: 10, - color: getGradeColor(i + 1), - ), - ); - }), - ), - ), - ), - const SizedBox(width: 12), - FirkaIconWidget( - FirkaIconType.majesticons, - _expanded - ? Majesticon.chevronUpLine - : Majesticon.chevronDownLine, + return GestureDetector( + onTap: () => setState(() => _expanded = !_expanded), + child: FirkaCard.single( + margin: EdgeInsets.zero, + padding: EdgeInsets.symmetric(horizontal: 16), + height: _expanded ? 115 : 52, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + spacing: 12, + children: [ + Row( + spacing: 12, + children: [ + Text( + widget.showAverage + ? '${widget.l10n.gradesCount(total)} ($averageText)' + : widget.l10n.gradesCount(total), + style: appStyle.fonts.B_16SB.apply( color: appStyle.colors.textPrimary, - size: 24, ), - ], - ), - if (_expanded) ...[ - const SizedBox(height: 12), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: List.generate(5, (i) { - final grade = i + 1; - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - width: 32, - height: 32, - child: FittedBox( - child: GradeWidget.gradeValue(grade), - ), - ), - const SizedBox(width: 4), - Text( - countsByGrade[i].toString(), - style: appStyle.fonts.B_16SB.apply( - color: appStyle.colors.textPrimary, - ), - ), - ], - ); - }), + ), + Expanded(child: _expanded ? SizedBox() : bar), + FirkaIconWidget( + FirkaIconType.majesticons, + _expanded + ? Majesticon.chevronUpLine + : Majesticon.chevronDownLine, + color: appStyle.colors.textPrimary, + size: 24, ), ], + ), + if (_expanded) ...[ + bar, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(5, (i) { + final grade = i + 1; + return Row( + mainAxisSize: MainAxisSize.min, + spacing: 6, + children: [ + GradeWidget.gradeValue(grade, size: 27), + Text( + countsByGrade[i].toString(), + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ); + }), + ), ], - ), + ], ), ), ); diff --git a/firka/lib/ui/phone/widgets/info_card.dart b/firka/lib/ui/phone/widgets/info_card.dart index 0a4059d..f1ac7ca 100644 --- a/firka/lib/ui/phone/widgets/info_card.dart +++ b/firka/lib/ui/phone/widgets/info_card.dart @@ -58,6 +58,7 @@ class InfoCard extends StatelessWidget { ), texts: [test.theme.firstUpper(), test.subject.name.firstUpper()], right: [buildSubject(color, test.subject)], + onTap: (context) => showTestBottomSheet(context, initData, test), ); } @@ -77,6 +78,7 @@ class InfoCard extends StatelessWidget { ), texts: [test.theme.firstUpper(), test.method.description.firstUpper()], right: [buildSubject(color, test.subject)], + onTap: (context) => showTestBottomSheet(context, initData, test), ); } diff --git a/firka/lib/ui/phone/widgets/lesson.dart b/firka/lib/ui/phone/widgets/lesson.dart index 6f3f7d9..da685b9 100644 --- a/firka/lib/ui/phone/widgets/lesson.dart +++ b/firka/lib/ui/phone/widgets/lesson.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:firka/core/extensions.dart'; import 'package:firka/core/settings.dart'; import 'package:firka/ui/components/firka_card.dart'; @@ -11,7 +9,6 @@ import 'package:flutter_svg/svg.dart'; import 'package:majesticons_flutter/majesticons_flutter.dart'; import 'package:kreta_api/kreta_api.dart'; -import 'package:firka/core/debug_helper.dart'; import 'package:firka/ui/components/common_bottom_sheets.dart'; import 'package:firka/ui/shared/class_icon.dart'; import 'package:firka/ui/shared/firka_icon.dart'; @@ -19,14 +16,12 @@ import 'bubble_test.dart'; class LessonWidget extends StatelessWidget { final AppInitialization data; - final int? lessonNo; final Lesson lesson; final bool active; final Test? test; const LessonWidget( this.data, - this.lessonNo, this.lesson, this.test, { this.active = false, @@ -48,7 +43,7 @@ class LessonWidget extends StatelessWidget { .subGroup("timetable_toast") .boolean("substitution"); final showLessonNos = - lessonNo != null && + lesson.lessonNumber != null && data.settings .group("settings") .subGroup("timetable_toast") @@ -79,12 +74,10 @@ class LessonWidget extends StatelessWidget { elements.add( GestureDetector( onTap: () { - if (lessonNo == null) return; showLessonBottomSheet( context, data, lesson, - lessonNo, accent, secondary, bgColor, @@ -122,8 +115,8 @@ class LessonWidget extends StatelessWidget { height: 18, ), Text( - lessonNo.toString(), - style: appStyle.fonts.B_12R.apply( + lesson.lessonNumber!.toString(), + style: appStyle.fonts.B_14SB.apply( color: secondary, ), textAlign: TextAlign.center, @@ -195,8 +188,8 @@ class LessonWidget extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: 6), child: Text( roomName, - style: appStyle.fonts.B_12R.apply( - color: appStyle.colors.textSecondary, + style: appStyle.fonts.B_14R.apply( + color: appStyle.colors.secondary, ), overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, @@ -244,16 +237,7 @@ class LessonWidget extends StatelessWidget { elements.add( GestureDetector( onTap: () { - showTestBottomSheet( - context, - data, - lesson, - lessonNo, - accent, - secondary, - bgColor, - test!, - ); + showTestBottomSheet(context, data, test!); }, child: FirkaCard.single( height: 48, diff --git a/firka/lib/ui/phone/widgets/lesson_big.dart b/firka/lib/ui/phone/widgets/lesson_big.dart index 3570d81..30ebdc2 100644 --- a/firka/lib/ui/phone/widgets/lesson_big.dart +++ b/firka/lib/ui/phone/widgets/lesson_big.dart @@ -1,5 +1,4 @@ import 'package:firka/core/extensions.dart'; -import 'package:firka/core/settings.dart'; import 'package:firka/ui/components/firka_card.dart'; import 'package:firka/app/app_state.dart'; import 'package:firka/ui/theme/style.dart'; @@ -13,7 +12,6 @@ import 'package:firka/core/debug_helper.dart'; import 'package:firka/ui/components/common_bottom_sheets.dart'; import 'package:firka/ui/shared/class_icon.dart'; import 'package:firka/ui/shared/firka_icon.dart'; -import 'bubble_test.dart'; class LessonBigWidget extends StatelessWidget { final AppInitialization data; @@ -96,7 +94,7 @@ class LessonBigWidget extends StatelessWidget { ) : Container( height: 28, - padding: EdgeInsets.symmetric(horizontal: 6), + padding: EdgeInsets.symmetric(horizontal: 8), decoration: ShapeDecoration( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), @@ -112,12 +110,14 @@ class LessonBigWidget extends StatelessWidget { color: appStyle.colors.accent, ), SizedBox(width: 8), - Text( - test!.theme, - style: appStyle.fonts.B_16R.apply( - color: appStyle.colors.textPrimary, + Expanded( + child: Text( + test!.theme, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + overflow: TextOverflow.ellipsis, ), - overflow: TextOverflow.ellipsis, ), ], ), @@ -129,7 +129,6 @@ class LessonBigWidget extends StatelessWidget { context, data, lesson, - lessonNo, accent, secondary, bgColor, @@ -139,7 +138,7 @@ class LessonBigWidget extends StatelessWidget { child: FirkaCard.single( height: 104, borderColor: active ? appStyle.colors.accent : null, - margin: EdgeInsets.all(0), + margin: EdgeInsets.only(bottom: active ? 0 : 1), padding: EdgeInsets.only(left: 16, right: 16), color: isDismissed ? appStyle.colors.cardTranslucent @@ -165,7 +164,7 @@ class LessonBigWidget extends StatelessWidget { ), Text( lessonNo.toString(), - style: appStyle.fonts.B_12R.apply(color: secondary), + style: appStyle.fonts.B_14SB.apply(color: secondary), textAlign: TextAlign.center, ), ], @@ -227,8 +226,8 @@ class LessonBigWidget extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: 6), child: Text( roomName, - style: appStyle.fonts.B_12R.apply( - color: appStyle.colors.textSecondary, + style: appStyle.fonts.B_14R.apply( + color: appStyle.colors.secondary, ), overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, diff --git a/firka/lib/ui/phone/widgets/lesson_slider.dart b/firka/lib/ui/phone/widgets/lesson_slider.dart new file mode 100644 index 0000000..bf215c2 --- /dev/null +++ b/firka/lib/ui/phone/widgets/lesson_slider.dart @@ -0,0 +1,235 @@ +import 'dart:async'; +import 'dart:collection'; + +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:firka/app/app_state.dart'; +import 'package:firka/core/extensions.dart'; +import 'package:firka/ui/phone/widgets/home_main_starting_soon.dart'; +import 'package:firka/ui/phone/widgets/lesson_big.dart'; +import 'package:firka/ui/shared/firka_icon.dart'; +import 'package:firka_common/firka_common.dart'; +import 'package:firka_common/ui/components/filled_circle.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/rendering.dart'; +import 'package:kreta_api/kreta_api.dart'; +import 'package:majesticons_flutter/majesticons_flutter.dart'; + +class _LessonSliderState extends State { + DateTime now = timeNow(); + int? swipeBack; + late Timer timer; + int? activeLessonIndex; + int? centeredPageIndex; + CarouselSliderController controller = CarouselSliderController(); + + @override + void initState() { + super.initState(); + + timer = Timer.periodic(Duration(seconds: 1), (timer) async { + if (widget.lessons.isEmpty || !mounted) return; + setState(() { + if (swipeBack != null) swipeBack = swipeBack! - 1; + now = timeNow(); + }); + }); + } + + @override + void dispose() { + timer.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (widget.lessons.isEmpty) { + return SizedBox(); + } + + var lessons = widget.lessons.keys.toList(); + + var currentLesson = widget.lessons.keys.firstWhereOrNull( + (lesson) => now.isBefore(lesson.end), + ); + + int tmpIndex = lessons.isEmpty || now.isBefore(lessons.first.start) + ? 0 + : currentLesson == null + ? lessons.length + 1 + : lessons.indexOf(currentLesson) + 1; + + if (activeLessonIndex == null || tmpIndex != activeLessonIndex) { + activeLessonIndex = tmpIndex; + swipeBack = 0; + } + + centeredPageIndex ??= activeLessonIndex!; + + if (controller.ready && swipeBack == 0) { + controller.animateToPage(activeLessonIndex!); + } + + return Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + spacing: 12, + children: [ + OverflowBox( + maxWidth: MediaQuery.widthOf(context), + fit: OverflowBoxFit.deferToChild, + child: CarouselSlider( + items: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 5), + child: StartingSoonWidget(initData.l10n, now, lessons), + ), + ...widget.lessons.entries.map( + (entry) => Padding( + padding: EdgeInsets.symmetric(horizontal: 5), + child: LessonBigWidget( + initData, + lessons.getLessonNo(entry.key), + entry.key, + entry.value, + active: currentLesson == entry.key, + ), + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 5), + child: FirkaCard.single( + padding: EdgeInsets.all(16), + margin: EdgeInsets.only(bottom: 1), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + FilledCircle( + diameter: 32, + color: appStyle.colors.a15p, + child: FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.moonSolid, + size: 20, + color: appStyle.colors.accent, + ), + ), + SizedBox(width: 8), + Text( + widget.tomorrowTestAmount == 0 + ? initData.l10n.tt_no_classes_l2 + : initData.l10n.get_ready, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), + Container( + height: 28, + padding: EdgeInsets.symmetric(horizontal: 6), + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + color: appStyle.colors.background, + ), + child: Row( + children: [ + FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.editPen4Solid, + size: 12, + color: appStyle.colors.accent, + ), + SizedBox(width: 8), + Text( + widget.tomorrowTestAmount == 0 + ? initData.l10n.no_tests_tomorrow + : initData.l10n.tests_tomorrow( + widget.tomorrowTestAmount.toString(), + ), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ], + carouselController: controller, + options: CarouselOptions( + initialPage: activeLessonIndex!, + height: 106, + viewportFraction: 346 / 376, + enableInfiniteScroll: false, + onPageChanged: (index, reason) { + centeredPageIndex = index; + if (index == activeLessonIndex) { + swipeBack = null; + } else if (reason == CarouselPageChangedReason.manual) { + swipeBack = 5; + } else { + return; + } + setState(() {}); + }, + ), + ), + ), + SizedBox( + height: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 8, + children: [ + FirkaIconWidget( + FirkaIconType.majesticonsLocal, + "sunSolid", + color: centeredPageIndex == 0 + ? appStyle.colors.accent + : appStyle.colors.accent.withAlpha(128), + size: centeredPageIndex == 0 ? 16 : 12, + ), + ...widget.lessons.keys.indexed.map( + (i) => FilledCircle( + diameter: centeredPageIndex == i.$1 + 1 ? 10 : 8, + color: centeredPageIndex == i.$1 + 1 + ? appStyle.colors.accent + : appStyle.colors.accent.withAlpha(128), + child: SizedBox(), + ), + ), + FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.moonSolid, + color: centeredPageIndex == widget.lessons.keys.length + 1 + ? appStyle.colors.accent + : appStyle.colors.accent.withAlpha(128), + size: centeredPageIndex == widget.lessons.keys.length + 1 + ? 16 + : 12, + ), + ], + ), + ), + ], + ); + } +} + +class LessonSlider extends StatefulWidget { + final LinkedHashMap lessons; + final int tomorrowTestAmount; + + const LessonSlider(this.lessons, this.tomorrowTestAmount, {super.key}); + + @override + State createState() => _LessonSliderState(); +} diff --git a/firka/lib/ui/phone/widgets/tt_day.dart b/firka/lib/ui/phone/widgets/tt_day.dart index 87808ce..156a67e 100644 --- a/firka/lib/ui/phone/widgets/tt_day.dart +++ b/firka/lib/ui/phone/widgets/tt_day.dart @@ -90,9 +90,7 @@ class TimeTableDayWidget extends StatelessWidget { } if (events.isNotEmpty) { - ttLessons.add( - SizedBox() - ); + ttLessons.add(SizedBox()); } var showBreak = data.settings @@ -106,7 +104,6 @@ class TimeTableDayWidget extends StatelessWidget { ttLessons.add( LessonWidget( data, - lessons.getLessonNo(lesson), lesson, tests.firstWhereOrNull( (test) => test.lessonNumber == lesson.lessonNumber, diff --git a/firka_common/lib/ui/components/filled_circle.dart b/firka_common/lib/ui/components/filled_circle.dart index c177598..e3ae1f8 100644 --- a/firka_common/lib/ui/components/filled_circle.dart +++ b/firka_common/lib/ui/components/filled_circle.dart @@ -18,14 +18,14 @@ class FilledCircle extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( + return SizedBox( width: diameter, height: diameter, - decoration: ShapeDecoration( + child: Material( + shape: const CircleBorder(), color: color, - shape: CircleBorder(eccentricity: 1), + child: Center(child: child), ), - child: Center(child: child), ); } }