1
0
forked from firka/firka

feat: main page lessons carousel slider

This commit is contained in:
checkedear
2026-04-25 17:05:20 +02:00
parent 7847878551
commit 09c408a6be

View File

@@ -1,7 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:firka/api/client/kreta_stream.dart'; import 'package:firka/api/client/kreta_stream.dart';
import 'package:firka/ui/phone/widgets/info_card.dart'; import 'package:firka/ui/phone/widgets/info_card.dart';
import 'package:firka/ui/phone/widgets/lesson.dart';
import 'package:firka_common/ui/components/filled_circle.dart';
import 'package:flutter/rendering.dart';
import 'package:kreta_api/kreta_api.dart'; import 'package:kreta_api/kreta_api.dart';
import 'package:firka/core/extensions.dart'; import 'package:firka/core/extensions.dart';
import 'package:firka/ui/phone/widgets/home_main_starting_soon.dart'; import 'package:firka/ui/phone/widgets/home_main_starting_soon.dart';
@@ -34,12 +38,15 @@ class _HomeMainScreen extends FirkaState<HomeMainScreen> {
_HomeMainScreen(); _HomeMainScreen();
DateTime now = timeNow(); DateTime now = timeNow();
int swipeBack = 0;
int activeLessonIndex = 0;
List<Lesson>? lessons; List<Lesson>? lessons;
List<NoticeBoardItem>? noticeBoard; List<NoticeBoardItem>? noticeBoard;
List<InfoBoardItem>? infoBoard; List<InfoBoardItem>? infoBoard;
List<Test>? tests; List<Test>? tests;
List<Grade>? grades; List<Grade>? grades;
List<Homework>? homework; List<Homework>? homework;
CarouselSliderController controller = CarouselSliderController();
Student? student; Student? student;
Timer? timer; Timer? timer;
@@ -177,6 +184,7 @@ class _HomeMainScreen extends FirkaState<HomeMainScreen> {
})(); })();
timer = Timer.periodic(Duration(seconds: 1), (timer) async { timer = Timer.periodic(Duration(seconds: 1), (timer) async {
if (swipeBack > 0) swipeBack -= 1;
if (!mounted) return; if (!mounted) return;
setState(() { setState(() {
now = timeNow(); now = timeNow();
@@ -203,98 +211,6 @@ class _HomeMainScreen extends FirkaState<HomeMainScreen> {
} }
Widget _buildContent(BuildContext context) { Widget _buildContent(BuildContext context) {
Widget welcomeWidget = SizedBox();
Widget nextClass = SizedBox();
Widget? nextTest;
bool lessonActive = false;
if (lessons != null && lessons!.isNotEmpty) {
if (now.isBefore(lessons!.first.start)) {
welcomeWidget = StartingSoonWidget(widget.data.l10n, now, lessons!);
} else {
var currentLesson = lessons!.firstWhereOrNull(
(lesson) => now.isAfter(lesson.start) && now.isBefore(lesson.end),
);
var prevLesson = lessons!.getPrevLesson(now);
var nextLesson = lessons!.getNextLesson(now);
int? lessonIndex;
if (currentLesson != null) {
lessonIndex = lessons!.getLessonNo(currentLesson);
lessonActive = true;
}
welcomeWidget = LessonBigWidget(
widget.data.l10n,
now,
lessonIndex,
currentLesson,
prevLesson,
nextLesson,
lessons!,
tests ?? [],
);
}
}
if (lessons != null && lessons!.isNotEmpty) {
var nextLesson = lessons!.getNextLesson(now);
if (nextLesson != null) {
nextClass = LessonSmallWidget(
widget.data.l10n,
nextLesson,
lessonActive,
);
if (tests != null) {
final testsOnDate = tests!
.where(
(test) =>
test.date.isAfter(
nextLesson.start.getMidnight().subtract(
Duration(seconds: 1),
),
) &&
test.date.isBefore(
nextLesson.end.getMidnight().add(
Duration(hours: 23, minutes: 59),
),
) &&
test.subject.uid == nextLesson.subject?.uid,
)
.toList();
if (testsOnDate.isNotEmpty) {
final test = testsOnDate.first;
nextTest = FirkaCard(
left: [
FirkaIconWidget(
FirkaIconType.majesticons,
Majesticon.editPen4Solid,
color: appStyle.colors.accent,
),
SizedBox(width: 6),
Text(
test.theme,
style: appStyle.fonts.B_16SB.apply(
color: appStyle.colors.textSecondary,
),
),
],
right: [
Text(
test.method.description ?? "N/A",
style: appStyle.fonts.B_16R.apply(
color: appStyle.colors.textTertiary,
),
),
],
);
}
}
}
}
if (student != null && lessons != null) { if (student != null && lessons != null) {
final infoItems = [...(infoBoard ?? []), ...(noticeBoard ?? [])]; final infoItems = [...(infoBoard ?? []), ...(noticeBoard ?? [])];
final gradeItems = grades ?? []; final gradeItems = grades ?? [];
@@ -322,6 +238,48 @@ class _HomeMainScreen extends FirkaState<HomeMainScreen> {
(item1, item2) => item2.$2.difference(item1.$2).inMilliseconds, (item1, item2) => item2.$2.difference(item1.$2).inMilliseconds,
); );
var currentLesson = lessons!.firstWhereOrNull(
(lesson) => now.isBefore(lesson.end),
);
Map<Lesson, Test?> lessonTestMap = Map.fromEntries(
lessons!.indexed.map(
(i) => MapEntry(
i.$2,
testItems.firstWhereOrNull(
(t) =>
t.date.getMidnight() == i.$2.start.getMidnight() &&
(i.$2.lessonNumber ?? i.$1) == t.lessonNumber,
),
),
),
);
int tmpIndex = lessons!.isEmpty || now.isBefore(lessons!.first.start)
? 0
: currentLesson == null
? lessons!.length + 1
: lessons!.indexOf(currentLesson) + 1;
if (tmpIndex != activeLessonIndex) {
activeLessonIndex = tmpIndex;
swipeBack = 0;
}
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))),
)
.length;
return Padding( return Padding(
padding: const EdgeInsets.only(left: 20.0, top: 24.0, right: 20.0), padding: const EdgeInsets.only(left: 20.0, top: 24.0, right: 20.0),
child: Column( child: Column(
@@ -329,13 +287,159 @@ class _HomeMainScreen extends FirkaState<HomeMainScreen> {
children: [ children: [
WelcomeWidget(widget.data.l10n, now, student!, lessons!), WelcomeWidget(widget.data.l10n, now, student!, lessons!),
SizedBox(height: 48), SizedBox(height: 48),
welcomeWidget, if (lessons!.isNotEmpty)
lessonActive ? SizedBox(height: 5) : SizedBox(height: 0), OverflowBox(
nextClass, maxWidth: MediaQuery.widthOf(context),
nextTest != null ? SizedBox(height: 12) : SizedBox(height: 0), fit: OverflowBoxFit.deferToChild,
nextTest ?? SizedBox(), child: CarouselSlider(
nextTest != null ? SizedBox(height: 12) : SizedBox(height: 0), 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: LessonWidget(
widget.data,
lessons!.getLessonNo(entry.key),
entry.key,
entry.value,
active: currentLesson == entry.key,
expanded: true,
),
),
),
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) {
if (index == activeLessonIndex) {
swipeBack = -1;
} else if (reason == CarouselPageChangedReason.manual) {
swipeBack = 5;
}
},
),
),
),
if (lessons!.isNotEmpty) SizedBox(height: 12),
if (lessons!.isNotEmpty)
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 8,
children: [
FirkaIconWidget(
FirkaIconType.majesticonsLocal,
"sunSolid",
color: activeLessonIndex == 0
? appStyle.colors.accent
: appStyle.colors.accent.withAlpha(128),
size: activeLessonIndex == 0 ? 16 : 12,
),
...lessons!.indexed.map(
(i) => FilledCircle(
diameter: activeLessonIndex == i.$1 + 1 ? 10 : 8,
color: activeLessonIndex == i.$1 + 1
? appStyle.colors.accent
: appStyle.colors.accent.withAlpha(128),
child: SizedBox(),
),
),
FirkaIconWidget(
FirkaIconType.majesticons,
Majesticon.moonSolid,
color: activeLessonIndex == lessons!.length + 1
? appStyle.colors.accent
: appStyle.colors.accent.withAlpha(128),
size: activeLessonIndex == lessons!.length + 1 ? 14 : 10,
),
],
),
if (lessons!.isNotEmpty) SizedBox(height: 12),
Expanded( Expanded(
child: RefreshIndicator(
onRefresh: () => fetchData(),
notificationPredicate: (ScrollNotification notification) {
return notification.depth == 0;
},
triggerMode: RefreshIndicatorTriggerMode.onEdge,
displacement: 0,
child: ListView( child: ListView(
children: noticeBoardWidgets children: noticeBoardWidgets
.groupList((e) => e.$2) .groupList((e) => e.$2)
@@ -359,6 +463,7 @@ class _HomeMainScreen extends FirkaState<HomeMainScreen> {
.toList(), .toList(),
), ),
), ),
),
], ],
), ),
); );