diff --git a/firka/lib/helpers/extensions.dart b/firka/lib/helpers/extensions.dart index 79bfe3d5..790f23ff 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, yyyymmddwedd } +enum FormatMode { yearly, grades, welcome, hmm, da, dd, yyyymmddwedd, yyyymmmm } enum Cycle { morning, day, afternoon, night } @@ -81,6 +81,8 @@ extension DateExtension on DateTime { return DateFormat('dd').format(this); case FormatMode.yyyymmddwedd: return "${DateFormat('yyyy MMM. dd').format(weekStart).toLowerCase()}-${DateFormat('dd').format(weekEnd)}"; + case FormatMode.yyyymmmm: + return DateFormat('yyyy MMMM').format(weekStart); } } diff --git a/firka/lib/ui/phone/pages/home/home_grades.dart b/firka/lib/ui/phone/pages/home/home_grades.dart index 282115d7..7d77ffc3 100644 --- a/firka/lib/ui/phone/pages/home/home_grades.dart +++ b/firka/lib/ui/phone/pages/home/home_grades.dart @@ -13,7 +13,7 @@ import '../../../model/style.dart'; class HomeGradesScreen extends StatelessAsyncWidget { final AppInitialization data; - final void Function(ActiveHomePage) cb; + final void Function(ActiveHomePage, bool) cb; const HomeGradesScreen(this.data, this.cb, {super.key}); @@ -57,7 +57,7 @@ class HomeGradesScreen extends StatelessAsyncWidget { gradeCards.add(GestureDetector( child: GradeSmallCard(grades.response!, subject), onTap: () { - cb(ActiveHomePage(HomePages.grades, subPageUid: subject.uid)); + cb(ActiveHomePage(HomePages.grades, subPageUid: subject.uid), true); }, )); } diff --git a/firka/lib/ui/phone/pages/home/home_timetable.dart b/firka/lib/ui/phone/pages/home/home_timetable.dart index c8627a17..d3910f2b 100644 --- a/firka/lib/ui/phone/pages/home/home_timetable.dart +++ b/firka/lib/ui/phone/pages/home/home_timetable.dart @@ -10,13 +10,15 @@ import 'package:transparent_pointer/transparent_pointer.dart'; import '../../../../main.dart'; import '../../../widget/firka_icon.dart'; +import '../../screens/home/home_screen.dart'; import '../../widgets/bottom_tt_icon.dart'; import '../../widgets/tt_day.dart'; class HomeTimetableScreen extends StatefulWidget { final AppInitialization data; + final void Function(ActiveHomePage, bool) cb; - const HomeTimetableScreen(this.data, {super.key}); + const HomeTimetableScreen(this.data, this.cb, {super.key}); @override State createState() => _HomeTimetableScreen(); @@ -130,17 +132,23 @@ class _HomeTimetableScreen extends State { ), Row( children: [ - 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, + 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: () { + widget.cb( + ActiveHomePage(HomePages.timetableMo), false); + }, ), Card( color: appStyle.colors.buttonSecondaryFill, diff --git a/firka/lib/ui/phone/pages/home/home_timetable_mo.dart b/firka/lib/ui/phone/pages/home/home_timetable_mo.dart new file mode 100644 index 00000000..ec7b5e21 --- /dev/null +++ b/firka/lib/ui/phone/pages/home/home_timetable_mo.dart @@ -0,0 +1,308 @@ +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:firka/helpers/api/model/timetable.dart'; +import 'package:firka/helpers/debug_helper.dart'; +import 'package:firka/helpers/extensions.dart'; +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 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; + +import '../../../../main.dart'; +import '../../../widget/firka_icon.dart'; +import '../../screens/home/home_screen.dart'; + +class HomeTimetableMonthlyScreen extends StatefulWidget { + final AppInitialization data; + final void Function(ActiveHomePage, bool) cb; + + const HomeTimetableMonthlyScreen(this.data, this.cb, {super.key}); + + @override + State createState() => + _HomeTimetableMonthlyScreen(); +} + +class _HomeTimetableMonthlyScreen extends State { + List? lessons; + List? dates; + DateTime? now; + int active = 0; + bool disposed = false; + + _HomeTimetableMonthlyScreen(); + + @override + void dispose() { + super.dispose(); + + disposed = true; + } + + Future initForMonth(DateTime now) async { + final monthStart = DateTime.utc(now.year, now.month, 1); + final monthEnd = + DateTime.utc(now.year, now.month + 1).subtract(Duration(days: 1)); + + final start = monthStart.subtract(Duration(days: 7)).getMonday(); + var end = + monthEnd.add(Duration(days: 7)).getMonday().add(Duration(days: 7)); + + var days = end.difference(start).inDays; + + var lessonsResp = + await widget.data.client.getTimeTable(monthStart, monthEnd); + List dates = List.empty(growable: true); + + for (var i = 0; i < days; i++) { + dates.add(start.add(Duration(days: i))); + } + + if (lessonsResp.response != null) { + lessons = lessonsResp.response; + } + + if (disposed) return; + setState(() { + this.dates = dates; + }); + } + + @override + void initState() { + super.initState(); + + now = timeNow(); + initForMonth(now!); + } + + @override + Widget build(BuildContext context) { + if (lessons != 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)); + + for (var d in dates!) { + 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(6)), + )), + SizedBox(height: 4), + Text(d.format(widget.data.l10n, FormatMode.dd), + style: appStyle.fonts.B_14R.apply( + color: (d.weekday == DateTime.saturday || + d.weekday == DateTime.sunday) + ? appStyle.colors.errorText + : appStyle.colors.textTertiary)), + SizedBox(height: 12), + ], + )); + } else { + Widget body = SizedBox(); + + var lessonsToday = lessons!.where((lesson) => + lesson.start.isAfter(d.getMidnight()) && + lesson.end.isBefore( + d.getMidnight().add(Duration(hours: 23, minutes: 59)))); + + if (lessonsToday.isNotEmpty) { + body = Center( + child: Text(lessonsToday.length.toString(), + style: appStyle.fonts.H_16px + .apply(color: appStyle.colors.secondary)), + ); + } + ttDays.add(Column( + children: [ + Container( + width: 40, + height: 40, + clipBehavior: Clip.antiAlias, + decoration: ShapeDecoration( + color: appStyle.colors.card, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6)), + ), + child: body, + ), + SizedBox(height: 4), + Text(d.format(widget.data.l10n, FormatMode.dd), + style: appStyle.fonts.B_14R.apply( + color: (d.weekday == DateTime.saturday || + d.weekday == DateTime.sunday) && + lessonsToday.isEmpty + ? appStyle.colors.errorText + : appStyle.colors.textSecondary)), + SizedBox(height: 12), + ], + )); + } + } + + return 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: () { + widget.cb( + ActiveHomePage(HomePages.timetable), false); + }, + ), + 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, + ), + ), + ), + 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, + ), + ), + ) + ], + ), + ], + ), + 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: 30)); + 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_14R), + GestureDetector( + child: FirkaIconWidget( + FirkaIconType.icons, + "dropdownRight", + size: 24, + color: appStyle.colors.accent, + ), + onTap: () async { + var newNow = now!.add(Duration(days: 30)); + 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: SizedBox( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height / 1.45, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: StaggeredGrid.count( + crossAxisCount: 7, + children: ttDays, + ), + )), + )), + ]); + } else { + 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/screens/home/home_screen.dart b/firka/lib/ui/phone/screens/home/home_screen.dart index e61bdd99..8321a2df 100644 --- a/firka/lib/ui/phone/screens/home/home_screen.dart +++ b/firka/lib/ui/phone/screens/home/home_screen.dart @@ -6,6 +6,7 @@ import 'package:firka/main.dart'; import 'package:firka/ui/model/style.dart'; import 'package:firka/ui/phone/pages/home/home_grades.dart'; import 'package:firka/ui/phone/pages/home/home_main.dart'; +import 'package:firka/ui/phone/pages/home/home_timetable_mo.dart'; import 'package:firka/ui/phone/widgets/bottom_nav_icon.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -30,7 +31,7 @@ class HomeScreen extends StatefulWidget { State createState() => _HomeScreenState(); } -enum HomePages { home, grades, timetable } +enum HomePages { home, grades, timetable, timetableMo } enum ActiveToastType { fetching, error, none } @@ -63,9 +64,9 @@ class _HomeScreenState extends State { ActiveToastType activeToast = ActiveToastType.none; - void setPageCB(ActiveHomePage newPage) { + void setPageCB(ActiveHomePage newPage, bool setPrev) { setState(() { - previousPages.add(page); + if (setPrev) previousPages.add(page); canPop = false; page = newPage; }); @@ -126,9 +127,8 @@ class _HomeScreenState extends State { color: appStyle.colors.errorCard, shadowColor: Colors.transparent, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(200)), - ), + borderRadius: BorderRadius.all(Radius.circular(200)), + ), child: Padding( padding: EdgeInsets.symmetric(horizontal: 15, vertical: 8), child: Row( @@ -370,7 +370,7 @@ class _HomeScreenState extends State { class HomeSubPage extends StatelessWidget { final ActiveHomePage page; - final void Function(ActiveHomePage) cb; + final void Function(ActiveHomePage, bool) cb; final AppInitialization data; const HomeSubPage(this.page, this.cb, this.data, {super.key}); @@ -387,7 +387,9 @@ class HomeSubPage extends StatelessWidget { return HomeGradesScreen(data, cb); } case HomePages.timetable: - return HomeTimetableScreen(data); + return HomeTimetableScreen(data, cb); + case HomePages.timetableMo: + return HomeTimetableMonthlyScreen(data, cb); } } } diff --git a/firka/pubspec.yaml b/firka/pubspec.yaml index b02223f3..cb13e0d1 100644 --- a/firka/pubspec.yaml +++ b/firka/pubspec.yaml @@ -62,6 +62,7 @@ dependencies: brotli: ^0.6.0 crypto: ^3.0.6 transparent_pointer: ^1.0.1 + flutter_staggered_grid_view: ^0.7.0 dev_dependencies: flutter_test: