diff --git a/firka/lib/ui/phone/pages/home/home_grades_subject.dart b/firka/lib/ui/phone/pages/home/home_grades_subject.dart index 0c069d7..8ea39cb 100644 --- a/firka/lib/ui/phone/pages/home/home_grades_subject.dart +++ b/firka/lib/ui/phone/pages/home/home_grades_subject.dart @@ -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 { 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 { ); 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)); } } diff --git a/firka/lib/ui/phone/pages/home/home_main.dart b/firka/lib/ui/phone/pages/home/home_main.dart index 06fcc3f..abc4275 100644 --- a/firka/lib/ui/phone/pages/home/home_main.dart +++ b/firka/lib/ui/phone/pages/home/home_main.dart @@ -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 { 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( diff --git a/firka/lib/ui/phone/widgets/info_board_item.dart b/firka/lib/ui/phone/widgets/info_board_item.dart deleted file mode 100644 index 4b9516d..0000000 --- a/firka/lib/ui/phone/widgets/info_board_item.dart +++ /dev/null @@ -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, - ), - ), - ], - ), - ], - ), - ], - ); - } -} diff --git a/firka/lib/ui/phone/widgets/info_card.dart b/firka/lib/ui/phone/widgets/info_card.dart new file mode 100644 index 0000000..bc6d590 --- /dev/null +++ b/firka/lib/ui/phone/widgets/info_card.dart @@ -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 texts; + final List right; + + final List 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( + 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 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 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); + }, + ); + } +} diff --git a/firka_common/lib/ui/components/filled_circle.dart b/firka_common/lib/ui/components/filled_circle.dart new file mode 100644 index 0000000..c177598 --- /dev/null +++ b/firka_common/lib/ui/components/filled_circle.dart @@ -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), + ); + } +} diff --git a/firka_common/lib/ui/components/grade.dart b/firka_common/lib/ui/components/grade.dart index 0a2a555..9557b82 100644 --- a/firka_common/lib/ui/components/grade.dart +++ b/firka_common/lib/ui/components/grade.dart @@ -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, ); } }