This commit is contained in:
Márton Kiss
2023-05-26 21:25:00 +02:00
parent 9e3b805fdd
commit 1558794e93
528 changed files with 38239 additions and 37732 deletions

View File

@@ -1,302 +1,302 @@
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/theme/colors/colors.dart';
import 'package:filcnaplo_kreta_api/models/grade.dart';
import 'package:filcnaplo/helpers/subject.dart';
import 'package:filcnaplo/utils/format.dart';
import 'package:filcnaplo_mobile_ui/pages/grades/calculator/grade_calculator_provider.dart';
import 'package:filcnaplo_mobile_ui/pages/grades/subject_grades_container.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart';
class GradeTile extends StatelessWidget {
const GradeTile(this.grade, {Key? key, this.onTap, this.padding, this.censored = false}) : super(key: key);
final Grade grade;
final void Function()? onTap;
final EdgeInsetsGeometry? padding;
final bool censored;
@override
Widget build(BuildContext context) {
String title;
String subtitle;
EdgeInsets leadingPadding = EdgeInsets.zero;
bool isSubjectView = SubjectGradesContainer.of(context) != null;
String subjectName = grade.subject.renamedTo ?? grade.subject.name.capital();
String modeDescription = grade.mode.description.capital();
String description = grade.description.capital();
GradeCalculatorProvider calculatorProvider = Provider.of<GradeCalculatorProvider>(context, listen: false);
// Test order:
// description
// mode
// value name
if (grade.type == GradeType.midYear || grade.type == GradeType.ghost) {
if (grade.description != "") {
title = description;
} else {
title = modeDescription != "" ? modeDescription : grade.value.valueName.split("(")[0];
}
} else {
title = subjectName;
}
// Test order:
// subject name
// mode + weight != 100
if (grade.type == GradeType.midYear) {
subtitle = isSubjectView
? description != ""
? modeDescription
: ""
: subjectName;
} else {
subtitle = grade.value.valueName.split("(")[0];
}
if (subtitle != "") leadingPadding = const EdgeInsets.only(top: 2.0);
return Material(
type: MaterialType.transparency,
child: Padding(
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
child: ListTile(
visualDensity: VisualDensity.compact,
contentPadding: isSubjectView
? grade.type != GradeType.ghost
? const EdgeInsets.symmetric(horizontal: 12.0)
: const EdgeInsets.only(left: 12.0, right: 4.0)
: const EdgeInsets.only(left: 8.0, right: 12.0),
onTap: onTap,
// onLongPress: kDebugMode ? () => log(jsonEncode(grade.json)) : null,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)),
leading: isSubjectView
? GradeValueWidget(grade.value)
: SizedBox(
width: 44,
height: 44,
child: censored
? Container(
decoration: BoxDecoration(
color: AppColors.of(context).text.withOpacity(.55),
borderRadius: BorderRadius.circular(60.0),
),
)
: Center(
child: Padding(
padding: leadingPadding,
child: Icon(
SubjectIcon.resolveVariant(subject: grade.subject, context: context),
size: 28.0,
color: AppColors.of(context).text.withOpacity(.75),
),
),
),
),
title: censored
? Wrap(
children: [
Container(
width: 110,
height: 15,
decoration: BoxDecoration(
color: AppColors.of(context).text.withOpacity(.85),
borderRadius: BorderRadius.circular(8.0),
),
),
],
)
: Text(
title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontWeight: FontWeight.w600, fontStyle: grade.subject.isRenamed && title == subjectName ? FontStyle.italic : null),
),
subtitle: subtitle != ""
? censored
? Wrap(
children: [
Container(
width: 50,
height: 10,
decoration: BoxDecoration(
color: AppColors.of(context).text.withOpacity(.45),
borderRadius: BorderRadius.circular(8.0),
),
),
],
)
: Text(
subtitle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w500),
)
: null,
trailing: isSubjectView
? grade.type != GradeType.ghost
? Text(grade.date.format(context), style: const TextStyle(fontWeight: FontWeight.w500))
: IconButton(
splashRadius: 24.0,
icon: Icon(FeatherIcons.trash2, color: AppColors.of(context).red),
onPressed: () {
calculatorProvider.removeGrade(grade);
},
)
: censored
? Container(
width: 15,
height: 15,
decoration: BoxDecoration(
color: AppColors.of(context).text.withOpacity(.45),
borderRadius: BorderRadius.circular(8.0),
),
)
: GradeValueWidget(grade.value),
minLeadingWidth: isSubjectView ? 32.0 : 0,
),
),
);
}
}
class GradeValueWidget extends StatelessWidget {
const GradeValueWidget(
this.value, {
Key? key,
this.size = 38.0,
this.fill = false,
this.contrast = false,
this.shadow = false,
this.outline = false,
this.complemented = false,
this.nocolor = false,
}) : super(key: key);
final GradeValue value;
final double size;
final bool fill;
final bool contrast;
final bool shadow;
final bool outline;
final bool complemented;
final bool nocolor;
@override
Widget build(BuildContext context) {
GradeValue value = this.value;
bool isSubjectView = SubjectGradesContainer.of(context) != null;
Color color = gradeColor(context: context, value: value.value, nocolor: nocolor);
Widget valueText;
final percentage = value.percentage;
if (percentage) {
double multiplier = 1.0;
if (isSubjectView) multiplier = 0.75;
valueText = Text.rich(
TextSpan(
text: value.value.toString(),
children: [
TextSpan(
text: "\n%",
style: TextStyle(fontWeight: FontWeight.w700, fontSize: size / 2.5 * multiplier, height: 0.7),
),
],
style: TextStyle(fontWeight: FontWeight.w700, fontSize: size / 1 * multiplier, height: 1),
),
textAlign: TextAlign.center,
);
} else if (value.valueName.toLowerCase().specialChars() == 'nem irt') {
valueText = const Icon(FeatherIcons.slash);
} else {
valueText = Stack(alignment: Alignment.topRight, children: [
Transform.translate(
offset: (value.weight >= 200) ? const Offset(2, 1.5) : Offset.zero,
child: Text(
value.value.toString(),
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: value.weight == 50 ? FontWeight.w500 : FontWeight.bold,
fontSize: size,
color: contrast ? Colors.white : color,
shadows: [
if (value.weight >= 200)
Shadow(
color: (contrast ? Colors.white : color).withOpacity(.4),
offset: const Offset(-4, -3),
)
],
),
),
),
if (complemented)
Transform.translate(
offset: const Offset(9, 1),
child: Text(
"*",
style: TextStyle(fontSize: size / 1.6, fontWeight: FontWeight.bold),
),
),
]);
}
return fill
? Container(
width: size * 1.4,
height: size * 1.4,
decoration: BoxDecoration(
color: color.withOpacity(contrast ? 1.0 : .25),
shape: BoxShape.circle,
boxShadow: [
if (shadow)
BoxShadow(
color: color,
blurRadius: 62.0,
)
],
),
child: Center(child: valueText),
)
: valueText;
}
}
Color gradeColor({required BuildContext context, required num value, bool nocolor = false}) {
int valueInt = 0;
var settings = Provider.of<SettingsProvider>(context, listen: false);
try {
if (value < 2.0) {
valueInt = 1;
} else {
if (value >= value.floor() + settings.rounding / 10) {
valueInt = value.ceil();
} else {
valueInt = value.floor();
}
}
} catch (_) {}
if (nocolor) return AppColors.of(context).text;
switch (valueInt) {
case 5:
return settings.gradeColors[4];
case 4:
return settings.gradeColors[3];
case 3:
return settings.gradeColors[2];
case 2:
return settings.gradeColors[1];
case 1:
return settings.gradeColors[0];
default:
return AppColors.of(context).text;
}
}
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/theme/colors/colors.dart';
import 'package:filcnaplo_kreta_api/models/grade.dart';
import 'package:filcnaplo/helpers/subject.dart';
import 'package:filcnaplo/utils/format.dart';
import 'package:filcnaplo_mobile_ui/pages/grades/calculator/grade_calculator_provider.dart';
import 'package:filcnaplo_mobile_ui/pages/grades/subject_grades_container.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart';
class GradeTile extends StatelessWidget {
const GradeTile(this.grade, {Key? key, this.onTap, this.padding, this.censored = false}) : super(key: key);
final Grade grade;
final void Function()? onTap;
final EdgeInsetsGeometry? padding;
final bool censored;
@override
Widget build(BuildContext context) {
String title;
String subtitle;
EdgeInsets leadingPadding = EdgeInsets.zero;
bool isSubjectView = SubjectGradesContainer.of(context) != null;
String subjectName = grade.subject.renamedTo ?? grade.subject.name.capital();
String modeDescription = grade.mode.description.capital();
String description = grade.description.capital();
GradeCalculatorProvider calculatorProvider = Provider.of<GradeCalculatorProvider>(context, listen: false);
// Test order:
// description
// mode
// value name
if (grade.type == GradeType.midYear || grade.type == GradeType.ghost) {
if (grade.description != "") {
title = description;
} else {
title = modeDescription != "" ? modeDescription : grade.value.valueName.split("(")[0];
}
} else {
title = subjectName;
}
// Test order:
// subject name
// mode + weight != 100
if (grade.type == GradeType.midYear) {
subtitle = isSubjectView
? description != ""
? modeDescription
: ""
: subjectName;
} else {
subtitle = grade.value.valueName.split("(")[0];
}
if (subtitle != "") leadingPadding = const EdgeInsets.only(top: 2.0);
return Material(
type: MaterialType.transparency,
child: Padding(
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
child: ListTile(
visualDensity: VisualDensity.compact,
contentPadding: isSubjectView
? grade.type != GradeType.ghost
? const EdgeInsets.symmetric(horizontal: 12.0)
: const EdgeInsets.only(left: 12.0, right: 4.0)
: const EdgeInsets.only(left: 8.0, right: 12.0),
onTap: onTap,
// onLongPress: kDebugMode ? () => log(jsonEncode(grade.json)) : null,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)),
leading: isSubjectView
? GradeValueWidget(grade.value)
: SizedBox(
width: 44,
height: 44,
child: censored
? Container(
decoration: BoxDecoration(
color: AppColors.of(context).text.withOpacity(.55),
borderRadius: BorderRadius.circular(60.0),
),
)
: Center(
child: Padding(
padding: leadingPadding,
child: Icon(
SubjectIcon.resolveVariant(subject: grade.subject, context: context),
size: 28.0,
color: AppColors.of(context).text.withOpacity(.75),
),
),
),
),
title: censored
? Wrap(
children: [
Container(
width: 110,
height: 15,
decoration: BoxDecoration(
color: AppColors.of(context).text.withOpacity(.85),
borderRadius: BorderRadius.circular(8.0),
),
),
],
)
: Text(
title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontWeight: FontWeight.w600, fontStyle: grade.subject.isRenamed && title == subjectName ? FontStyle.italic : null),
),
subtitle: subtitle != ""
? censored
? Wrap(
children: [
Container(
width: 50,
height: 10,
decoration: BoxDecoration(
color: AppColors.of(context).text.withOpacity(.45),
borderRadius: BorderRadius.circular(8.0),
),
),
],
)
: Text(
subtitle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w500),
)
: null,
trailing: isSubjectView
? grade.type != GradeType.ghost
? Text(grade.date.format(context), style: const TextStyle(fontWeight: FontWeight.w500))
: IconButton(
splashRadius: 24.0,
icon: Icon(FeatherIcons.trash2, color: AppColors.of(context).red),
onPressed: () {
calculatorProvider.removeGrade(grade);
},
)
: censored
? Container(
width: 15,
height: 15,
decoration: BoxDecoration(
color: AppColors.of(context).text.withOpacity(.45),
borderRadius: BorderRadius.circular(8.0),
),
)
: GradeValueWidget(grade.value),
minLeadingWidth: isSubjectView ? 32.0 : 0,
),
),
);
}
}
class GradeValueWidget extends StatelessWidget {
const GradeValueWidget(
this.value, {
Key? key,
this.size = 38.0,
this.fill = false,
this.contrast = false,
this.shadow = false,
this.outline = false,
this.complemented = false,
this.nocolor = false,
}) : super(key: key);
final GradeValue value;
final double size;
final bool fill;
final bool contrast;
final bool shadow;
final bool outline;
final bool complemented;
final bool nocolor;
@override
Widget build(BuildContext context) {
GradeValue value = this.value;
bool isSubjectView = SubjectGradesContainer.of(context) != null;
Color color = gradeColor(context: context, value: value.value, nocolor: nocolor);
Widget valueText;
final percentage = value.percentage;
if (percentage) {
double multiplier = 1.0;
if (isSubjectView) multiplier = 0.75;
valueText = Text.rich(
TextSpan(
text: value.value.toString(),
children: [
TextSpan(
text: "\n%",
style: TextStyle(fontWeight: FontWeight.w700, fontSize: size / 2.5 * multiplier, height: 0.7),
),
],
style: TextStyle(fontWeight: FontWeight.w700, fontSize: size / 1 * multiplier, height: 1),
),
textAlign: TextAlign.center,
);
} else if (value.valueName.toLowerCase().specialChars() == 'nem irt') {
valueText = const Icon(FeatherIcons.slash);
} else {
valueText = Stack(alignment: Alignment.topRight, children: [
Transform.translate(
offset: (value.weight >= 200) ? const Offset(2, 1.5) : Offset.zero,
child: Text(
value.value.toString(),
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: value.weight == 50 ? FontWeight.w500 : FontWeight.bold,
fontSize: size,
color: contrast ? Colors.white : color,
shadows: [
if (value.weight >= 200)
Shadow(
color: (contrast ? Colors.white : color).withOpacity(.4),
offset: const Offset(-4, -3),
)
],
),
),
),
if (complemented)
Transform.translate(
offset: const Offset(9, 1),
child: Text(
"*",
style: TextStyle(fontSize: size / 1.6, fontWeight: FontWeight.bold),
),
),
]);
}
return fill
? Container(
width: size * 1.4,
height: size * 1.4,
decoration: BoxDecoration(
color: color.withOpacity(contrast ? 1.0 : .25),
shape: BoxShape.circle,
boxShadow: [
if (shadow)
BoxShadow(
color: color,
blurRadius: 62.0,
)
],
),
child: Center(child: valueText),
)
: valueText;
}
}
Color gradeColor({required BuildContext context, required num value, bool nocolor = false}) {
int valueInt = 0;
var settings = Provider.of<SettingsProvider>(context, listen: false);
try {
if (value < 2.0) {
valueInt = 1;
} else {
if (value >= value.floor() + settings.rounding / 10) {
valueInt = value.ceil();
} else {
valueInt = value.floor();
}
}
} catch (_) {}
if (nocolor) return AppColors.of(context).text;
switch (valueInt) {
case 5:
return settings.gradeColors[4];
case 4:
return settings.gradeColors[3];
case 3:
return settings.gradeColors[2];
case 2:
return settings.gradeColors[1];
case 1:
return settings.gradeColors[0];
default:
return AppColors.of(context).text;
}
}

View File

@@ -1,308 +1,308 @@
import 'package:filcnaplo_kreta_api/providers/exam_provider.dart';
import 'package:filcnaplo_kreta_api/providers/homework_provider.dart';
import 'package:filcnaplo/theme/colors/colors.dart';
import 'package:filcnaplo_kreta_api/models/exam.dart';
import 'package:filcnaplo_kreta_api/models/homework.dart';
import 'package:filcnaplo_kreta_api/models/lesson.dart';
import 'package:filcnaplo/utils/format.dart';
import 'package:filcnaplo_mobile_ui/common/panel/panel.dart';
import 'package:filcnaplo_mobile_ui/common/widgets/exam/exam_view.dart';
import 'package:filcnaplo_mobile_ui/common/widgets/homework/homework_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'lesson_tile.i18n.dart';
class LessonTile extends StatelessWidget {
const LessonTile(this.lesson, {Key? key, this.onTap, this.swapDesc = false}) : super(key: key);
final Lesson lesson;
final bool swapDesc;
final void Function()? onTap;
@override
Widget build(BuildContext context) {
List<Widget> subtiles = [];
Color accent = Theme.of(context).colorScheme.secondary;
bool fill = false;
bool fillLeading = false;
String lessonIndexTrailing = "";
// Only put a trailing . if its a digit
if (RegExp(r'\d').hasMatch(lesson.lessonIndex)) lessonIndexTrailing = ".";
var now = DateTime.now();
if (lesson.start.isBefore(now) && lesson.end.isAfter(now) && lesson.status?.name != "Elmaradt") {
fillLeading = true;
}
if (lesson.substituteTeacher != "") {
fill = true;
accent = AppColors.of(context).yellow;
}
if (lesson.status?.name == "Elmaradt") {
fill = true;
accent = AppColors.of(context).red;
}
if (lesson.isEmpty) {
accent = AppColors.of(context).text.withOpacity(0.6);
}
if (!lesson.studentPresence) {
subtiles.add(LessonSubtile(
type: LessonSubtileType.absence,
title: "absence".i18n,
));
}
if (lesson.homeworkId != "") {
Homework homework = Provider.of<HomeworkProvider>(context, listen: false)
.homework
.firstWhere((h) => h.id == lesson.homeworkId, orElse: () => Homework.fromJson({}));
if (homework.id != "") {
subtiles.add(LessonSubtile(
type: LessonSubtileType.homework,
title: homework.content,
onPressed: () => HomeworkView.show(homework, context: context),
));
}
}
if (lesson.exam != "") {
Exam exam = Provider.of<ExamProvider>(context, listen: false).exams.firstWhere((t) => t.id == lesson.exam, orElse: () => Exam.fromJson({}));
if (exam.id != "") {
subtiles.add(LessonSubtile(
type: LessonSubtileType.exam,
title: exam.description != "" ? exam.description : exam.mode?.description ?? "exam".i18n,
onPressed: () => ExamView.show(exam, context: context),
));
}
}
String description = '';
String room = '';
final cleanDesc = lesson.description.specialChars().toLowerCase().replaceAll(lesson.subject.name.specialChars().toLowerCase(), '');
if (!swapDesc) {
if (cleanDesc != "") {
description = lesson.description;
}
// Changed lesson Description
if (lesson.isChanged) {
if (lesson.status?.name == "Elmaradt") {
description = 'cancelled'.i18n;
} else if (lesson.substituteTeacher != "") {
description = 'substitution'.i18n;
}
}
room = lesson.room.replaceAll("_", " ");
} else {
description = lesson.room.replaceAll("_", " ");
}
return Padding(
padding: const EdgeInsets.only(bottom: 2.0),
child: Material(
color: fill ? accent.withOpacity(.25) : Colors.transparent,
borderRadius: BorderRadius.circular(12.0),
child: Visibility(
visible: lesson.subject.id != '' || lesson.isEmpty,
replacement: Padding(
padding: const EdgeInsets.only(top: 6.0),
child: PanelTitle(title: Text(lesson.name)),
),
child: Padding(
padding: EdgeInsets.only(bottom: subtiles.isEmpty ? 0.0 : 12.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
minVerticalPadding: 12.0,
dense: true,
onTap: onTap,
// onLongPress: kDebugMode ? () => log(jsonEncode(lesson.json)) : null,
visualDensity: VisualDensity.compact,
contentPadding: const EdgeInsets.symmetric(horizontal: 4.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
title: Text(
!lesson.isEmpty ? lesson.subject.renamedTo ?? lesson.subject.name.capital() : "empty".i18n,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 15.5,
color: AppColors.of(context).text.withOpacity(!lesson.isEmpty ? 1.0 : 0.5),
fontStyle: lesson.subject.isRenamed ? FontStyle.italic : null),
),
subtitle: description != ""
? Text(
description,
style: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 14.0,
),
maxLines: 1,
softWrap: false,
overflow: TextOverflow.ellipsis,
)
: null,
minLeadingWidth: 34.0,
leading: AspectRatio(
aspectRatio: 1,
child: Center(
child: Stack(
children: [
Text(
lesson.lessonIndex + lessonIndexTrailing,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.w600,
color: accent,
),
),
// Current lesson indicator
Transform.translate(
offset: const Offset(-12.0, -2.0),
child: Container(
decoration: BoxDecoration(
color: fillLeading ? Theme.of(context).colorScheme.secondary.withOpacity(.3) : const Color(0x00000000),
borderRadius: BorderRadius.circular(12.0),
boxShadow: [
if (fillLeading)
BoxShadow(
color: Theme.of(context).colorScheme.secondary.withOpacity(.25),
blurRadius: 6.0,
)
],
),
margin: const EdgeInsets.symmetric(vertical: 4.0),
width: 4.0,
height: double.infinity,
),
)
],
),
),
),
trailing: !lesson.isEmpty
? Row(
mainAxisSize: MainAxisSize.min,
children: [
if (!swapDesc)
SizedBox(
width: 52.0,
child: Padding(
padding: const EdgeInsets.only(right: 6.0),
child: Text(
room,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: TextStyle(
fontWeight: FontWeight.w500,
color: AppColors.of(context).text.withOpacity(.75),
),
),
),
),
Stack(
alignment: Alignment.center,
children: [
// Fix alignment hack
const Opacity(opacity: 0, child: Text("EE:EE")),
Text(
"${DateFormat("H:mm").format(lesson.start)}\n${DateFormat("H:mm").format(lesson.end)}",
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.w500,
color: AppColors.of(context).text.withOpacity(.9),
),
),
],
),
],
)
: null,
),
// Homework & Exams
...subtiles,
],
),
),
),
),
);
}
}
enum LessonSubtileType { homework, exam, absence }
class LessonSubtile extends StatelessWidget {
const LessonSubtile({Key? key, this.onPressed, required this.title, required this.type}) : super(key: key);
final Function()? onPressed;
final String title;
final LessonSubtileType type;
@override
Widget build(BuildContext context) {
IconData icon;
Color iconColor = AppColors.of(context).text;
switch (type) {
case LessonSubtileType.absence:
icon = FeatherIcons.slash;
iconColor = AppColors.of(context).red;
break;
case LessonSubtileType.exam:
icon = FeatherIcons.file;
break;
case LessonSubtileType.homework:
icon = FeatherIcons.home;
break;
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: InkWell(
onTap: onPressed,
borderRadius: BorderRadius.circular(8.0),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
children: [
Center(
child: SizedBox(
width: 30.0,
child: Icon(icon, color: iconColor.withOpacity(.75), size: 20.0),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 20.0),
child: Text(
title.escapeHtml(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontWeight: FontWeight.w500, color: AppColors.of(context).text.withOpacity(.65)),
),
),
),
],
),
),
),
);
}
}
import 'package:filcnaplo_kreta_api/providers/exam_provider.dart';
import 'package:filcnaplo_kreta_api/providers/homework_provider.dart';
import 'package:filcnaplo/theme/colors/colors.dart';
import 'package:filcnaplo_kreta_api/models/exam.dart';
import 'package:filcnaplo_kreta_api/models/homework.dart';
import 'package:filcnaplo_kreta_api/models/lesson.dart';
import 'package:filcnaplo/utils/format.dart';
import 'package:filcnaplo_mobile_ui/common/panel/panel.dart';
import 'package:filcnaplo_mobile_ui/common/widgets/exam/exam_view.dart';
import 'package:filcnaplo_mobile_ui/common/widgets/homework/homework_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'lesson_tile.i18n.dart';
class LessonTile extends StatelessWidget {
const LessonTile(this.lesson, {Key? key, this.onTap, this.swapDesc = false}) : super(key: key);
final Lesson lesson;
final bool swapDesc;
final void Function()? onTap;
@override
Widget build(BuildContext context) {
List<Widget> subtiles = [];
Color accent = Theme.of(context).colorScheme.secondary;
bool fill = false;
bool fillLeading = false;
String lessonIndexTrailing = "";
// Only put a trailing . if its a digit
if (RegExp(r'\d').hasMatch(lesson.lessonIndex)) lessonIndexTrailing = ".";
var now = DateTime.now();
if (lesson.start.isBefore(now) && lesson.end.isAfter(now) && lesson.status?.name != "Elmaradt") {
fillLeading = true;
}
if (lesson.substituteTeacher != "") {
fill = true;
accent = AppColors.of(context).yellow;
}
if (lesson.status?.name == "Elmaradt") {
fill = true;
accent = AppColors.of(context).red;
}
if (lesson.isEmpty) {
accent = AppColors.of(context).text.withOpacity(0.6);
}
if (!lesson.studentPresence) {
subtiles.add(LessonSubtile(
type: LessonSubtileType.absence,
title: "absence".i18n,
));
}
if (lesson.homeworkId != "") {
Homework homework = Provider.of<HomeworkProvider>(context, listen: false)
.homework
.firstWhere((h) => h.id == lesson.homeworkId, orElse: () => Homework.fromJson({}));
if (homework.id != "") {
subtiles.add(LessonSubtile(
type: LessonSubtileType.homework,
title: homework.content,
onPressed: () => HomeworkView.show(homework, context: context),
));
}
}
if (lesson.exam != "") {
Exam exam = Provider.of<ExamProvider>(context, listen: false).exams.firstWhere((t) => t.id == lesson.exam, orElse: () => Exam.fromJson({}));
if (exam.id != "") {
subtiles.add(LessonSubtile(
type: LessonSubtileType.exam,
title: exam.description != "" ? exam.description : exam.mode?.description ?? "exam".i18n,
onPressed: () => ExamView.show(exam, context: context),
));
}
}
String description = '';
String room = '';
final cleanDesc = lesson.description.specialChars().toLowerCase().replaceAll(lesson.subject.name.specialChars().toLowerCase(), '');
if (!swapDesc) {
if (cleanDesc != "") {
description = lesson.description;
}
// Changed lesson Description
if (lesson.isChanged) {
if (lesson.status?.name == "Elmaradt") {
description = 'cancelled'.i18n;
} else if (lesson.substituteTeacher != "") {
description = 'substitution'.i18n;
}
}
room = lesson.room.replaceAll("_", " ");
} else {
description = lesson.room.replaceAll("_", " ");
}
return Padding(
padding: const EdgeInsets.only(bottom: 2.0),
child: Material(
color: fill ? accent.withOpacity(.25) : Colors.transparent,
borderRadius: BorderRadius.circular(12.0),
child: Visibility(
visible: lesson.subject.id != '' || lesson.isEmpty,
replacement: Padding(
padding: const EdgeInsets.only(top: 6.0),
child: PanelTitle(title: Text(lesson.name)),
),
child: Padding(
padding: EdgeInsets.only(bottom: subtiles.isEmpty ? 0.0 : 12.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
minVerticalPadding: 12.0,
dense: true,
onTap: onTap,
// onLongPress: kDebugMode ? () => log(jsonEncode(lesson.json)) : null,
visualDensity: VisualDensity.compact,
contentPadding: const EdgeInsets.symmetric(horizontal: 4.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
title: Text(
!lesson.isEmpty ? lesson.subject.renamedTo ?? lesson.subject.name.capital() : "empty".i18n,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 15.5,
color: AppColors.of(context).text.withOpacity(!lesson.isEmpty ? 1.0 : 0.5),
fontStyle: lesson.subject.isRenamed ? FontStyle.italic : null),
),
subtitle: description != ""
? Text(
description,
style: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 14.0,
),
maxLines: 1,
softWrap: false,
overflow: TextOverflow.ellipsis,
)
: null,
minLeadingWidth: 34.0,
leading: AspectRatio(
aspectRatio: 1,
child: Center(
child: Stack(
children: [
Text(
lesson.lessonIndex + lessonIndexTrailing,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.w600,
color: accent,
),
),
// Current lesson indicator
Transform.translate(
offset: const Offset(-12.0, -2.0),
child: Container(
decoration: BoxDecoration(
color: fillLeading ? Theme.of(context).colorScheme.secondary.withOpacity(.3) : const Color(0x00000000),
borderRadius: BorderRadius.circular(12.0),
boxShadow: [
if (fillLeading)
BoxShadow(
color: Theme.of(context).colorScheme.secondary.withOpacity(.25),
blurRadius: 6.0,
)
],
),
margin: const EdgeInsets.symmetric(vertical: 4.0),
width: 4.0,
height: double.infinity,
),
)
],
),
),
),
trailing: !lesson.isEmpty
? Row(
mainAxisSize: MainAxisSize.min,
children: [
if (!swapDesc)
SizedBox(
width: 52.0,
child: Padding(
padding: const EdgeInsets.only(right: 6.0),
child: Text(
room,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: TextStyle(
fontWeight: FontWeight.w500,
color: AppColors.of(context).text.withOpacity(.75),
),
),
),
),
Stack(
alignment: Alignment.center,
children: [
// Fix alignment hack
const Opacity(opacity: 0, child: Text("EE:EE")),
Text(
"${DateFormat("H:mm").format(lesson.start)}\n${DateFormat("H:mm").format(lesson.end)}",
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.w500,
color: AppColors.of(context).text.withOpacity(.9),
),
),
],
),
],
)
: null,
),
// Homework & Exams
...subtiles,
],
),
),
),
),
);
}
}
enum LessonSubtileType { homework, exam, absence }
class LessonSubtile extends StatelessWidget {
const LessonSubtile({Key? key, this.onPressed, required this.title, required this.type}) : super(key: key);
final Function()? onPressed;
final String title;
final LessonSubtileType type;
@override
Widget build(BuildContext context) {
IconData icon;
Color iconColor = AppColors.of(context).text;
switch (type) {
case LessonSubtileType.absence:
icon = FeatherIcons.slash;
iconColor = AppColors.of(context).red;
break;
case LessonSubtileType.exam:
icon = FeatherIcons.file;
break;
case LessonSubtileType.homework:
icon = FeatherIcons.home;
break;
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: InkWell(
onTap: onPressed,
borderRadius: BorderRadius.circular(8.0),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
children: [
Center(
child: SizedBox(
width: 30.0,
child: Icon(icon, color: iconColor.withOpacity(.75), size: 20.0),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 20.0),
child: Text(
title.escapeHtml(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontWeight: FontWeight.w500, color: AppColors.of(context).text.withOpacity(.65)),
),
),
),
],
),
),
),
);
}
}

View File

@@ -1,33 +1,33 @@
import 'package:i18n_extension/i18n_extension.dart';
extension Localization on String {
static final _t = Translations.byLocale("hu_hu") +
{
"en_en": {
"empty": "Free period",
"cancelled": "Cancelled",
"substitution": "Substituted",
"absence": "You were absent on this lesson",
"exam": "Exam"
},
"hu_hu": {
"empty": "Lyukasóra",
"cancelled": "Elmarad",
"substitution": "Helyettesítés",
"absence": "Hiányoztál ezen az órán",
"exam": "Dolgozat"
},
"de_de": {
"empty": "Springstunde",
"cancelled": "Abgesagte",
"substitution": "Vertretene",
"absence": "Sie waren in dieser Lektion nicht anwesend",
"exam": "Prüfung"
}
};
String get i18n => localize(this, _t);
String fill(List<Object> params) => localizeFill(this, params);
String plural(int value) => localizePlural(value, this, _t);
String version(Object modifier) => localizeVersion(modifier, this, _t);
}
import 'package:i18n_extension/i18n_extension.dart';
extension Localization on String {
static final _t = Translations.byLocale("hu_hu") +
{
"en_en": {
"empty": "Free period",
"cancelled": "Cancelled",
"substitution": "Substituted",
"absence": "You were absent on this lesson",
"exam": "Exam"
},
"hu_hu": {
"empty": "Lyukasóra",
"cancelled": "Elmarad",
"substitution": "Helyettesítés",
"absence": "Hiányoztál ezen az órán",
"exam": "Dolgozat"
},
"de_de": {
"empty": "Springstunde",
"cancelled": "Abgesagte",
"substitution": "Vertretene",
"absence": "Sie waren in dieser Lektion nicht anwesend",
"exam": "Prüfung"
}
};
String get i18n => localize(this, _t);
String fill(List<Object> params) => localizeFill(this, params);
String plural(int value) => localizePlural(value, this, _t);
String version(Object modifier) => localizeVersion(modifier, this, _t);
}

View File

@@ -1,121 +1,121 @@
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/theme/colors/colors.dart';
import 'package:filcnaplo/utils/color.dart';
import 'package:filcnaplo/utils/format.dart';
import 'package:filcnaplo_kreta_api/models/message.dart';
import 'package:filcnaplo_mobile_ui/common/profile_image/profile_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart';
class MessageTile extends StatelessWidget {
const MessageTile(
this.message, {
Key? key,
this.messages,
this.padding,
this.onTap,
this.censored = false,
}) : super(key: key);
final Message message;
final List<Message>? messages;
final EdgeInsetsGeometry? padding;
final Function()? onTap;
final bool censored;
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
child: Padding(
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
child: ListTile(
onTap: onTap,
visualDensity: VisualDensity.compact,
contentPadding: const EdgeInsets.only(left: 8.0, right: 4.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)),
leading: !Provider.of<SettingsProvider>(context, listen: false).presentationMode
? ProfileImage(
name: message.author,
radius: 22.0,
backgroundColor: ColorUtils.stringToColor(message.author),
censored: censored,
)
: ProfileImage(
name: "Béla",
radius: 22.0,
backgroundColor: Theme.of(context).colorScheme.secondary,
censored: censored,
),
title: censored
? Wrap(
children: [
Container(
width: 105,
height: 15,
decoration: BoxDecoration(
color: AppColors.of(context).text.withOpacity(.85),
borderRadius: BorderRadius.circular(8.0),
),
),
],
)
: Row(
children: [
Expanded(
child: Text(
!Provider.of<SettingsProvider>(context, listen: false).presentationMode ? message.author : "Béla",
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 15.5),
),
),
if (message.attachments.isNotEmpty) const Icon(FeatherIcons.paperclip, size: 16.0)
],
),
subtitle: censored
? Wrap(
children: [
Container(
width: 150,
height: 10,
decoration: BoxDecoration(
color: AppColors.of(context).text.withOpacity(.45),
borderRadius: BorderRadius.circular(8.0),
),
),
],
)
: Text(
message.subject,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14.0),
),
trailing: censored
? Wrap(
children: [
Container(
width: 35,
height: 15,
decoration: BoxDecoration(
color: AppColors.of(context).text.withOpacity(.45),
borderRadius: BorderRadius.circular(8.0),
),
),
],
)
: Text(
message.date.format(context),
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 14.0,
color: AppColors.of(context).text.withOpacity(.75),
),
),
),
),
);
}
}
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/theme/colors/colors.dart';
import 'package:filcnaplo/utils/color.dart';
import 'package:filcnaplo/utils/format.dart';
import 'package:filcnaplo_kreta_api/models/message.dart';
import 'package:filcnaplo_mobile_ui/common/profile_image/profile_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart';
class MessageTile extends StatelessWidget {
const MessageTile(
this.message, {
Key? key,
this.messages,
this.padding,
this.onTap,
this.censored = false,
}) : super(key: key);
final Message message;
final List<Message>? messages;
final EdgeInsetsGeometry? padding;
final Function()? onTap;
final bool censored;
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
child: Padding(
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
child: ListTile(
onTap: onTap,
visualDensity: VisualDensity.compact,
contentPadding: const EdgeInsets.only(left: 8.0, right: 4.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)),
leading: !Provider.of<SettingsProvider>(context, listen: false).presentationMode
? ProfileImage(
name: message.author,
radius: 22.0,
backgroundColor: ColorUtils.stringToColor(message.author),
censored: censored,
)
: ProfileImage(
name: "Béla",
radius: 22.0,
backgroundColor: Theme.of(context).colorScheme.secondary,
censored: censored,
),
title: censored
? Wrap(
children: [
Container(
width: 105,
height: 15,
decoration: BoxDecoration(
color: AppColors.of(context).text.withOpacity(.85),
borderRadius: BorderRadius.circular(8.0),
),
),
],
)
: Row(
children: [
Expanded(
child: Text(
!Provider.of<SettingsProvider>(context, listen: false).presentationMode ? message.author : "Béla",
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 15.5),
),
),
if (message.attachments.isNotEmpty) const Icon(FeatherIcons.paperclip, size: 16.0)
],
),
subtitle: censored
? Wrap(
children: [
Container(
width: 150,
height: 10,
decoration: BoxDecoration(
color: AppColors.of(context).text.withOpacity(.45),
borderRadius: BorderRadius.circular(8.0),
),
),
],
)
: Text(
message.subject,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14.0),
),
trailing: censored
? Wrap(
children: [
Container(
width: 35,
height: 15,
decoration: BoxDecoration(
color: AppColors.of(context).text.withOpacity(.45),
borderRadius: BorderRadius.circular(8.0),
),
),
],
)
: Text(
message.date.format(context),
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 14.0,
color: AppColors.of(context).text.withOpacity(.75),
),
),
),
),
);
}
}