forked from firka/firka
feat: main page lessons carousel slider
This commit is contained in:
@@ -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,34 +287,181 @@ 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: [
|
||||||
Expanded(
|
Padding(
|
||||||
child: ListView(
|
padding: EdgeInsets.symmetric(horizontal: 5),
|
||||||
children: noticeBoardWidgets
|
child: StartingSoonWidget(
|
||||||
.groupList((e) => e.$2)
|
widget.data.l10n,
|
||||||
.entries
|
now,
|
||||||
.map(
|
lessons!,
|
||||||
(e) => Column(
|
|
||||||
spacing: 10,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
e.key.format(widget.data.l10n, FormatMode.main),
|
|
||||||
style: appStyle.fonts.B_16R.apply(
|
|
||||||
color: appStyle.colors.textSecondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
...e.value.map((v) => v.$1),
|
|
||||||
SizedBox(height: 10),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
.toList(),
|
...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(
|
||||||
|
child: RefreshIndicator(
|
||||||
|
onRefresh: () => fetchData(),
|
||||||
|
notificationPredicate: (ScrollNotification notification) {
|
||||||
|
return notification.depth == 0;
|
||||||
|
},
|
||||||
|
triggerMode: RefreshIndicatorTriggerMode.onEdge,
|
||||||
|
displacement: 0,
|
||||||
|
child: ListView(
|
||||||
|
children: noticeBoardWidgets
|
||||||
|
.groupList((e) => e.$2)
|
||||||
|
.entries
|
||||||
|
.map(
|
||||||
|
(e) => Column(
|
||||||
|
spacing: 10,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
e.key.format(widget.data.l10n, FormatMode.main),
|
||||||
|
style: appStyle.fonts.B_16R.apply(
|
||||||
|
color: appStyle.colors.textSecondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
...e.value.map((v) => v.$1),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user