Files
firka/firka/lib/ui/phone/pages/home/home_timetable_mo.dart
2025-08-24 15:48:57 +02:00

490 lines
18 KiB
Dart

import 'package:firka/helpers/api/consts.dart';
import 'package:firka/helpers/api/consts.dart';
import 'package:firka/helpers/api/consts.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/foundation.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<HomeTimetableMonthlyScreen> createState() =>
_HomeTimetableMonthlyScreen();
}
enum ActiveFilter { lessonNo, homework, omissions }
class _HomeTimetableMonthlyScreen extends State<HomeTimetableMonthlyScreen> {
List<Lesson>? lessons;
List<DateTime>? dates;
DateTime? now;
int active = 0;
bool disposed = false;
ActiveFilter activeFilter = ActiveFilter.lessonNo;
_HomeTimetableMonthlyScreen();
@override
void dispose() {
super.dispose();
disposed = true;
}
Future<void> 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<DateTime> 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<Widget> 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))));
var omissionType = lessonsToday.firstWhereOrNull((lesson) =>
lesson.studentPresence != null &&
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)),
);
}
break;
case ActiveFilter.homework:
if (lessonsToday.firstWhereOrNull(
(lesson) => lesson.homeworkUid != null) !=
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.na:
case OmissionConsts.absence:
body = Center(
child: FirkaIconWidget(
FirkaIconType.majesticons,
Majesticon.multiplySolid,
size: 20.0,
color: appStyle.colors.accent,
),
);
break;
default:
debugPrint(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: activeFilter == ActiveFilter.lessonNo &&
omissionType != null &&
(omissionType.studentPresence!.name ==
OmissionConsts.absence ||
omissionType.studentPresence!.name ==
OmissionConsts.na)
? appStyle.colors.error15p
: timeNow().day == d.day && timeNow().month == d.month
? appStyle.colors.buttonSecondaryFill
: appStyle.colors.card,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6)),
),
child: body,
),
SizedBox(height: 4),
Text(d.format(widget.data.l10n, FormatMode.d),
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,
),
)),
)),
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) =>
lesson.start.isAfter(currentMonthStart) &&
lesson.end.isBefore(currentMonthEnd) &&
lesson.homeworkUid != null)
.length,
activeFilter == ActiveFilter.homework, () {
setState(() {
activeFilter = ActiveFilter.homework;
});
}),
_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.present)
.length,
activeFilter == ActiveFilter.omissions, () {
setState(() {
activeFilter = ActiveFilter.omissions;
});
}),
],
),
SizedBox()
],
),
)
],
),
),
]);
} else {
return SizedBox(
height: MediaQuery.of(context).size.height / 1.35,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(),
DelayedSpinnerWidget(),
SizedBox(),
],
),
);
}
}
}
class _StatusToast extends StatelessWidget {
final FirkaIconWidget _icon;
final int _count;
final bool _active;
final void Function() _onTap;
const _StatusToast(this._icon, this._count, this._active, this._onTap);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _onTap,
child: Container(
clipBehavior: Clip.antiAlias,
decoration: ShapeDecoration(
color: _active
? appStyle.colors.buttonSecondaryFill
: appStyle.colors.cardTranslucent,
shape:
RoundedRectangleBorder(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))
],
),
),
),
);
}
}