From 1ce4368dc68608452f154e3d5f538a2ad4e8f695 Mon Sep 17 00:00:00 2001 From: Armand <4831c0@proton.me> Date: Sun, 24 Aug 2025 11:39:48 +0200 Subject: [PATCH] tt: add buttons to change between weeks --- firka/assets/icons/dropdownLeft.svg | 3 + firka/assets/icons/dropdownRight.svg | 3 + firka/lib/helpers/extensions.dart | 16 +- firka/lib/l10n | 2 +- .../ui/phone/pages/home/home_timetable.dart | 204 ++++++++++++------ .../lib/ui/phone/widgets/bottom_tt_icon.dart | 2 +- firka/lib/ui/phone/widgets/tt_day.dart | 19 +- firka/lib/ui/widget/delayed_spinner.dart | 6 +- firka/lib/ui/widget/firka_icon.dart | 7 + firka/pubspec.yaml | 2 + 10 files changed, 189 insertions(+), 75 deletions(-) create mode 100644 firka/assets/icons/dropdownLeft.svg create mode 100644 firka/assets/icons/dropdownRight.svg diff --git a/firka/assets/icons/dropdownLeft.svg b/firka/assets/icons/dropdownLeft.svg new file mode 100644 index 0000000..09ee5c9 --- /dev/null +++ b/firka/assets/icons/dropdownLeft.svg @@ -0,0 +1,3 @@ + + + diff --git a/firka/assets/icons/dropdownRight.svg b/firka/assets/icons/dropdownRight.svg new file mode 100644 index 0000000..e81b856 --- /dev/null +++ b/firka/assets/icons/dropdownRight.svg @@ -0,0 +1,3 @@ + + + diff --git a/firka/lib/helpers/extensions.dart b/firka/lib/helpers/extensions.dart index e1490ee..435cd02 100644 --- a/firka/lib/helpers/extensions.dart +++ b/firka/lib/helpers/extensions.dart @@ -33,7 +33,7 @@ extension DurationExtension on Duration { } } -enum FormatMode { yearly, grades, welcome, hmm, da, dd } +enum FormatMode { yearly, grades, welcome, hmm, da, dd, yyyymmddwedd } enum Cycle { morning, day, afternoon, night } @@ -51,6 +51,9 @@ extension DateExtension on DateTime { var yesterday = today.subtract(Duration(days: 1)); var yesterdayLim = today.subtract(Duration(days: 2)); + var weekStart = today.subtract(Duration(days: today.weekday - 1)); + var weekEnd = weekStart.add(Duration(days: 6)); + switch (mode) { case FormatMode.grades: if (isBefore(yesterdayLim)) { @@ -77,9 +80,20 @@ extension DateExtension on DateTime { return DateFormat('MMMMEEEEd').format(this).substring(0, 2); case FormatMode.dd: return DateFormat('dd').format(this); + case FormatMode.yyyymmddwedd: + return "${DateFormat('yyyy MMM. dd').format(weekStart).toLowerCase()}-${DateFormat('dd').format(weekEnd)}"; } } + int weekNumber() { + int dayOfYear = int.parse(DateFormat("D").format(this)); + return ((dayOfYear - weekday + 10) / 7).floor(); + } + + bool isAWeek() { + return weekNumber() % 2 == 0; + } + DateTime getMonday() { return subtract(Duration(days: weekday - 1)); } diff --git a/firka/lib/l10n b/firka/lib/l10n index 24104a8..f69c2d2 160000 --- a/firka/lib/l10n +++ b/firka/lib/l10n @@ -1 +1 @@ -Subproject commit 24104a853f3b62635412c364257b516198772490 +Subproject commit f69c2d2fe2b161a4e94706ee9a577ece677f3d84 diff --git a/firka/lib/ui/phone/pages/home/home_timetable.dart b/firka/lib/ui/phone/pages/home/home_timetable.dart index 43d9c04..8370b86 100644 --- a/firka/lib/ui/phone/pages/home/home_timetable.dart +++ b/firka/lib/ui/phone/pages/home/home_timetable.dart @@ -6,6 +6,7 @@ import 'package:firka/ui/model/style.dart'; import 'package:firka/ui/widget/delayed_spinner.dart'; import 'package:flutter/material.dart'; import 'package:majesticons_flutter/majesticons_flutter.dart'; +import 'package:transparent_pointer/transparent_pointer.dart'; import '../../../../main.dart'; import '../../../widget/firka_icon.dart'; @@ -24,6 +25,7 @@ class HomeTimetableScreen extends StatefulWidget { class _HomeTimetableScreen extends State { List? lessons; List? dates; + DateTime? now; int active = 0; bool disposed = false; final CarouselSliderController _controller = CarouselSliderController(); @@ -37,50 +39,52 @@ class _HomeTimetableScreen extends State { disposed = true; } + Future initForWeek(DateTime now) async { + var monday = now.getMonday().getMidnight(); + var sunday = monday.add(Duration(days: 6)); + + var lessonsResp = await widget.data.client.getTimeTable(monday, sunday); + List dates = List.empty(growable: true); + + if (lessonsResp.response != null) { + lessons = lessonsResp.response; + + for (var i = 0; i < 7; i++) { + var t = monday.add(Duration(days: i)); + + var hasLessons = i < 5 || + lessons!.firstWhereOrNull((lesson) { + return lesson.start.getMidnight().millisecondsSinceEpoch == + t.getMidnight().millisecondsSinceEpoch; + }) != + null; + + if (hasLessons) { + dates.add(t); + } + } + } + + if (disposed) return; + setState(() { + this.dates = dates; + if (now.isAfter(dates.last)) { + active = dates.length - 1; + } else { + active = dates.indexWhere((d) => + d.isAfter(now.getMidnight()) && + d.isBefore( + now.getMidnight().add(Duration(hours: 23, minutes: 59)))); + } + }); + } + @override void initState() { super.initState(); - var monday = timeNow().getMonday().getMidnight(); - var sunday = monday.add(Duration(days: 6)); - - (() async { - var lessonsResp = await widget.data.client.getTimeTable(monday, sunday); - List dates = List.empty(growable: true); - - if (lessonsResp.response != null) { - lessons = lessonsResp.response; - - for (var i = 0; i < 7; i++) { - var t = monday.add(Duration(days: i)); - - var hasLessons = i < 5 || - lessons!.firstWhereOrNull((lesson) { - return lesson.start.getMidnight().millisecondsSinceEpoch == - t.getMidnight().millisecondsSinceEpoch; - }) != - null; - - if (hasLessons) { - dates.add(t); - } - } - } - - if (disposed) return; - setState(() { - this.dates = dates; - if (timeNow().isAfter(dates.last)) { - active = dates.length - 1; - } else { - active = dates.indexWhere((d) => - d.isAfter(timeNow().getMidnight()) && - d.isBefore(timeNow() - .getMidnight() - .add(Duration(hours: 23, minutes: 59)))); - } - }); - })(); + now = timeNow(); + initForWeek(now!); } @override @@ -105,14 +109,13 @@ class _HomeTimetableScreen extends State { lesson.end.isBefore(date.add(Duration(hours: 24)))) .toList(); - ttDays.add(TimeTableDayWidget( - widget.data.l10n, date, lessonsOnDate, active == i)); + ttDays.add(TimeTableDayWidget(widget.data.l10n, date, lessonsOnDate)); } return Stack(children: [ SizedBox( width: MediaQuery.of(context).size.width, - height: 50, + height: 74 + 16, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Column( @@ -167,36 +170,113 @@ class _HomeTimetableScreen extends State { ), ], ), + 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 = now!.subtract(Duration(days: 7)); + setState(() { + now = newNow; + lessons = null; + dates = null; + }); + await initForWeek(newNow); + setState(() { + now = newNow; + }); + }, + ), + Row( + children: [ + Text( + dates!.first.format( + widget.data.l10n, FormatMode.yyyymmddwedd), + style: appStyle.fonts.B_14R), + SizedBox(width: 4), + Text("•", + style: appStyle.fonts.B_16R + .apply(color: appStyle.colors.accent)), + SizedBox(width: 4), + Text( + dates!.first.isAWeek() + ? widget.data.l10n.a_week + : widget.data.l10n.b_week, + style: appStyle.fonts.B_14R), + ], + ), + GestureDetector( + child: FirkaIconWidget( + FirkaIconType.icons, + "dropdownRight", + size: 24, + color: appStyle.colors.accent, + ), + onTap: () async { + var newNow = now!.add(Duration(days: 7)); + await initForWeek(newNow); + setState(() { + now = newNow; + }); + }, + ), + ], + ) ], ), ), ), Column( children: [ - SizedBox( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height / 1.4, - child: CarouselSlider( - items: ttDays, - carouselController: _controller, - options: CarouselOptions( - height: MediaQuery.of(context).size.height / 1.36, - enableInfiniteScroll: false, - initialPage: active, - onPageChanged: (i, _) { - setState(() { - active = i; - }); - }), - )), - Row( + TransparentPointer( + child: SizedBox( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height / 1.4, + child: CarouselSlider( + items: ttDays, + carouselController: _controller, + options: CarouselOptions( + height: MediaQuery.of(context).size.height / 1.36, + viewportFraction: 1, + enableInfiniteScroll: false, + initialPage: active, + onPageChanged: (i, _) { + setState(() { + active = i; + }); + }), + ))), + TransparentPointer( + child: Row( children: ttWidgets, - ), + )), ], ) ]); } else { - return DelayedSpinnerWidget(); + return SizedBox( + height: MediaQuery.of(context).size.height / 1.35, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(), + DelayedSpinnerWidget(), + SizedBox(), + ], + ), + ); } } } diff --git a/firka/lib/ui/phone/widgets/bottom_tt_icon.dart b/firka/lib/ui/phone/widgets/bottom_tt_icon.dart index 016e59f..d650913 100644 --- a/firka/lib/ui/phone/widgets/bottom_tt_icon.dart +++ b/firka/lib/ui/phone/widgets/bottom_tt_icon.dart @@ -17,7 +17,7 @@ class BottomTimeTableNavIconWidget extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( - behavior: HitTestBehavior.opaque, + behavior: HitTestBehavior.translucent, onTap: () { onTap(); }, diff --git a/firka/lib/ui/phone/widgets/tt_day.dart b/firka/lib/ui/phone/widgets/tt_day.dart index 6cca130..ca259c1 100644 --- a/firka/lib/ui/phone/widgets/tt_day.dart +++ b/firka/lib/ui/phone/widgets/tt_day.dart @@ -10,10 +10,8 @@ class TimeTableDayWidget extends StatelessWidget { final AppLocalizations l10n; final DateTime date; final List lessons; - final bool active; - const TimeTableDayWidget(this.l10n, this.date, this.lessons, this.active, - {super.key}); + const TimeTableDayWidget(this.l10n, this.date, this.lessons, {super.key}); @override Widget build(BuildContext context) { @@ -41,15 +39,18 @@ class TimeTableDayWidget extends StatelessWidget { } return SizedBox( - width: MediaQuery.of(context).size.width / (active ? 1 : 1.6), + width: MediaQuery.of(context).size.width / 1.1, child: lessons.isEmpty ? noLessonsWidget : Padding( - padding: const EdgeInsets.only(top: 50 + 20), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: ttBody, + padding: + const EdgeInsets.only(top: 70 + 16 + 20, left: 4, right: 4), + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: ttBody, + ), ), ), ); diff --git a/firka/lib/ui/widget/delayed_spinner.dart b/firka/lib/ui/widget/delayed_spinner.dart index 53e4b65..aac5a57 100644 --- a/firka/lib/ui/widget/delayed_spinner.dart +++ b/firka/lib/ui/widget/delayed_spinner.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import '../model/style.dart'; + class DelayedSpinnerWidget extends StatefulWidget { const DelayedSpinnerWidget({super.key}); @@ -27,7 +29,9 @@ class _DelayedSpinner extends State { @override Widget build(BuildContext context) { if (showSpinner) { - return CircularProgressIndicator(); + return CircularProgressIndicator( + color: appStyle.colors.accent, + ); } else { return SizedBox(); } diff --git a/firka/lib/ui/widget/firka_icon.dart b/firka/lib/ui/widget/firka_icon.dart index 2034e74..2bab759 100644 --- a/firka/lib/ui/widget/firka_icon.dart +++ b/firka/lib/ui/widget/firka_icon.dart @@ -5,6 +5,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:majesticons_flutter/majesticons_flutter.dart'; enum FirkaIconType { + icons, majesticons, majesticonsLocal, } @@ -21,6 +22,12 @@ class FirkaIconWidget extends StatelessWidget { @override Widget build(BuildContext context) { switch (iconType) { + case FirkaIconType.icons: + return SvgPicture.asset( + 'assets/icons/${iconData as String}.svg', + color: color, + height: size, + ); case FirkaIconType.majesticons: return Majesticon(iconData as Uint8List, color: color, size: size); case FirkaIconType.majesticonsLocal: diff --git a/firka/pubspec.yaml b/firka/pubspec.yaml index adcdd68..b02223f 100644 --- a/firka/pubspec.yaml +++ b/firka/pubspec.yaml @@ -61,6 +61,7 @@ dependencies: home_widget: ^0.8.0 brotli: ^0.6.0 crypto: ^3.0.6 + transparent_pointer: ^1.0.1 dev_dependencies: flutter_test: @@ -89,6 +90,7 @@ flutter: - assets/images/carousel/ - assets/images/icons/ - assets/images/background.png + - assets/icons/ - assets/majesticons/ - assets/firka.i