1
0
forked from firka/firka

tt: add buttons to change between weeks

This commit is contained in:
2025-08-24 11:39:48 +02:00
parent 78a9a9c05f
commit 1ce4368dc6
10 changed files with 189 additions and 75 deletions

View File

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 7L9 12L14 17" stroke="#A7DC22" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 219 B

View File

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 7L15 12L10 17" stroke="#A7DC22" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 220 B

View File

@@ -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));
}

View File

@@ -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<HomeTimetableScreen> {
List<Lesson>? lessons;
List<DateTime>? dates;
DateTime? now;
int active = 0;
bool disposed = false;
final CarouselSliderController _controller = CarouselSliderController();
@@ -37,50 +39,52 @@ class _HomeTimetableScreen extends State<HomeTimetableScreen> {
disposed = true;
}
Future<void> 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<DateTime> 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<DateTime> 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<HomeTimetableScreen> {
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<HomeTimetableScreen> {
),
],
),
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(),
],
),
);
}
}
}

View File

@@ -17,7 +17,7 @@ class BottomTimeTableNavIconWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
behavior: HitTestBehavior.translucent,
onTap: () {
onTap();
},

View File

@@ -10,10 +10,8 @@ class TimeTableDayWidget extends StatelessWidget {
final AppLocalizations l10n;
final DateTime date;
final List<Lesson> 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,
),
),
),
);

View File

@@ -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<DelayedSpinnerWidget> {
@override
Widget build(BuildContext context) {
if (showSpinner) {
return CircularProgressIndicator();
return CircularProgressIndicator(
color: appStyle.colors.accent,
);
} else {
return SizedBox();
}

View File

@@ -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:

View File

@@ -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