remove stateless_async_widget.dart

This commit is contained in:
2025-08-29 11:31:45 +02:00
parent c042206004
commit 69ca2a27fe
4 changed files with 456 additions and 385 deletions

View File

@@ -1,23 +0,0 @@
import 'package:firka/ui/widget/delayed_spinner.dart';
import 'package:flutter/material.dart';
abstract class StatelessAsyncWidget extends StatelessWidget {
const StatelessAsyncWidget({super.key});
Future<Widget> buildAsync(BuildContext context);
@override
Widget build(BuildContext context) {
return FutureBuilder<Widget>(
future: buildAsync(context),
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: DelayedSpinnerWidget());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else {
return snapshot.data!;
}
});
}
}

View File

@@ -1,167 +1,204 @@
import 'package:firka/helpers/api/client/kreta_client.dart';
import 'package:firka/helpers/api/model/generic.dart';
import 'package:firka/helpers/ui/firka_card.dart';
import 'package:firka/helpers/ui/grade_helpers.dart';
import 'package:firka/helpers/ui/stateless_async_widget.dart';
import 'package:firka/ui/phone/screens/home/home_screen.dart';
import 'package:firka/ui/widget/grade_small_card.dart';
import 'package:flutter/material.dart';
import '../../../../helpers/api/model/grade.dart';
import '../../../../helpers/api/model/subject.dart';
import '../../../../helpers/api/model/timetable.dart';
import '../../../../helpers/debug_helper.dart';
import '../../../../main.dart';
import '../../../model/style.dart';
import '../../../widget/delayed_spinner.dart';
class HomeGradesScreen extends StatelessAsyncWidget {
class HomeGradesScreen extends StatefulWidget {
final AppInitialization data;
final void Function(ActiveHomePage, bool) cb;
const HomeGradesScreen(this.data, this.cb, {super.key});
@override
Future<Widget> buildAsync(BuildContext context) async {
var now = timeNow();
var start = now.subtract(Duration(days: now.weekday - 1));
var end = start.add(Duration(days: 6));
State<StatefulWidget> createState() => _HomeGradesScreen();
}
var grades = await data.client.getGrades();
var subjectAvg = 0.00;
var week = await data.client.getTimeTable(start, end);
final List<Subject> subjects = List<Subject>.empty(growable: true);
final List<Widget> gradeCards = [];
class _HomeGradesScreen extends State<HomeGradesScreen> {
ApiResponse<List<Grade>>? grades;
ApiResponse<List<Lesson>>? week;
for (var grade in grades.response!) {
if (subjects.where((s) => s.uid == grade.subject.uid).isEmpty) {
subjects.add(grade.subject);
}
}
@override
void initState() {
super.initState();
subjects.sort((s1, s2) => s1.name.compareTo(s2.name));
(() async {
var now = timeNow();
var start = now.subtract(Duration(days: now.weekday - 1));
var end = start.add(Duration(days: 6));
for (var subject in subjects) {
for (var grade in grades.response!) {
if (grade.subject.uid != subject.uid) continue;
grades = await widget.data.client.getGrades();
week = await widget.data.client.getTimeTable(start, end);
if (grade.valueType.name == "Szazalekos") {
grade.valueType = NameUidDesc(
uid: "1,Osztalyzat", name: "Osztalyzat", description: "");
if (grade.numericValue != null) {
grade.numericValue = percentageToGrade(grade.numericValue!);
}
}
}
var avg = grades.response!.getAverageBySubject(subject);
if (mounted) setState(() {});
})();
}
if (avg.isNaN) {
gradeCards.add(GradeSmallCard(grades.response!, subject));
} else {
gradeCards.add(GestureDetector(
child: GradeSmallCard(grades.response!, subject),
onTap: () {
cb(ActiveHomePage(HomePages.grades, subPageUid: subject.uid), true);
},
));
}
subjectAvg += roundGrade(avg);
}
subjectAvg /= subjects.length;
var subjectAvgColor = getGradeColor(subjectAvg);
return Flexible(
child: Padding(
padding: const EdgeInsets.only(
left: 16.0,
right: 16.0,
),
@override
Widget build(BuildContext context) {
if (grades == null || week == null) {
return SizedBox(
height: MediaQuery.of(context).size.height / 1.35,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Text(
data.l10n.subjects,
style: appStyle.fonts.H_H2
.apply(color: appStyle.colors.textSecondary),
)
],
),
SizedBox(height: 16), // TODO: Add graphs here
// ...gradeCards,
SizedBox(
height: MediaQuery.of(context).size.height -
MediaQuery.of(context).padding.top -
230,
child: ListView(
children: [
Text(
data.l10n.your_subjects,
style: appStyle.fonts.H_14px
.apply(color: appStyle.colors.textSecondary),
),
SizedBox(height: 16),
...gradeCards,
SizedBox(height: 16),
Text(
data.l10n.data,
style: appStyle.fonts.B_16SB
.apply(color: appStyle.colors.textSecondary),
),
SizedBox(height: 16),
FirkaCard(
left: [
Text(
data.l10n.subject_avg,
style: appStyle.fonts.B_16SB
.apply(color: appStyle.colors.textPrimary),
),
],
right: [
Card(
shadowColor: Colors.transparent,
color: subjectAvgColor.withAlpha(38),
child: Padding(
padding: EdgeInsets.only(
left: 8, right: 8, top: 4, bottom: 4),
child: Text(
subjectAvg.toStringAsFixed(2),
style: appStyle.fonts.B_16SB
.apply(color: subjectAvgColor),
),
),
),
],
),
FirkaCard(left: [
Text(
data.l10n.class_avg,
style: appStyle.fonts.B_16SB
.apply(color: appStyle.colors.textPrimary),
),
]),
FirkaCard(
left: [
Text(
data.l10n.class_n,
style: appStyle.fonts.B_16SB
.apply(color: appStyle.colors.textPrimary),
),
],
right: [
Text(
week.response!.length.toString(),
style: appStyle.fonts.B_14SB
.apply(color: appStyle.colors.textPrimary),
),
],
),
],
),
),
SizedBox(),
DelayedSpinnerWidget(),
SizedBox(),
],
),
),
);
);
} else {
var subjectAvg = 0.00;
final List<Subject> subjects = List<Subject>.empty(growable: true);
final List<Widget> gradeCards = [];
for (var grade in grades!.response!) {
if (subjects.where((s) => s.uid == grade.subject.uid).isEmpty) {
subjects.add(grade.subject);
}
}
subjects.sort((s1, s2) => s1.name.compareTo(s2.name));
for (var subject in subjects) {
for (var grade in grades!.response!) {
if (grade.subject.uid != subject.uid) continue;
if (grade.valueType.name == "Szazalekos") {
grade.valueType = NameUidDesc(
uid: "1,Osztalyzat", name: "Osztalyzat", description: "");
if (grade.numericValue != null) {
grade.numericValue = percentageToGrade(grade.numericValue!);
}
}
}
var avg = grades!.response!.getAverageBySubject(subject);
if (avg.isNaN) {
gradeCards.add(GradeSmallCard(grades!.response!, subject));
} else {
gradeCards.add(GestureDetector(
child: GradeSmallCard(grades!.response!, subject),
onTap: () {
widget.cb(
ActiveHomePage(HomePages.grades, subPageUid: subject.uid),
true);
},
));
}
subjectAvg += roundGrade(avg);
}
subjectAvg /= subjects.length;
var subjectAvgColor = getGradeColor(subjectAvg);
return Flexible(
child: Padding(
padding: const EdgeInsets.only(
left: 16.0,
right: 16.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
widget.data.l10n.subjects,
style: appStyle.fonts.H_H2
.apply(color: appStyle.colors.textSecondary),
)
],
),
SizedBox(height: 16), // TODO: Add graphs here
// ...gradeCards,
SizedBox(
height: MediaQuery.of(context).size.height -
MediaQuery.of(context).padding.top -
230,
child: ListView(
children: [
Text(
widget.data.l10n.your_subjects,
style: appStyle.fonts.H_14px
.apply(color: appStyle.colors.textSecondary),
),
SizedBox(height: 16),
...gradeCards,
SizedBox(height: 16),
Text(
widget.data.l10n.data,
style: appStyle.fonts.B_16SB
.apply(color: appStyle.colors.textSecondary),
),
SizedBox(height: 16),
FirkaCard(
left: [
Text(
widget.data.l10n.subject_avg,
style: appStyle.fonts.B_16SB
.apply(color: appStyle.colors.textPrimary),
),
],
right: [
Card(
shadowColor: Colors.transparent,
color: subjectAvgColor.withAlpha(38),
child: Padding(
padding: EdgeInsets.only(
left: 8, right: 8, top: 4, bottom: 4),
child: Text(
subjectAvg.toStringAsFixed(2),
style: appStyle.fonts.B_16SB
.apply(color: subjectAvgColor),
),
),
),
],
),
FirkaCard(left: [
Text(
widget.data.l10n.class_avg,
style: appStyle.fonts.B_16SB
.apply(color: appStyle.colors.textPrimary),
),
]),
FirkaCard(
left: [
Text(
widget.data.l10n.class_n,
style: appStyle.fonts.B_16SB
.apply(color: appStyle.colors.textPrimary),
),
],
right: [
Text(
week!.response!.length.toString(),
style: appStyle.fonts.B_14SB
.apply(color: appStyle.colors.textPrimary),
),
],
),
],
),
),
],
),
),
);
}
}
}

View File

@@ -1,129 +1,161 @@
import 'package:firka/helpers/api/model/grade.dart';
import 'package:firka/helpers/extensions.dart';
import 'package:firka/helpers/ui/firka_card.dart';
import 'package:firka/helpers/ui/grade.dart';
import 'package:firka/helpers/ui/stateless_async_widget.dart';
import 'package:flutter/material.dart';
import '../../../../main.dart';
import '../../../model/style.dart';
import '../../../widget/delayed_spinner.dart';
class HomeGradesSubjectScreen extends StatelessAsyncWidget {
class HomeGradesSubjectScreen extends StatefulWidget {
final AppInitialization data;
final String subPageUid;
const HomeGradesSubjectScreen(this.data, this.subPageUid, {super.key});
@override
Future<Widget> buildAsync(BuildContext context) async {
var grades = (await data.client.getGrades())
.response!
.where((grade) => grade.subject.uid == subPageUid)
.where((grade) => grade.type.name != "felevi_jegy_ertekeles");
var aGrade = grades.first;
var groups = grades.groupList((grade) => grade.recordDate);
State<StatefulWidget> createState() => _HomeGradesSubjectScreen();
}
var gradeWidgets = List<Widget>.empty(growable: true);
class _HomeGradesSubjectScreen extends State<HomeGradesSubjectScreen> {
Iterable<Grade>? grades;
for (var group in groups.entries) {
gradeWidgets.add(SizedBox(
height: 8,
));
gradeWidgets.add(Text(
group.key.format(data.l10n, FormatMode.grades),
style: appStyle.fonts.H_14px,
));
gradeWidgets.add(SizedBox(
height: 8,
));
for (var grade in group.value) {
gradeWidgets.add(FirkaCard(
left: [
Row(
children: [
GradeWidget(grade),
SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
grade.topic ?? grade.type.description!,
style: appStyle.fonts.B_14SB,
),
Text(
grade.mode?.description ?? "",
style: appStyle.fonts.B_14R,
)
],
)
],
)
],
@override
void initState() {
super.initState();
(() async {
grades = (await widget.data.client.getGrades())
.response!
.where((grade) => grade.subject.uid == widget.subPageUid)
.where((grade) => grade.type.name != "felevi_jegy_ertekeles");
if (mounted) setState(() {});
})();
}
@override
Widget build(BuildContext context) {
if (grades != null) {
var aGrade = grades!.first;
var groups = grades!.groupList((grade) => grade.recordDate);
var gradeWidgets = List<Widget>.empty(growable: true);
for (var group in groups.entries) {
gradeWidgets.add(SizedBox(
height: 8,
));
}
}
return Flexible(
child: Padding(
padding: const EdgeInsets.only(
left: 16.0,
right: 16.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
data.l10n.subjects,
style: appStyle
.fonts.H_16px // TODO: Replace this with the proper font
.apply(color: appStyle.colors.textPrimary),
)
],
),
SizedBox(height: 16),
SizedBox(
height: MediaQuery.of(context).size.height -
MediaQuery.of(context).padding.top -
230,
child: ListView(
gradeWidgets.add(Text(
group.key.format(widget.data.l10n, FormatMode.grades),
style: appStyle.fonts.H_14px,
));
gradeWidgets.add(SizedBox(
height: 8,
));
for (var grade in group.value) {
gradeWidgets.add(FirkaCard(
left: [
Row(
children: [
FirkaCard(
left: [
Padding(
padding: EdgeInsets.only(left: 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
aGrade.subject.name,
style: appStyle.fonts.H_H2,
),
Text(
aGrade.teacher, // For some reason the teacher's
// name isn't stored in the subject, so we need
// to get *a* grade, and then get the teacher's
// name from there :3
style: appStyle.fonts.B_14R,
)
],
),
GradeWidget(grade),
SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
grade.topic ?? grade.type.description!,
style: appStyle.fonts.B_14SB,
),
Text(
grade.mode?.description ?? "",
style: appStyle.fonts.B_14R,
)
],
),
Padding(
padding: EdgeInsets.only(left: 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: gradeWidgets,
),
)
],
)
],
));
}
}
return Flexible(
child: Padding(
padding: const EdgeInsets.only(
left: 16.0,
right: 16.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
widget.data.l10n.subjects,
style: appStyle
.fonts.H_16px // TODO: Replace this with the proper font
.apply(color: appStyle.colors.textPrimary),
)
],
),
),
SizedBox(height: 16),
SizedBox(
height: MediaQuery.of(context).size.height -
MediaQuery.of(context).padding.top -
230,
child: ListView(
children: [
FirkaCard(
left: [
Padding(
padding: EdgeInsets.only(left: 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
aGrade.subject.name,
style: appStyle.fonts.H_H2,
),
Text(
aGrade.teacher, // For some reason the teacher's
// name isn't stored in the subject, so we need
// to get *a* grade, and then get the teacher's
// name from there :3
style: appStyle.fonts.B_14R,
)
],
),
)
],
),
Padding(
padding: EdgeInsets.only(left: 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: gradeWidgets,
),
)
],
),
),
],
),
),
);
} else {
return SizedBox(
height: MediaQuery.of(context).size.height / 1.35,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(),
DelayedSpinnerWidget(),
SizedBox(),
],
),
),
);
);
}
}
}

View File

@@ -18,6 +18,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:home_widget/home_widget.dart';
import 'package:majesticons_flutter/majesticons_flutter.dart';
import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';
import '../../../../helpers/db/widget.dart';
import '../../../../helpers/debug_helper.dart';
@@ -70,6 +71,8 @@ class _HomeScreenState extends State<HomeScreen> {
Widget? toast;
bool pairingDone = false;
bool _disposed = false;
final RefreshController _refreshController =
RefreshController(initialRefresh: false);
ActiveToastType activeToast = ActiveToastType.none;
@@ -262,6 +265,17 @@ class _HomeScreenState extends State<HomeScreen> {
));
}
void _onRefresh() async {
await Future.delayed(Duration(milliseconds: 1000));
_refreshController.refreshCompleted();
}
void _onLoading() async {
if (mounted) setState(() {});
_refreshController.loadComplete();
}
@override
Widget build(BuildContext context) {
_updateSystemUI(); // Update system UI on every build, to compensate for the android system being dumb
@@ -327,138 +341,149 @@ class _HomeScreenState extends State<HomeScreen> {
}
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
return PopScope(
canPop: canPop,
child: Scaffold(
backgroundColor: appStyle.colors.background,
body: SafeArea(
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: Stack(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return SmartRefresher(
controller: _refreshController,
onLoading: _onLoading,
onRefresh: _onRefresh,
header: MaterialClassicHeader(
color: appStyle.colors.accent,
backgroundColor: appStyle.colors.background,
offset: 24,
),
child: PopScope(
canPop: canPop,
child: Scaffold(
backgroundColor: appStyle.colors.background,
body: SafeArea(
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: Stack(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [HomeSubPage(page, setPageCB, widget.data)],
),
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
appStyle.colors.background,
appStyle.colors.background.withValues(alpha: 0.0),
],
stops: const [0.0, 1.0],
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [HomeSubPage(page, setPageCB, widget.data)],
),
),
width: MediaQuery.of(context).size.width,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 55, vertical: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Home Button
BottomNavIconWidget(() {
if (page.page != HomePages.home) {
HapticFeedback.lightImpact();
setState(() {
previousPages.add(page);
canPop = false;
page = ActiveHomePage(HomePages.home);
});
}
},
page.page == HomePages.home,
page.page == HomePages.home
? Majesticon.homeSolid
: Majesticon.homeLine,
widget.data.l10n.home,
page.page == HomePages.home
? appStyle.colors.accent
: appStyle.colors.secondary,
appStyle.colors.textPrimary),
// Grades Button
BottomNavIconWidget(() {
if (page.page != HomePages.grades) {
HapticFeedback.lightImpact();
setState(() {
previousPages.add(page);
canPop = false;
page = ActiveHomePage(HomePages.grades);
});
}
},
page.page == HomePages.grades,
page.page == HomePages.grades
? Majesticon.bookmarkSolid
: Majesticon.bookmarkLine,
widget.data.l10n.grades,
page.page == HomePages.grades
? appStyle.colors.accent
: appStyle.colors.secondary,
appStyle.colors.textPrimary),
// Timetable Button
BottomNavIconWidget(() {
if (page.page != HomePages.timetable) {
HapticFeedback.lightImpact();
setState(() {
previousPages.add(page);
canPop = false;
page = ActiveHomePage(HomePages.timetable);
});
}
},
page.page == HomePages.timetable,
page.page == HomePages.timetable
? Majesticon.calendarSolid
: Majesticon.calendarLine,
widget.data.l10n.timetable,
page.page == HomePages.timetable
? appStyle.colors.accent
: appStyle.colors.secondary,
appStyle.colors.textPrimary),
// More Button
BottomNavIconWidget(
() {
HapticFeedback.lightImpact();
showExtrasBottomSheet(context, widget.data);
},
false,
Majesticon.globeEarthLine,
widget.data.l10n.other,
appStyle.colors.secondary,
appStyle.colors.textPrimary,
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
appStyle.colors.background,
appStyle.colors.background
.withValues(alpha: 0.0),
],
stops: const [0.0, 1.0],
),
],
),
width: MediaQuery.of(context).size.width,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 55, vertical: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Home Button
BottomNavIconWidget(() {
if (page.page != HomePages.home) {
HapticFeedback.lightImpact();
setState(() {
previousPages.add(page);
canPop = false;
page = ActiveHomePage(HomePages.home);
});
}
},
page.page == HomePages.home,
page.page == HomePages.home
? Majesticon.homeSolid
: Majesticon.homeLine,
widget.data.l10n.home,
page.page == HomePages.home
? appStyle.colors.accent
: appStyle.colors.secondary,
appStyle.colors.textPrimary),
// Grades Button
BottomNavIconWidget(() {
if (page.page != HomePages.grades) {
HapticFeedback.lightImpact();
setState(() {
previousPages.add(page);
canPop = false;
page = ActiveHomePage(HomePages.grades);
});
}
},
page.page == HomePages.grades,
page.page == HomePages.grades
? Majesticon.bookmarkSolid
: Majesticon.bookmarkLine,
widget.data.l10n.grades,
page.page == HomePages.grades
? appStyle.colors.accent
: appStyle.colors.secondary,
appStyle.colors.textPrimary),
// Timetable Button
BottomNavIconWidget(() {
if (page.page != HomePages.timetable) {
HapticFeedback.lightImpact();
setState(() {
previousPages.add(page);
canPop = false;
page =
ActiveHomePage(HomePages.timetable);
});
}
},
page.page == HomePages.timetable,
page.page == HomePages.timetable
? Majesticon.calendarSolid
: Majesticon.calendarLine,
widget.data.l10n.timetable,
page.page == HomePages.timetable
? appStyle.colors.accent
: appStyle.colors.secondary,
appStyle.colors.textPrimary),
// More Button
BottomNavIconWidget(
() {
HapticFeedback.lightImpact();
showExtrasBottomSheet(context, widget.data);
},
false,
Majesticon.globeEarthLine,
widget.data.l10n.other,
appStyle.colors.secondary,
appStyle.colors.textPrimary,
),
],
),
),
),
),
],
),
toast ?? SizedBox(),
],
),
toast ?? SizedBox(),
],
),
),
),
),
),
onPopInvokedWithResult: (_, __) => {
if (previousPages.isNotEmpty && page != previousPages.last)
{
setState(() {
page = previousPages.removeLast();
canPop = previousPages.isEmpty;
})
}
},
);
onPopInvokedWithResult: (_, __) => {
if (previousPages.isNotEmpty && page != previousPages.last)
{
setState(() {
page = previousPages.removeLast();
canPop = previousPages.isEmpty;
})
}
},
));
}
@override