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/info_card.dart';
import 'package:kreta_api/kreta_api.dart';
import 'package:firka/core/extensions.dart';
import 'package:firka/ui/components/common_bottom_sheets.dart';
@@ -115,7 +116,7 @@ class _HomeGradesSubjectScreen extends FirkaState<HomeGradesSubjectScreen> {
left: [
Row(
children: [
GradeWidget.gradeValue(e.$1),
GradeWidget.gradeValue(e.$1, gradeWeight: e.$2),
SizedBox(width: 8),
Text(
'${widget.data.l10n.ghost_grade} ${e.$2}%',
@@ -156,50 +157,7 @@ class _HomeGradesSubjectScreen extends FirkaState<HomeGradesSubjectScreen> {
);
gradeWidgets.add(SizedBox(height: 8));
for (var grade in group.value) {
gradeWidgets.add(
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);
},
),
);
gradeWidgets.add(InfoCard.gradeDesc(grade));
}
}

View File

@@ -1,23 +1,20 @@
import 'dart:async';
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: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/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/shared/delayed_spinner.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:majesticons_flutter/majesticons_flutter.dart';
import 'package:firka/core/debug_helper.dart';
import 'package:firka/core/state/firka_state.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/core/bloc/home_refresh_cubit.dart';
import 'package:firka/ui/theme/style.dart';
@@ -306,60 +303,15 @@ class _HomeMainScreen extends FirkaState<HomeMainScreen> {
final noticeBoardWidgets = <(Widget, DateTime)>[];
for (final item in infoItems) {
noticeBoardWidgets.add((
GestureDetector(
child: InfoBoardItemWidget(item),
onTap: () {
context.push('/message', extra: item);
},
),
item.date,
));
noticeBoardWidgets.add((InfoCard.infoBoardItem(item), item.date));
}
for (final grade in gradeItems) {
noticeBoardWidgets.add((
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,
));
noticeBoardWidgets.add((InfoCard.gradeSubj(grade), grade.recordDate));
}
for (final entry in homeworkItems) {
noticeBoardWidgets.add((
HomeworkWidget(widget.data, entry),
entry.creationDate,
));
noticeBoardWidgets.add((InfoCard.homework(entry), entry.creationDate));
}
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/theme/style.dart';
import 'filled_circle.dart';
class GradeWidget extends StatelessWidget {
const GradeWidget(this.grade, {super.key})
: gradeValue = null,
_fromValue = false;
gradeWeight = null;
const GradeWidget.gradeValue(int value, {super.key})
: grade = null,
gradeValue = value,
_fromValue = true;
const GradeWidget.gradeValue(
this.gradeValue, {
this.gradeWeight = 100,
super.key,
}) : grade = null;
final Grade? grade;
final int? gradeValue;
final bool _fromValue;
final int? gradeWeight;
@override
Widget build(BuildContext context) {
if (_fromValue && gradeValue != null) {
return _buildNumericCircle(
gradeValue!,
getGradeColor(gradeValue!.toDouble()),
);
if (gradeValue != null && gradeValue != null) {
return _buildNumericCircle(gradeValue!, gradeWeight!);
}
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.numericValue != null) {
gradeColor = getGradeColor(
percentageToGrade(g.numericValue!).toDouble(),
);
}
final str = g.strValue.replaceAll('%', '');
return Card(
shape: const CircleBorder(),
shadowColor: Colors.transparent,
final gradeColor = getGradeColor(percentageToGrade(g.numericValue!));
return FilledCircle(
diameter: 36,
color: gradeColor.withAlpha(38),
child: Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(str, 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,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
g.numericValue!.toString(),
style: appStyle.fonts.P_14.copyWith(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) {
return Card(
shape: const CircleBorder(),
shadowColor: Colors.transparent,
color: gradeColor.withAlpha(38),
child: Padding(
padding: const EdgeInsets.only(left: 8, right: 8),
child: Text(
value.toString(),
style: appStyle.fonts.H_H1.copyWith(fontSize: 24, color: gradeColor),
Widget _buildNumericCircle(int value, int weight) {
final gradeColor = getGradeColor(value);
final size = 36.0;
final text = Text(
value.toString(),
style: weight < 100
? appStyle.fonts.H_H1.copyWith(
foreground: Paint()
..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,
);
}
}