From f3f6e2f3ee17eee148d9d7af661281ef4018ead3 Mon Sep 17 00:00:00 2001 From: Armand <4831c0@proton.me> Date: Mon, 1 Sep 2025 17:30:27 +0200 Subject: [PATCH] home: add info board list --- .../lib/helpers/api/client/kreta_client.dart | 28 +++++++ firka/lib/helpers/api/consts.dart | 6 ++ firka/lib/helpers/api/model/notice_board.dart | 49 ++++++++++++ .../db/models/generic_cache_model.dart | 9 ++- firka/lib/ui/phone/pages/home/home_main.dart | 77 +++++++++++++------ .../lib/ui/phone/widgets/info_board_item.dart | 66 ++++++++++++++++ .../ui/phone/widgets/notice_board_item.dart | 16 ++++ firka/test/widget_test.dart | 30 -------- 8 files changed, 227 insertions(+), 54 deletions(-) create mode 100644 firka/lib/ui/phone/widgets/info_board_item.dart create mode 100644 firka/lib/ui/phone/widgets/notice_board_item.dart delete mode 100644 firka/test/widget_test.dart diff --git a/firka/lib/helpers/api/client/kreta_client.dart b/firka/lib/helpers/api/client/kreta_client.dart index 8b3389ba..f4853f53 100644 --- a/firka/lib/helpers/api/client/kreta_client.dart +++ b/firka/lib/helpers/api/client/kreta_client.dart @@ -214,6 +214,34 @@ class KretaClient { return ApiResponse(items, status, err, cached); } + ApiResponse>? infoBoardCache; + + Future>> getInfoBoard( + {bool forceCache = true}) async { + if (forceCache && infoBoardCache != null) return infoBoardCache!; + var (resp, status, ex, cached) = await _cachingGet(CacheId.getInfoBoard, + KretaEndpoints.getInfoBoard(model.iss!), forceCache, 0); + + var items = List.empty(growable: true); + String? err; + try { + List rawItems = resp; + for (var item in rawItems) { + items.add(InfoBoardItem.fromJson(item)); + } + } catch (ex) { + err = ex.toString(); + } + + if (ex != null) { + err = ex.toString(); + } + + if (err == null) infoBoardCache = ApiResponse(items, 200, null, true); + + return ApiResponse(items, status, err, cached); + } + ApiResponse>? gradeCache; Future>> getGrades({bool forceCache = true}) async { diff --git a/firka/lib/helpers/api/consts.dart b/firka/lib/helpers/api/consts.dart index 37296c91..787ac5eb 100644 --- a/firka/lib/helpers/api/consts.dart +++ b/firka/lib/helpers/api/consts.dart @@ -67,6 +67,12 @@ class KretaEndpoints { static String getNoticeBoard(String iss) => "${kreta(iss)}/ellenorzo/v3/sajat/FaliujsagElemek"; + // for some reason the [redacted] devs decided to make + // two different apis to get items for the notice board + // that appears on the home screen, like wtf + static String getInfoBoard(String iss) => + "${kreta(iss)}/ellenorzo/v3/sajat/Feljegyzesek"; + static String getGrades(String iss) => "${kreta(iss)}/ellenorzo/v3/sajat/Ertekelesek"; diff --git a/firka/lib/helpers/api/model/notice_board.dart b/firka/lib/helpers/api/model/notice_board.dart index ad709f5d..a39e0e1c 100644 --- a/firka/lib/helpers/api/model/notice_board.dart +++ b/firka/lib/helpers/api/model/notice_board.dart @@ -1,3 +1,5 @@ +import 'package:firka/helpers/api/model/generic.dart'; + class NoticeBoardItem { final String uid; final String author; @@ -40,3 +42,50 @@ class NoticeBoardItem { ')'; } } + +class InfoBoardItem { + final String uid; + final String title; + final DateTime date; + final String author; + final DateTime createdAt; + final String contentHTML; + final String contentText; + final NameUidDesc type; + + InfoBoardItem( + {required this.uid, + required this.title, + required this.date, + required this.author, + required this.createdAt, + required this.contentHTML, + required this.contentText, + required this.type}); + + factory InfoBoardItem.fromJson(Map json) { + return InfoBoardItem( + uid: json['Uid'], + title: json['Cim'], + date: DateTime.parse(json['Datum']), + author: json['KeszitoTanarNeve'], + createdAt: DateTime.parse(json['KeszitesDatuma']), + contentText: json['Tartalom'], + contentHTML: json['TartalomFormazott'], + type: NameUidDesc.fromJson(json['Tipus'])); + } + + @override + String toString() { + return 'InfoBoard(' + 'uid: "$uid", ' + 'title: "$title", ' + 'date: "$date", ' + 'author: "$author", ' + 'createdAt: "$createdAt", ' + 'contentText: "$contentText", ' + 'contentHTML: "$contentHTML", ' + 'type: $type' + ')'; + } +} diff --git a/firka/lib/helpers/db/models/generic_cache_model.dart b/firka/lib/helpers/db/models/generic_cache_model.dart index 026695dd..000755d5 100644 --- a/firka/lib/helpers/db/models/generic_cache_model.dart +++ b/firka/lib/helpers/db/models/generic_cache_model.dart @@ -2,7 +2,14 @@ import 'package:isar/isar.dart'; part 'generic_cache_model.g.dart'; -enum CacheId { getStudent, getNoticeBoard, getGrades, getOmissions, getTests } +enum CacheId { + getStudent, + getNoticeBoard, + getInfoBoard, + getGrades, + getOmissions, + getTests +} @collection class GenericCacheModel { diff --git a/firka/lib/ui/phone/pages/home/home_main.dart b/firka/lib/ui/phone/pages/home/home_main.dart index f23dd95a..0cc80cdc 100644 --- a/firka/lib/ui/phone/pages/home/home_main.dart +++ b/firka/lib/ui/phone/pages/home/home_main.dart @@ -2,10 +2,12 @@ import 'dart:async'; import 'package:firka/helpers/extensions.dart'; import 'package:firka/ui/phone/widgets/home_main_starting_soon.dart'; +import 'package:firka/ui/phone/widgets/info_board_item.dart'; import 'package:firka/ui/phone/widgets/lesson_small.dart'; import 'package:firka/ui/widget/delayed_spinner.dart'; import 'package:flutter/material.dart'; +import '../../../../helpers/api/model/notice_board.dart'; import '../../../../helpers/api/model/student.dart'; import '../../../../helpers/api/model/timetable.dart'; import '../../../../helpers/debug_helper.dart'; @@ -31,6 +33,8 @@ class _HomeMainScreen extends State { DateTime now = timeNow(); List? lessons; + List? noticeBoard; + List? infoBoard; Student? student; Timer? timer; @@ -48,24 +52,36 @@ class _HomeMainScreen extends State { if (mounted) { setState(() { lessons = newData.$1; - student = newData.$2; + noticeBoard = newData.$2; + infoBoard = newData.$3; + student = newData.$4; }); } widget.finishNotifier.update(); } - Future<(List, Student)> loadData(DateTime now, - {bool forceCache = true}) async { + Future<(List, List, List, Student)> + loadData(DateTime now, {bool forceCache = true}) async { var midnight = now.getMidnight(); var respTT = await widget.data.client.getTimeTable( midnight, midnight.add(Duration(hours: 23, minutes: 59)), forceCache: forceCache); + var respNB = + await widget.data.client.getNoticeBoard(forceCache: forceCache); + + var respIB = await widget.data.client.getInfoBoard(forceCache: forceCache); + var respStudent = await widget.data.client.getStudent(forceCache: forceCache); - return Future.value((respTT.response!, respStudent.response!)); + return Future.value(( + respTT.response!, + respNB.response!, + respIB.response!, + respStudent.response! + )); } @override @@ -83,7 +99,9 @@ class _HomeMainScreen extends State { if (mounted) { setState(() { lessons = newData.$1; - student = newData.$2; + noticeBoard = newData.$2; + infoBoard = newData.$3; + student = newData.$4; }); } })(); @@ -111,7 +129,7 @@ class _HomeMainScreen extends State { Widget nextClass = SizedBox(); bool lessonActive = false; - if (lessons != null && lessons!.isNotEmpty) { + if (lessons != null && noticeBoard != null && lessons!.isNotEmpty) { if (now.isBefore(lessons!.first.start)) { welcomeWidget = StartingSoonWidget(widget.data.l10n, now, lessons!); } else { @@ -141,24 +159,37 @@ class _HomeMainScreen extends State { } } - if (student != null && lessons != null) { + if (student != null && noticeBoard != null && lessons != null) { + List noticeBoardWidgets = List.empty(growable: true); + // TODO: Add notice board items once we actually have those + + for (var item in infoBoard!) { + noticeBoardWidgets.add(InfoBoardItemWidget(item)); + } + return Padding( - padding: const EdgeInsets.only( - left: 20.0, - top: 24.0, - right: 20.0, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - WelcomeWidget(widget.data.l10n, now, student!, lessons!), - SizedBox(height: 48), - welcomeWidget, - lessonActive ? SizedBox(height: 5) : SizedBox(height: 0), - nextClass - ], - ), - ); + padding: const EdgeInsets.only( + left: 20.0, + top: 24.0, + right: 20.0, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + WelcomeWidget(widget.data.l10n, now, student!, lessons!), + SizedBox(height: 48), + welcomeWidget, + lessonActive ? SizedBox(height: 5) : SizedBox(height: 0), + nextClass, + SizedBox( + height: MediaQuery.of(context).size.height / 1.6, + child: ListView( + children: noticeBoardWidgets, + ), + ) + ], + ), + ); } else { return DelayedSpinnerWidget(); } diff --git a/firka/lib/ui/phone/widgets/info_board_item.dart b/firka/lib/ui/phone/widgets/info_board_item.dart new file mode 100644 index 00000000..b5ebe85f --- /dev/null +++ b/firka/lib/ui/phone/widgets/info_board_item.dart @@ -0,0 +1,66 @@ +import 'package:firka/helpers/ui/firka_card.dart'; +import 'package:firka/ui/model/style.dart'; +import 'package:flutter/material.dart'; + +import '../../../helpers/api/model/notice_board.dart'; + +// TODO: Finish +class InfoBoardItemWidget extends StatelessWidget { + final InfoBoardItem item; + + const InfoBoardItemWidget(this.item, {super.key}); + + @override + Widget build(BuildContext context) { + return FirkaCard(left: [ + Row( + children: [ + Container( + decoration: ShapeDecoration( + color: appStyle.colors.accent, + shape: CircleBorder( + eccentricity: 1, + // borderRadius: BorderRadius.circular(6)), + )), + child: SizedBox( + width: 28, + height: 28, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 6), + child: Text( + item.author[0], + style: appStyle.fonts.H_18px.copyWith( + fontSize: 20, color: appStyle.colors.textPrimary), + ), + ) + ], + ), + ), + ), + SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width / 1.4, + child: Text( + item.title, + style: appStyle.fonts.B_14SB + .apply(color: appStyle.colors.textPrimary), + ), + ), + Text( + item.author, + style: appStyle.fonts.B_14R + .apply(color: appStyle.colors.textSecondary), + ) + ], + ) + ], + ) + ]); + } +} diff --git a/firka/lib/ui/phone/widgets/notice_board_item.dart b/firka/lib/ui/phone/widgets/notice_board_item.dart new file mode 100644 index 00000000..f841c3f0 --- /dev/null +++ b/firka/lib/ui/phone/widgets/notice_board_item.dart @@ -0,0 +1,16 @@ +import 'package:firka/helpers/ui/firka_card.dart'; +import 'package:flutter/material.dart'; + +import '../../../helpers/api/model/notice_board.dart'; + +// TODO: Finish +class NoticeBoardItemWidget extends StatelessWidget { + final NoticeBoardItem item; + + const NoticeBoardItemWidget(this.item, {super.key}); + + @override + Widget build(BuildContext context) { + return FirkaCard(left: [Text(item.title)]); + } +} diff --git a/firka/test/widget_test.dart b/firka/test/widget_test.dart deleted file mode 100644 index 571620db..00000000 --- a/firka/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:firka/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -}