1
0
forked from firka/firka

fix: grade style

This commit is contained in:
checkedear
2026-04-13 15:30:32 +02:00
parent fa66d9af14
commit e7c0a95638
6 changed files with 298 additions and 234 deletions

View File

@@ -1,4 +1,5 @@
import 'package:firka/ui/phone/widgets/grade_summary_bar.dart'; import 'package:firka/ui/phone/widgets/grade_summary_bar.dart';
import 'package:firka/ui/phone/widgets/info_card.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/components/common_bottom_sheets.dart'; import 'package:firka/ui/components/common_bottom_sheets.dart';
@@ -115,7 +116,7 @@ class _HomeGradesSubjectScreen extends FirkaState<HomeGradesSubjectScreen> {
left: [ left: [
Row( Row(
children: [ children: [
GradeWidget.gradeValue(e.$1), GradeWidget.gradeValue(e.$1, gradeWeight: e.$2),
SizedBox(width: 8), SizedBox(width: 8),
Text( Text(
'${widget.data.l10n.ghost_grade} ${e.$2}%', '${widget.data.l10n.ghost_grade} ${e.$2}%',
@@ -156,50 +157,7 @@ class _HomeGradesSubjectScreen extends FirkaState<HomeGradesSubjectScreen> {
); );
gradeWidgets.add(SizedBox(height: 8)); gradeWidgets.add(SizedBox(height: 8));
for (var grade in group.value) { for (var grade in group.value) {
gradeWidgets.add( gradeWidgets.add(InfoCard.gradeDesc(grade));
GestureDetector(
child: FirkaCard(
left: [
Row(
children: [
GradeWidget(grade),
SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: MediaQuery.of(context).size.width / 1.45,
child: Text(
(grade.topic ?? grade.type.description!)
.firstUpper(),
style: appStyle.fonts.B_16SB.apply(
color: appStyle.colors.textPrimary,
),
),
),
grade.mode?.description != null
? SizedBox(
width:
MediaQuery.of(context).size.width / 1.45,
child: Text(
grade.mode!.description!.firstUpper(),
style: appStyle.fonts.B_16R.apply(
color: appStyle.colors.textSecondary,
),
),
)
: SizedBox(),
],
),
],
),
],
),
onTap: () {
showGradeBottomSheet(context, widget.data, grade);
},
),
);
} }
} }

View File

@@ -1,23 +1,20 @@
import 'dart:async'; import 'dart:async';
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: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/components/common_bottom_sheets.dart';
import 'package:firka/ui/phone/widgets/home_main_starting_soon.dart'; import 'package:firka/ui/phone/widgets/home_main_starting_soon.dart';
import 'package:firka/ui/phone/widgets/homework.dart'; import 'package:firka/ui/phone/widgets/homework.dart';
import 'package:firka/ui/phone/widgets/info_board_item.dart';
import 'package:firka/ui/phone/widgets/lesson_small.dart'; import 'package:firka/ui/phone/widgets/lesson_small.dart';
import 'package:firka/ui/shared/delayed_spinner.dart'; import 'package:firka/ui/shared/delayed_spinner.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:majesticons_flutter/majesticons_flutter.dart'; import 'package:majesticons_flutter/majesticons_flutter.dart';
import 'package:firka/core/debug_helper.dart'; import 'package:firka/core/debug_helper.dart';
import 'package:firka/core/state/firka_state.dart'; import 'package:firka/core/state/firka_state.dart';
import 'package:firka/ui/components/firka_card.dart'; import 'package:firka/ui/components/firka_card.dart';
import 'package:firka/ui/components/grade.dart';
import 'package:firka/app/app_state.dart'; import 'package:firka/app/app_state.dart';
import 'package:firka/core/bloc/home_refresh_cubit.dart'; import 'package:firka/core/bloc/home_refresh_cubit.dart';
import 'package:firka/ui/theme/style.dart'; import 'package:firka/ui/theme/style.dart';
@@ -306,60 +303,15 @@ class _HomeMainScreen extends FirkaState<HomeMainScreen> {
final noticeBoardWidgets = <(Widget, DateTime)>[]; final noticeBoardWidgets = <(Widget, DateTime)>[];
for (final item in infoItems) { for (final item in infoItems) {
noticeBoardWidgets.add(( noticeBoardWidgets.add((InfoCard.infoBoardItem(item), item.date));
GestureDetector(
child: InfoBoardItemWidget(item),
onTap: () {
context.push('/message', extra: item);
},
),
item.date,
));
} }
for (final grade in gradeItems) { for (final grade in gradeItems) {
noticeBoardWidgets.add(( noticeBoardWidgets.add((InfoCard.gradeSubj(grade), grade.recordDate));
GestureDetector(
child: FirkaCard(
left: [
GradeWidget(grade),
SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
(grade.topic ?? grade.type.description!).firstUpper(),
style: appStyle.fonts.B_16SB.apply(
color: appStyle.colors.textPrimary,
),
),
grade.mode?.description != null
? Text(
grade.mode!.description!.firstUpper(),
style: appStyle.fonts.B_16R.apply(
color: appStyle.colors.textSecondary,
),
)
: SizedBox(),
],
),
),
],
),
onTap: () {
showGradeBottomSheet(context, widget.data, grade);
},
),
grade.recordDate,
));
} }
for (final entry in homeworkItems) { for (final entry in homeworkItems) {
noticeBoardWidgets.add(( noticeBoardWidgets.add((InfoCard.homework(entry), entry.creationDate));
HomeworkWidget(widget.data, entry),
entry.creationDate,
));
} }
noticeBoardWidgets.sort( noticeBoardWidgets.sort(

View File

@@ -1,73 +0,0 @@
import 'package:firka/ui/components/firka_card.dart';
import 'package:firka/ui/theme/style.dart';
import 'package:flutter/material.dart';
import 'package:kreta_api/kreta_api.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_16SB.apply(
color: appStyle.colors.textPrimary,
),
),
),
Text(
item.author,
style: appStyle.fonts.B_16R.apply(
color: appStyle.colors.textSecondary,
),
),
],
),
],
),
],
);
}
}

View File

@@ -0,0 +1,176 @@
import 'package:firka/app/app_state.dart';
import 'package:firka/core/extensions.dart';
import 'package:firka/data/models/homework_cache_model.dart';
import 'package:firka/ui/components/common_bottom_sheets.dart';
import 'package:firka/ui/components/firka_card.dart';
import 'package:firka/ui/components/grade.dart';
import 'package:firka/ui/components/grade_helpers.dart';
import 'package:firka/ui/shared/class_icon.dart';
import 'package:firka/ui/shared/firka_icon.dart';
import 'package:firka/ui/theme/style.dart';
import 'package:firka_common/ui/components/filled_circle.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:kreta_api/kreta_api.dart';
import 'package:majesticons_flutter/majesticons_flutter.dart';
class InfoCard extends StatelessWidget {
final void Function(BuildContext)? onTap;
final Widget icon;
final List<String> texts;
final List<Widget> right;
final List<TextStyle> textSyles = [
appStyle.fonts.B_16SB.apply(color: appStyle.colors.textPrimary),
appStyle.fonts.B_16R.apply(color: appStyle.colors.textSecondary),
];
InfoCard({
required this.icon,
required this.texts,
this.right = const [],
this.onTap,
super.key,
});
static Widget buildSubject(Color color, Subject subject) {
return FilledCircle(
diameter: 32,
color: color.withAlpha(38),
child: ClassIconWidget(
uid: subject.uid,
className: subject.name,
category: subject.category.name!,
color: color,
size: 20,
),
);
}
factory InfoCard.infoBoardItem(InfoBoardItem item) {
return InfoCard(
icon: FilledCircle(
diameter: 36,
color: appStyle.colors.accent,
child: Text(
item.author[0],
style: appStyle.fonts.H_H2.apply(color: appStyle.colors.textPrimary),
),
),
texts: [item.title, item.author],
onTap: (context) => context.push('/message', extra: item),
);
}
factory InfoCard.homework(Homework homework) {
return InfoCard(
icon: FilledCircle(
diameter: 36,
color: appStyle.colors.accent.withAlpha(38),
child: FutureBuilder<bool>(
future: isHomeworkDone(initData.isar, homework.uid),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return SizedBox();
}
final done = snapshot.data!;
return done
? FirkaIconWidget(
FirkaIconType.majesticonsLocal,
"homeWithMark",
color: appStyle.colors.accent,
size: 24,
)
: FirkaIconWidget(
FirkaIconType.majesticons,
Majesticon.homeSolid,
color: appStyle.colors.accent,
size: 24,
);
},
),
),
texts: [initData.l10n.homework, homework.subjectName],
right: [buildSubject(appStyle.colors.accent, homework.subject)],
onTap: (context) => showHomeworkBottomSheet(context, initData, homework),
);
}
factory InfoCard.gradeSubj(
Grade grade, {
void Function(BuildContext)? onTap,
}) {
return InfoCard(
icon: GradeWidget(grade),
texts: [
(grade.topic ?? grade.mode?.description ?? grade.type.description!)
.firstUpper(),
grade.subject.name.firstUpper(),
],
right: [buildSubject(appStyle.colors.accent, grade.subject)],
onTap:
onTap ?? (context) => showGradeBottomSheet(context, initData, grade),
);
}
factory InfoCard.gradeDesc(
Grade grade, {
void Function(BuildContext)? onTap,
}) {
if (grade.type.description == null) {
logger.info(grade);
}
List<String> texts = [
(grade.mode?.description ?? grade.type.description!).firstUpper(),
];
if (grade.topic != null) {
texts = [grade.topic!.firstUpper(), ...texts];
}
return InfoCard(
icon: GradeWidget(grade),
texts: texts,
right: [buildSubject(appStyle.colors.accent, grade.subject)],
onTap:
onTap ?? (context) => showGradeBottomSheet(context, initData, grade),
);
}
@override
Widget build(BuildContext context) {
List<Text> children = [];
int i = 0;
for (var text in texts) {
children.add(
Text(text, style: textSyles[i], overflow: TextOverflow.ellipsis),
);
if (i < textSyles.length) {
i++;
}
}
return GestureDetector(
child: FirkaCard.single(
padding: 16,
child: Row(
spacing: 12,
children: [
icon,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
),
),
...right,
],
),
),
onTap: () {
if (onTap == null) return;
onTap!.call(context);
},
);
}
}

View File

@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:kreta_api/kreta_api.dart';
import 'package:firka_common/ui/components/grade_helpers.dart';
import 'package:firka_common/ui/theme/style.dart';
class FilledCircle extends StatelessWidget {
final double diameter;
final Color color;
final Widget child;
const FilledCircle({
required this.diameter,
required this.child,
required this.color,
super.key,
});
@override
Widget build(BuildContext context) {
return Container(
width: diameter,
height: diameter,
decoration: ShapeDecoration(
color: color,
shape: CircleBorder(eccentricity: 1),
),
child: Center(child: child),
);
}
}

View File

@@ -4,94 +4,114 @@ import 'package:kreta_api/kreta_api.dart';
import 'package:firka_common/ui/components/grade_helpers.dart'; import 'package:firka_common/ui/components/grade_helpers.dart';
import 'package:firka_common/ui/theme/style.dart'; import 'package:firka_common/ui/theme/style.dart';
import 'filled_circle.dart';
class GradeWidget extends StatelessWidget { class GradeWidget extends StatelessWidget {
const GradeWidget(this.grade, {super.key}) const GradeWidget(this.grade, {super.key})
: gradeValue = null, : gradeValue = null,
_fromValue = false; gradeWeight = null;
const GradeWidget.gradeValue(int value, {super.key}) const GradeWidget.gradeValue(
: grade = null, this.gradeValue, {
gradeValue = value, this.gradeWeight = 100,
_fromValue = true; super.key,
}) : grade = null;
final Grade? grade; final Grade? grade;
final int? gradeValue; final int? gradeValue;
final bool _fromValue; final int? gradeWeight;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_fromValue && gradeValue != null) { if (gradeValue != null && gradeValue != null) {
return _buildNumericCircle( return _buildNumericCircle(gradeValue!, gradeWeight!);
gradeValue!,
getGradeColor(gradeValue!.toDouble()),
);
} }
final g = grade!; final g = grade!;
Color gradeColor = appStyle.colors.grade1;
final gradeStr = g.numericValue?.toString() ?? '0'; if (g.numericValue == null) {
final gradeColor = appStyle.colors.accent;
return FilledCircle(
diameter: 36,
color: gradeColor.withAlpha(38),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('', style: appStyle.fonts.P_14.copyWith(color: gradeColor)),
Text('', style: appStyle.fonts.P_14.copyWith(color: gradeColor)),
],
),
);
}
if (g.valueType.name == 'Szazalekos') { if (g.valueType.name == 'Szazalekos') {
if (g.numericValue != null) { final gradeColor = getGradeColor(percentageToGrade(g.numericValue!));
gradeColor = getGradeColor( return FilledCircle(
percentageToGrade(g.numericValue!).toDouble(), diameter: 36,
);
}
final str = g.strValue.replaceAll('%', '');
return Card(
shape: const CircleBorder(),
shadowColor: Colors.transparent,
color: gradeColor.withAlpha(38), color: gradeColor.withAlpha(38),
child: Padding( child: Row(
padding: const EdgeInsets.all(8), mainAxisSize: MainAxisSize.min,
child: Row( children: [
mainAxisSize: MainAxisSize.min, Text(
children: [ g.numericValue!.toString(),
Text(str, style: appStyle.fonts.P_14.copyWith(color: gradeColor)), style: appStyle.fonts.P_14.copyWith(color: gradeColor),
Text('%', style: appStyle.fonts.P_12.copyWith(color: gradeColor)),
],
),
),
);
}
if (g.numericValue != null) {
gradeColor = getGradeColor(g.numericValue!.toDouble());
}
if (gradeStr == '0') {
return Card(
shadowColor: Colors.transparent,
color: gradeColor.withAlpha(38),
child: Padding(
padding: const EdgeInsets.only(left: 8, right: 8, top: 2, bottom: 2),
child: Text(
g.strValue,
style: appStyle.fonts.H_H1.copyWith(
fontSize: 16,
color: gradeColor,
), ),
), Text('%', style: appStyle.fonts.P_12.copyWith(color: gradeColor)),
],
), ),
); );
} }
return _buildNumericCircle(g.numericValue!, gradeColor); return _buildNumericCircle(g.numericValue!, g.weightPercentage ?? 100);
} }
Widget _buildNumericCircle(int value, Color gradeColor) { Widget _buildNumericCircle(int value, int weight) {
return Card( final gradeColor = getGradeColor(value);
shape: const CircleBorder(), final size = 36.0;
shadowColor: Colors.transparent, final text = Text(
color: gradeColor.withAlpha(38), value.toString(),
child: Padding( style: weight < 100
padding: const EdgeInsets.only(left: 8, right: 8), ? appStyle.fonts.H_H1.copyWith(
child: Text( foreground: Paint()
value.toString(), ..color = gradeColor
style: appStyle.fonts.H_H1.copyWith(fontSize: 24, color: gradeColor), ..style = PaintingStyle.stroke
..strokeJoin = StrokeJoin.round
..strokeCap = StrokeCap.round
..strokeWidth = 1.13,
)
: appStyle.fonts.H_H1.copyWith(color: gradeColor),
);
if (weight > 100) {
final circle_size = 30.0;
final circle = FilledCircle(
diameter: circle_size,
color: gradeColor.withAlpha(38),
child: SizedBox(),
);
return SizedBox(
width: size,
height: size,
child: Stack(
children: [
Transform.translate(
offset: Offset(size - circle_size, 0),
child: circle,
),
Transform.translate(
offset: Offset(0, size - circle_size),
child: circle,
),
Center(child: text),
],
), ),
), );
}
return FilledCircle(
diameter: 36,
color: gradeColor.withAlpha(38),
child: text,
); );
} }
} }