Merge branch 'dev' of github.com:refilc/naplo into dev

This commit is contained in:
Kima
2023-08-27 21:38:35 +02:00
54 changed files with 1800 additions and 591 deletions

View File

@@ -0,0 +1,41 @@
import 'package:filcnaplo/theme/colors/colors.dart';
import 'package:flutter/material.dart';
class BetaChip extends StatelessWidget {
const BetaChip({Key? key, this.disabled = false}) : super(key: key);
final bool disabled;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 25,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
child: Padding(
padding: const EdgeInsets.only(left: 8, right: 8),
child: Center(
child: Text(
"BETA",
softWrap: true,
style: TextStyle(
fontSize: 10,
color: disabled
? AppColors.of(context).text.withOpacity(.5)
: Colors.white,
fontWeight: FontWeight.w600,
overflow: TextOverflow.ellipsis,
),
),
),
),
decoration: BoxDecoration(
color: !disabled
? Theme.of(context).colorScheme.secondary
: AppColors.of(context).text.withOpacity(.25),
borderRadius: BorderRadius.circular(40),
),
),
);
}
}

View File

@@ -9,7 +9,9 @@ import 'package:provider/provider.dart';
import 'absence_tile.i18n.dart';
class AbsenceTile extends StatelessWidget {
const AbsenceTile(this.absence, {Key? key, this.onTap, this.elevation = 0.0, this.padding}) : super(key: key);
const AbsenceTile(this.absence,
{Key? key, this.onTap, this.elevation = 0.0, this.padding})
: super(key: key);
final Absence absence;
final void Function()? onTap;
@@ -37,39 +39,61 @@ class AbsenceTile extends StatelessWidget {
child: Material(
type: MaterialType.transparency,
child: Padding(
padding: padding ?? (group ? EdgeInsets.zero : const EdgeInsets.symmetric(horizontal: 8.0)),
padding: padding ??
(group
? EdgeInsets.zero
: const EdgeInsets.symmetric(
horizontal: 8.0,
)),
child: ListTile(
onTap: onTap,
visualDensity: VisualDensity.compact,
dense: group,
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(!group ? 14.0 : 12.0)),
contentPadding: const EdgeInsets.only(
left: 14.0, right: 12.0, top: 2.0, bottom: 2.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(!group ? 14.0 : 12.0)),
leading: Container(
width: 44.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: !group ? color.withOpacity(.25) : null,
),
child: Center(child: Icon(justificationIcon(absence.state), color: color)),
child: Center(
child: Icon(justificationIcon(absence.state), color: color)),
),
title: !group
? Text.rich(TextSpan(
text: "${absence.delay == 0 ? "" : absence.delay}",
style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 15.5),
style: const TextStyle(
fontWeight: FontWeight.w700, fontSize: 15.5),
children: [
TextSpan(
text: absence.delay == 0
? justificationName(absence.state).fill(["absence".i18n]).capital()
: 'minute'.plural(absence.delay) + justificationName(absence.state).fill(["delay".i18n]),
? justificationName(absence.state)
.fill(["absence".i18n]).capital()
: 'minute'.plural(absence.delay) +
justificationName(absence.state)
.fill(["delay".i18n]),
style: const TextStyle(fontWeight: FontWeight.w600),
),
],
))
: Text(
(absence.lessonIndex != null ? "${absence.lessonIndex}. " : "") + (absence.subject.renamedTo ?? absence.subject.name.capital()),
(absence.lessonIndex != null
? "${absence.lessonIndex}. "
: "") +
(absence.subject.renamedTo ??
absence.subject.name.capital()),
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 14.0, fontStyle: absence.subject.isRenamed && settingsProvider.renamedSubjectsItalics ? FontStyle.italic : null),
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 14.0,
fontStyle: absence.subject.isRenamed &&
settingsProvider.renamedSubjectsItalics
? FontStyle.italic
: null),
),
subtitle: !group
? Text(
@@ -77,7 +101,12 @@ class AbsenceTile extends StatelessWidget {
maxLines: 2,
overflow: TextOverflow.ellipsis,
// DateFormat("MM. dd. (EEEEE)", I18n.of(context).locale.toString()).format(absence.date),
style: TextStyle(fontWeight: FontWeight.w500, fontStyle: absence.subject.isRenamed && settingsProvider.renamedSubjectsItalics ? FontStyle.italic : null),
style: TextStyle(
fontWeight: FontWeight.w500,
fontStyle: absence.subject.isRenamed &&
settingsProvider.renamedSubjectsItalics
? FontStyle.italic
: null),
)
: null,
),
@@ -97,7 +126,8 @@ class AbsenceTile extends StatelessWidget {
}
}
static Color justificationColor(Justification state, {required BuildContext context}) {
static Color justificationColor(Justification state,
{required BuildContext context}) {
switch (state) {
case Justification.excused:
return AppColors.of(context).green;

View File

@@ -17,19 +17,23 @@ import 'package:provider/provider.dart';
import 'absence_view.i18n.dart';
class AbsenceView extends StatelessWidget {
const AbsenceView(this.absence, {Key? key, this.outsideContext, this.viewable = false}) : super(key: key);
const AbsenceView(this.absence,
{Key? key, this.outsideContext, this.viewable = false})
: super(key: key);
final Absence absence;
final BuildContext? outsideContext;
final bool viewable;
static show(Absence absence, {required BuildContext context}) {
showBottomCard(context: context, child: AbsenceView(absence, outsideContext: context));
showBottomCard(
context: context, child: AbsenceView(absence, outsideContext: context));
}
@override
Widget build(BuildContext context) {
Color color = AbsenceTile.justificationColor(absence.state, context: context);
Color color =
AbsenceTile.justificationColor(absence.state, context: context);
SettingsProvider settingsProvider = Provider.of<SettingsProvider>(context);
return Padding(
@@ -41,7 +45,8 @@ class AbsenceView extends StatelessWidget {
ListTile(
visualDensity: VisualDensity.compact,
contentPadding: const EdgeInsets.only(left: 16.0, right: 12.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0)),
leading: Container(
width: 44.0,
height: 44.0,
@@ -60,10 +65,18 @@ class AbsenceView extends StatelessWidget {
absence.subject.renamedTo ?? absence.subject.name.capital(),
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontWeight: FontWeight.w700, fontStyle: absence.subject.isRenamed && settingsProvider.renamedSubjectsItalics ? FontStyle.italic : null),
style: TextStyle(
fontWeight: FontWeight.w700,
fontStyle: absence.subject.isRenamed &&
settingsProvider.renamedSubjectsItalics
? FontStyle.italic
: null),
),
subtitle: Text(
absence.teacher,
(absence.teacher.isRenamed
? absence.teacher.renamedTo
: absence.teacher.name) ??
'',
// DateFormat("MM. dd. (EEEEE)", I18n.of(context).locale.toString()).format(absence.date),
style: const TextStyle(fontWeight: FontWeight.w500),
),
@@ -77,12 +90,15 @@ class AbsenceView extends StatelessWidget {
if (absence.delay > 0)
Detail(
title: "delay".i18n,
description: absence.delay.toString() + " " + "minutes".i18n.plural(absence.delay),
description: absence.delay.toString() +
" " +
"minutes".i18n.plural(absence.delay),
),
if (absence.lessonIndex != null)
Detail(
title: "Lesson".i18n,
description: "${absence.lessonIndex}. (${absence.lessonStart.format(context, timeOnly: true)}"
description:
"${absence.lessonIndex}. (${absence.lessonStart.format(context, timeOnly: true)}"
" - "
"${absence.lessonEnd.format(context, timeOnly: true)})",
),
@@ -91,13 +107,19 @@ class AbsenceView extends StatelessWidget {
title: "Excuse".i18n,
description: absence.justification?.description ?? "",
),
if (absence.mode != null) Detail(title: "Mode".i18n, description: absence.mode?.description ?? ""),
Detail(title: "Submit date".i18n, description: absence.submitDate.format(context)),
if (absence.mode != null)
Detail(
title: "Mode".i18n,
description: absence.mode?.description ?? ""),
Detail(
title: "Submit date".i18n,
description: absence.submitDate.format(context)),
// Show in timetable
if (!viewable)
Padding(
padding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 6.0, top: 12.0),
padding: const EdgeInsets.only(
left: 16.0, right: 16.0, bottom: 6.0, top: 12.0),
child: PanelActionButton(
leading: const Icon(FeatherIcons.calendar),
title: Text(
@@ -109,12 +131,15 @@ class AbsenceView extends StatelessWidget {
Navigator.of(context).pop();
if (outsideContext != null) {
ReverseSearch.getLessonByAbsence(absence, context).then((lesson) {
ReverseSearch.getLessonByAbsence(absence, context)
.then((lesson) {
if (lesson != null) {
TimetablePage.jump(outsideContext!, lesson: lesson);
} else {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("Cannot find lesson".i18n, style: const TextStyle(color: Colors.white)),
ScaffoldMessenger.of(context)
.showSnackBar(CustomSnackBar(
content: Text("Cannot find lesson".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red,
context: context,
));

View File

@@ -5,6 +5,8 @@ import 'package:filcnaplo_mobile_ui/common/widgets/absence_group/absence_group_c
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_tile.dart';
import 'package:filcnaplo/utils/format.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:rounded_expansion_tile/rounded_expansion_tile.dart';
import 'absence_group_tile.i18n.dart';
import 'package:rounded_expansion_tile/rounded_expansion_tile.dart';
@@ -31,13 +33,17 @@ class AbsenceGroupTile extends StatelessWidget {
child: Material(
type: MaterialType.transparency,
child: Padding(
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
padding: padding ??
const EdgeInsets.symmetric(horizontal: 0.0, vertical: 0.0),
child: AbsenceGroupContainer(
child: RoundedExpansionTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
tilePadding: const EdgeInsets.symmetric(horizontal: 8.0),
backgroundColor: Colors.transparent,
childrenPadding: const EdgeInsets.symmetric(horizontal: 8.0),
tileColor: Colors.transparent,
duration: const Duration(milliseconds: 250),
trailingDuration: 0.5,
trailing: const Icon(FeatherIcons.chevronDown),
leading: Container(
width: 44.0,
height: 44.0,

View File

@@ -12,7 +12,8 @@ class ExamView extends StatelessWidget {
final Exam exam;
static show(Exam exam, {required BuildContext context}) => showBottomCard(context: context, child: ExamView(exam));
static show(Exam exam, {required BuildContext context}) =>
showBottomCard(context: context, child: ExamView(exam));
@override
Widget build(BuildContext context) {
@@ -27,7 +28,8 @@ class ExamView extends StatelessWidget {
leading: Padding(
padding: const EdgeInsets.only(left: 6.0),
child: Icon(
SubjectIcon.resolveVariant(subjectName: exam.subjectName, context: context),
SubjectIcon.resolveVariant(
subjectName: exam.subjectName, context: context),
size: 36.0,
color: AppColors.of(context).text.withOpacity(.75),
),
@@ -39,7 +41,10 @@ class ExamView extends StatelessWidget {
style: const TextStyle(fontWeight: FontWeight.w600),
),
subtitle: Text(
exam.teacher,
(exam.teacher.isRenamed
? exam.teacher.renamedTo
: exam.teacher.name) ??
'',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w500),
@@ -51,9 +56,14 @@ class ExamView extends StatelessWidget {
),
// Details
if (exam.writeDate.year != 0) Detail(title: "date".i18n, description: exam.writeDate.format(context)),
if (exam.description != "") Detail(title: "description".i18n, description: exam.description),
if (exam.mode != null) Detail(title: "mode".i18n, description: exam.mode!.description),
if (exam.writeDate.year != 0)
Detail(
title: "date".i18n,
description: exam.writeDate.format(context)),
if (exam.description != "")
Detail(title: "description".i18n, description: exam.description),
if (exam.mode != null)
Detail(title: "mode".i18n, description: exam.mode!.description),
],
),
);

View File

@@ -11,7 +11,8 @@ import 'grade_view.i18n.dart';
class GradeView extends StatelessWidget {
const GradeView(this.grade, {Key? key}) : super(key: key);
static show(Grade grade, {required BuildContext context}) => showBottomCard(context: context, child: GradeView(grade));
static show(Grade grade, {required BuildContext context}) =>
showBottomCard(context: context, child: GradeView(grade));
final Grade grade;
@@ -30,10 +31,21 @@ class GradeView extends StatelessWidget {
grade.subject.renamedTo ?? grade.subject.name.capital(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontWeight: FontWeight.w600, fontStyle: grade.subject.isRenamed && settingsProvider.renamedSubjectsItalics ? FontStyle.italic : null),
style: TextStyle(
fontWeight: FontWeight.w600,
fontStyle: grade.subject.isRenamed &&
settingsProvider.renamedSubjectsItalics
? FontStyle.italic
: null),
),
subtitle: Text(
!Provider.of<SettingsProvider>(context, listen: false).presentationMode ? grade.teacher : "Tanár",
!Provider.of<SettingsProvider>(context, listen: false)
.presentationMode
? (grade.teacher.isRenamed
? grade.teacher.renamedTo
: grade.teacher.name) ??
''
: "Tanár",
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w500),
@@ -49,13 +61,20 @@ class GradeView extends StatelessWidget {
title: "value".i18n,
description: "${grade.value.valueName} " + percentText(),
),
if (grade.description != "") Detail(title: "description".i18n, description: grade.description),
if (grade.mode.description != "") Detail(title: "mode".i18n, description: grade.mode.description),
if (grade.writeDate.year != 0) Detail(title: "date".i18n, description: grade.writeDate.format(context)),
if (grade.description != "")
Detail(title: "description".i18n, description: grade.description),
if (grade.mode.description != "")
Detail(title: "mode".i18n, description: grade.mode.description),
if (grade.writeDate.year != 0)
Detail(
title: "date".i18n,
description: grade.writeDate.format(context)),
],
),
);
}
String percentText() => grade.value.weight != 100 && grade.value.weight > 0 ? "${grade.value.weight}%" : "";
String percentText() => grade.value.weight != 100 && grade.value.weight > 0
? "${grade.value.weight}%"
: "";
}

View File

@@ -51,10 +51,18 @@ class HomeworkView extends StatelessWidget {
homework.subject.renamedTo ?? homework.subject.name.capital(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontWeight: FontWeight.w600, fontStyle: homework.subject.isRenamed && settingsProvider.renamedSubjectsItalics ? FontStyle.italic : null),
style: TextStyle(
fontWeight: FontWeight.w600,
fontStyle: homework.subject.isRenamed &&
settingsProvider.renamedSubjectsItalics
? FontStyle.italic
: null),
),
subtitle: Text(
homework.teacher,
(homework.teacher.isRenamed
? homework.teacher.renamedTo
: homework.teacher.name) ??
'',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w500),

View File

@@ -54,10 +54,23 @@ class LessonView extends StatelessWidget {
lesson.subject.renamedTo ?? lesson.subject.name.capital(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontWeight: FontWeight.w600, fontStyle: lesson.subject.isRenamed && settingsProvider.renamedSubjectsItalics ? FontStyle.italic : null),
style: TextStyle(
fontWeight: FontWeight.w600,
fontStyle: lesson.subject.isRenamed &&
settingsProvider.renamedSubjectsItalics
? FontStyle.italic
: null),
),
subtitle: Text(
lesson.substituteTeacher == "" ? lesson.teacher : lesson.substituteTeacher,
((lesson.substituteTeacher == null ||
lesson.substituteTeacher!.name == "")
? (lesson.teacher.isRenamed
? lesson.teacher.renamedTo
: lesson.teacher.name)
: (lesson.substituteTeacher!.isRenamed
? lesson.substituteTeacher!.renamedTo
: lesson.substituteTeacher!.name)) ??
'',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w500),
@@ -69,10 +82,18 @@ class LessonView extends StatelessWidget {
),
// Details
if (lesson.room != "") Detail(title: "Room".i18n, description: lesson.room.replaceAll("_", " ")),
if (lesson.description != "") Detail(title: "Description".i18n, description: lesson.description),
if (lesson.lessonYearIndex != null) Detail(title: "Lesson Number".i18n, description: "${lesson.lessonYearIndex}."),
if (lesson.groupName != "") Detail(title: "Group".i18n, description: lesson.groupName),
if (lesson.room != "")
Detail(
title: "Room".i18n,
description: lesson.room.replaceAll("_", " ")),
if (lesson.description != "")
Detail(title: "Description".i18n, description: lesson.description),
if (lesson.lessonYearIndex != null)
Detail(
title: "Lesson Number".i18n,
description: "${lesson.lessonYearIndex}."),
if (lesson.groupName != "")
Detail(title: "Group".i18n, description: lesson.groupName),
],
),
);

View File

@@ -4,7 +4,8 @@ import 'package:filcnaplo_mobile_ui/common/profile_image/profile_image.dart';
import 'package:flutter/material.dart';
class NoteTile extends StatelessWidget {
const NoteTile(this.note, {Key? key, this.onTap, this.padding}) : super(key: key);
const NoteTile(this.note, {Key? key, this.onTap, this.padding})
: super(key: key);
final Note note;
final void Function()? onTap;
@@ -20,11 +21,20 @@ class NoteTile extends StatelessWidget {
visualDensity: VisualDensity.compact,
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0),
onTap: onTap,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)),
leading: ProfileImage(
name: note.teacher,
name: (note.teacher.isRenamed
? note.teacher.renamedTo
: note.teacher.name) ??
'',
radius: 22.0,
backgroundColor: ColorUtils.stringToColor(note.teacher),
backgroundColor: ColorUtils.stringToColor(
(note.teacher.isRenamed
? note.teacher.renamedTo
: note.teacher.name) ??
'',
),
),
title: Text(
note.title,

View File

@@ -12,7 +12,8 @@ class NoteView extends StatelessWidget {
final Note note;
static void show(Note note, {required BuildContext context}) => showSlidingBottomSheet(context: context, child: NoteView(note));
static void show(Note note, {required BuildContext context}) =>
showSlidingBottomSheet(context: context, child: NoteView(note));
@override
Widget build(BuildContext context) {
@@ -25,9 +26,17 @@ class NoteView extends StatelessWidget {
// Header
ListTile(
leading: ProfileImage(
name: note.teacher,
name: (note.teacher.isRenamed
? note.teacher.renamedTo
: note.teacher.name) ??
'',
radius: 22.0,
backgroundColor: ColorUtils.stringToColor(note.teacher),
backgroundColor: ColorUtils.stringToColor(
(note.teacher.isRenamed
? note.teacher.renamedTo
: note.teacher.name) ??
'',
),
),
title: Text(
note.title,
@@ -36,7 +45,10 @@ class NoteView extends StatelessWidget {
style: const TextStyle(fontWeight: FontWeight.w600),
),
subtitle: Text(
note.teacher,
(note.teacher.isRenamed
? note.teacher.renamedTo
: note.teacher.name) ??
'',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w500),

View File

@@ -19,14 +19,18 @@ import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_view.i18n.dar
import 'package:provider/provider.dart';
class AbsenceSubjectView extends StatelessWidget {
const AbsenceSubjectView(this.subject, {Key? key, this.absences = const []}) : super(key: key);
const AbsenceSubjectView(this.subject, {Key? key, this.absences = const []})
: super(key: key);
final Subject subject;
final List<Absence> absences;
static void show(Subject subject, List<Absence> absences, {required BuildContext context}) {
static void show(Subject subject, List<Absence> absences,
{required BuildContext context}) {
Navigator.of(context, rootNavigator: true)
.push<Absence>(CupertinoPageRoute(builder: (context) => AbsenceSubjectView(subject, absences: absences)))
.push<Absence>(CupertinoPageRoute(
builder: (context) =>
AbsenceSubjectView(subject, absences: absences)))
.then((value) {
if (value == null) return;
@@ -36,7 +40,8 @@ class AbsenceSubjectView extends StatelessWidget {
TimetablePage.jump(context, lesson: lesson);
} else {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("Cannot find lesson".i18n, style: const TextStyle(color: Colors.white)),
content: Text("Cannot find lesson".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red,
context: context,
));
@@ -54,7 +59,10 @@ class AbsenceSubjectView extends StatelessWidget {
date: a.date,
))
.toList();
List<Widget> absenceTiles = sortDateWidgets(context, dateWidgets: dateWidgets, padding: EdgeInsets.zero, hasShadow: true);
List<Widget> absenceTiles = sortDateWidgets(context,
dateWidgets: dateWidgets,
padding: const EdgeInsets.symmetric(vertical: 6.0),
hasShadow: true);
SettingsProvider settingsProvider = Provider.of<SettingsProvider>(context);

View File

@@ -3,6 +3,7 @@ import 'dart:math';
import 'package:filcnaplo_kreta_api/models/category.dart';
import 'package:filcnaplo_kreta_api/models/grade.dart';
import 'package:filcnaplo_kreta_api/models/subject.dart';
import 'package:filcnaplo_kreta_api/models/teacher.dart';
import 'package:filcnaplo_mobile_ui/common/custom_snack_bar.dart';
import 'package:filcnaplo_mobile_ui/common/material_action_button.dart';
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
@@ -42,7 +43,8 @@ class _GradeCalculatorState extends State<GradeCalculator> {
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"Grade Calculator".i18n,
style: const TextStyle(fontSize: 20.0, fontWeight: FontWeight.w600),
style:
const TextStyle(fontSize: 20.0, fontWeight: FontWeight.w600),
),
),
@@ -63,7 +65,9 @@ class _GradeCalculatorState extends State<GradeCalculator> {
Container(
width: 80.0,
padding: const EdgeInsets.only(right: 12.0),
child: Center(child: GradeValueWidget(GradeValue(newValue.toInt(), "", "", 0))),
child: Center(
child: GradeValueWidget(
GradeValue(newValue.toInt(), "", "", 0))),
),
]),
@@ -90,7 +94,8 @@ class _GradeCalculatorState extends State<GradeCalculator> {
child: Center(
child: TextField(
controller: _weightController,
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 22.0),
style: const TextStyle(
fontWeight: FontWeight.w600, fontSize: 22.0),
autocorrect: false,
textAlign: TextAlign.right,
keyboardType: TextInputType.number,
@@ -122,7 +127,8 @@ class _GradeCalculatorState extends State<GradeCalculator> {
child: Text("Add Grade".i18n),
onPressed: () {
if (calculatorProvider.ghosts.length >= 50) {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(content: Text("limit_reached".i18n), context: context));
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("limit_reached".i18n), context: context));
return;
}
@@ -133,7 +139,11 @@ class _GradeCalculatorState extends State<GradeCalculator> {
grades.sort((a, b) => -a.writeDate.compareTo(b.writeDate));
date = grades.first.date.add(const Duration(days: 7));
} else {
List<Grade> grades = calculatorProvider.grades.where((e) => e.type == GradeType.midYear && e.subject == widget.subject).toList();
List<Grade> grades = calculatorProvider.grades
.where((e) =>
e.type == GradeType.midYear &&
e.subject == widget.subject)
.toList();
grades.sort((a, b) => -a.writeDate.compareTo(b.writeDate));
date = grades.first.date;
}
@@ -143,8 +153,9 @@ class _GradeCalculatorState extends State<GradeCalculator> {
date: date,
writeDate: date,
description: "Ghost Grade".i18n,
value: GradeValue(newValue.toInt(), "", "", newWeight.toInt()),
teacher: "Ghost",
value:
GradeValue(newValue.toInt(), "", "", newWeight.toInt()),
teacher: Teacher.fromString("Ghost"),
type: GradeType.ghost,
form: "",
subject: widget.subject,

View File

@@ -21,6 +21,10 @@ import 'package:filcnaplo_mobile_ui/pages/grades/calculator/grade_calculator_pro
import 'package:filcnaplo_mobile_ui/pages/grades/grades_count.dart';
import 'package:filcnaplo_mobile_ui/pages/grades/graph.dart';
import 'package:filcnaplo_mobile_ui/pages/grades/subject_grades_container.dart';
import 'package:filcnaplo_premium/ui/mobile/goal_planner/test.dart';
import 'package:filcnaplo_premium/models/premium_scopes.dart';
import 'package:filcnaplo_premium/providers/premium_provider.dart';
import 'package:filcnaplo_premium/ui/mobile/premium/upsell.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
@@ -215,25 +219,25 @@ class _GradeSubjectViewState extends State<GradeSubjectView> {
gradeCalc(context);
},
),
// FloatingActionButton.small(
// child: const Icon(FeatherIcons.flag, size: 20.0),
// backgroundColor: Theme.of(context).colorScheme.secondary,
// onPressed: () {
// if (!Provider.of<PremiumProvider>(context, listen: false)
// .hasScope(PremiumScopes.goalPlanner)) {
// PremiumLockedFeatureUpsell.show(
// context: context, feature: PremiumFeature.goalplanner);
// return;
// }
FloatingActionButton.small(
child: const Icon(FeatherIcons.flag, size: 20.0),
backgroundColor: Theme.of(context).colorScheme.secondary,
onPressed: () {
if (!Provider.of<PremiumProvider>(context, listen: false)
.hasScope(PremiumScopes.goalPlanner)) {
PremiumLockedFeatureUpsell.show(
context: context, feature: PremiumFeature.goalplanner);
return;
}
// ScaffoldMessenger.of(context).showSnackBar(
// const SnackBar(content: Text("Hamarosan...")));
// ScaffoldMessenger.of(context).showSnackBar(
// const SnackBar(content: Text("Hamarosan...")));
// Navigator.of(context).push(CupertinoPageRoute(
// builder: (context) => PremiumGoalplannerNewGoalScreen(
// subject: widget.subject)));
// },
// ),
Navigator.of(context).push(CupertinoPageRoute(
builder: (context) =>
GoalPlannerTest(subject: widget.subject)));
},
),
],
),
),
@@ -263,7 +267,8 @@ class _GradeSubjectViewState extends State<GradeSubjectView> {
subject: widget.subject, context: context),
scrollController: _scrollController,
title: widget.subject.renamedTo ?? widget.subject.name.capital(),
italic: settingsProvider.renamedSubjectsItalics && widget.subject.isRenamed,
italic: settingsProvider.renamedSubjectsItalics &&
widget.subject.isRenamed,
child: SubjectGradesContainer(
child: CupertinoScrollbar(
child: ListView.builder(

View File

@@ -356,14 +356,14 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
// confetti 🎊
if (_confettiController != null)
Align(
alignment: Alignment.bottomCenter,
alignment: Alignment.topCenter,
child: ConfettiWidget(
confettiController: _confettiController!,
blastDirection: -pi / 2,
emissionFrequency: 0.01,
numberOfParticles: 80,
maxBlastForce: 100,
minBlastForce: 90,
blastDirectionality: BlastDirectionality.explosive,
emissionFrequency: 0.02,
numberOfParticles: 120,
maxBlastForce: 20,
minBlastForce: 10,
gravity: 0.3,
minimumSize: const Size(5, 5),
maximumSize: const Size(20, 20),

View File

@@ -139,7 +139,6 @@ class NavigationScreenState extends State<NavigationScreen>
void initState() {
super.initState();
HomeWidget.setAppGroupId('hu.refilc.naplo.group');
_checkForWidgetLaunch();
@@ -199,9 +198,9 @@ class NavigationScreenState extends State<NavigationScreen>
// Show news
WidgetsBinding.instance.addPostFrameCallback((_) {
if (newsProvider.show) {
newsProvider.lock();
NewsView.show(newsProvider.news[newsProvider.state], context: context)
NewsView.show(newsProvider.news[0], context: context)
.then((value) => newsProvider.release());
newsProvider.lock();
}
});

View File

@@ -31,23 +31,30 @@ class NewsView extends StatelessWidget {
physics: const BouncingScrollPhysics(),
children: [
Padding(
padding: const EdgeInsets.only(left: 8.0, right: 6.0, top: 14.0, bottom: 8.0),
padding: const EdgeInsets.only(
left: 8.0, right: 6.0, top: 14.0, bottom: 8.0),
child: Text(
news.title,
maxLines: 3,
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 18.0),
style: const TextStyle(
fontWeight: FontWeight.w600, fontSize: 18.0),
),
),
SelectableLinkify(
text: news.content.escapeHtml(),
options: const LinkifyOptions(looseUrl: true, removeWww: true),
options:
const LinkifyOptions(looseUrl: true, removeWww: true),
onOpen: (link) {
launch(
link.url,
customTabsOption: CustomTabsOption(showPageTitle: true, toolbarColor: Theme.of(context).scaffoldBackgroundColor),
customTabsOption: CustomTabsOption(
showPageTitle: true,
toolbarColor:
Theme.of(context).scaffoldBackgroundColor),
);
},
style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14.0),
style: const TextStyle(
fontWeight: FontWeight.w500, fontSize: 14.0),
),
],
),
@@ -59,10 +66,15 @@ class NewsView extends StatelessWidget {
children: [
if (news.link != "")
DialogButton(
label: news.openLabel != "" ? news.openLabel : "open".i18n.toUpperCase(),
label: news.openLabel != ""
? news.openLabel
: "open".i18n.toUpperCase(),
onTap: () => launch(
news.link,
customTabsOption: CustomTabsOption(showPageTitle: true, toolbarColor: Theme.of(context).scaffoldBackgroundColor),
customTabsOption: CustomTabsOption(
showPageTitle: true,
toolbarColor:
Theme.of(context).scaffoldBackgroundColor),
),
),
DialogButton(
@@ -78,12 +90,15 @@ class NewsView extends StatelessWidget {
);
}
static Future<T?> show<T>(News news, {required BuildContext context, bool force = false}) {
static Future<T?> show<T>(News news,
{required BuildContext context, bool force = false}) {
if (news.title == "") return Future<T?>.value(null);
bool popup = news.platform == '' || force;
bool popup = news.platform == 'all' || force;
if (Provider.of<SettingsProvider>(context, listen: false).newsEnabled || news.emergency || force) {
if (Provider.of<SettingsProvider>(context, listen: false).newsEnabled ||
news.emergency ||
force) {
switch (news.platform.trim().toLowerCase()) {
case "android":
if (Platform.isAndroid) popup = true;
@@ -100,6 +115,9 @@ class NewsView extends StatelessWidget {
case "macos":
if (Platform.isMacOS) popup = true;
break;
case "all":
popup = true;
break;
default:
popup = true;
}
@@ -108,7 +126,10 @@ class NewsView extends StatelessWidget {
}
if (popup) {
return showDialog<T?>(context: context, builder: (context) => NewsView(news), barrierDismissible: true);
return showDialog<T?>(
context: context,
builder: (context) => NewsView(news),
barrierDismissible: true);
} else {
return Future<T?>.value(null);
}

View File

@@ -28,27 +28,22 @@ class PrivacyView extends StatelessWidget {
),
SelectableLinkify(
text: """
A reFilc egy kliensalkalmazás, segítségével az e-Kréta rendszeréből letöltheted és felhasználóbarát módon megjelenítheted az adataidat.
Tanulmányi adataid csak közvetlenül az alkalmazás és a Kréta-szerverek között közlekednek, titkosított kapcsolaton keresztül.
A reFilc (továbbiakban alkalmazás) egy mobilos, asztali és webes kliensalkalmazás, segítségével az e-Kréta rendszeréből letöltheted és felhasználóbarát módon megjelenítheted az adataidat. Tanulmányi adataid csak közvetlenül az alkalmazás és a Kréta-szerverek között közlekednek, titkosított kapcsolaton keresztül.
A reFilc fejlesztői és üzemeltetői a tanulmányi adataidat semmilyen célból nem másolják, nem tárolják és harmadik félnek nem továbbítják. Ezeket így az e-Kréta Informatikai Zrt. kezeli, az ő tájékoztatójukat itt találod: https://tudasbazis.ekreta.hu/pages/viewpage.action?pageId=4065038.
Azok törlésével vagy módosítával kapcsolatban keresd az osztályfőnöködet vagy az iskolád rendszergazdáját.
A reFilc fejlesztői és/vagy üzemeltetői, valamint az alkalmazás a tanulmányi és személyes adataidat semmilyen célból és semmilyen körülmények között nem másolják, nem tárolják és harmadik félnek nem továbbítják. Ezeket így az Educational Development Informatikai Zrt. kezeli, az Ő adatkezeléssel kapcsolatos tájékoztatójukat itt találod: https://tudasbazis.ekreta.hu/pages/viewpage.action?pageId=4065038
Az alkalmazás névtelen használati statisztikákat gyűjt, ezek alapján tudjuk meghatározni a felhasználók és a telepítések számát. Ezt a beállításokban kikapcsolhatod.
Kérünk, hogy ha csak teheted, hagyd ezt a funkciót bekapcsolva.
• Azok törlésével vagy módosítával kapcsolatban keresd az osztályfőnöködet vagy az iskolád rendszergazdáját.
Amikor az alkalmazás hibába ütközik, lehetőség van hibajelentés küldésére.
Ez személyes- vagy tanulmányi adatokat nem tartalmaz, viszont részletes információval szolgál a hibáról és eszközödről.
A küldés előtt megjelenő képernyőn a te felelősséged átnézni a továbbításra kerülő adatsort.
A hibajelentéseket a reFilc fejlesztői felületén és egy privát Discord szobában tároljuk, ezekhez csak az app fejlesztői férnek hozzá.
Az alkalmazás belépéskor a GitHub API segítségével ellenőrzi, hogy elérhető-e új verzió, és kérésre innen is tölti le a telepítőt.
• Az alkalmazás névtelen használati statisztikákat gyűjt, ezek alapján tudjuk meghatározni a felhasználók és a telepítések számát, valamint az eszközük platformját. Ezt a beállításokban kikapcsolhatod. Kérünk, hogy ha csak teheted, hagyd ezt a funkciót bekapcsolva, hogy pontosabb információnk legyen a felhasználóink platform-megoszlásáról.
Ha az adataiddal kapcsolatban bármilyen kérdésed van (törlés, módosítás, adathordozás), keress minket a filcnaplo@filcnaplo.hu címen.
• Amikor az alkalmazás hibába ütközik, lehetőség van hibajelentés küldésére. Ez személyes- és/vagy tanulmányi adatokat nem tartalmaz, viszont részletes információval szolgál a hibáról, annak okáról és eszközödről. A küldés előtt megjelenő képernyőn a te felelősséged átnézni a továbbításra kerülő adatsort. A hibajelentéseket a reFilc fejlesztői felületén és egy privát Discord szobában tároljuk, ezekhez csak az app fejlesztői férnek hozzá.
Az alkalmazás használatával jelzed, hogy ezt a tájékoztatót tudomásul vetted.
Az alkalmazás (az alábbi platformokon: Android, Linux, Windows) minden egyes indításakor a reFilc API, valamint a Github API segítségével ellenőrzi, hogy elérhető-e új verzió, és kérésre innen letölti és telepíti a frissítést.
Utolsó módosítás: 2023. 05. 28.
""",
• Amennyiben az adataiddal kapcsolatban bármilyen kérdésed van (megtekintés, törlés, módosítás, adathordozás), keress minket a social@refilc.hu e-mail címen, vagy Discord szerverünkön!
• A kliensalkalmazás bármely eszközön és platformon történő használatával tudomásul vetted és elfogadod a jelen adatkezelési tájékoztatót. A reFilc csapata fenntartja a jogot a tájékoztató módosítására és a módosításokról nem köteles értesíteni a felhasználóit!
""",
onOpen: (link) => launch(link.url,
customTabsOption: CustomTabsOption(
toolbarColor: Theme.of(context).scaffoldBackgroundColor,

View File

@@ -15,6 +15,7 @@ import 'package:filcnaplo/models/user.dart';
import 'package:filcnaplo/theme/colors/colors.dart';
import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_mobile_ui/common/action_button.dart';
import 'package:filcnaplo_mobile_ui/common/beta_chip.dart';
import 'package:filcnaplo_mobile_ui/common/bottom_sheet_menu/bottom_sheet_menu.dart';
import 'package:filcnaplo_mobile_ui/common/bottom_sheet_menu/bottom_sheet_menu_item.dart';
import 'package:filcnaplo_mobile_ui/common/panel/panel.dart';
@@ -41,6 +42,7 @@ import 'package:filcnaplo_premium/ui/mobile/settings/nickname.dart';
import 'package:filcnaplo_premium/ui/mobile/settings/profile_pic.dart';
import 'package:filcnaplo_premium/ui/mobile/settings/icon_pack.dart';
import 'package:filcnaplo_premium/ui/mobile/settings/modify_subject_names.dart';
import 'package:filcnaplo_premium/ui/mobile/settings/modify_teacher_names.dart';
class SettingsScreen extends StatefulWidget {
const SettingsScreen({Key? key}) : super(key: key);
@@ -156,7 +158,8 @@ class _SettingsScreenState extends State<SettingsScreen>
void initState() {
super.initState();
Future.delayed(Duration.zero, () {
futureRelease = Provider.of<UpdateProvider>(context, listen: false).installedVersion();
futureRelease = Provider.of<UpdateProvider>(context, listen: false)
.installedVersion();
});
_hideContainersController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 200));
@@ -383,7 +386,10 @@ class _SettingsScreenState extends State<SettingsScreen>
},
title: Text("language".i18n),
leading: const Icon(FeatherIcons.globe),
trailing: Text(languageText),
trailing: Text(
languageText,
style: const TextStyle(fontSize: 14.0),
),
),
PanelButton(
onPressed: () {
@@ -392,7 +398,10 @@ class _SettingsScreenState extends State<SettingsScreen>
},
title: Text("startpage".i18n),
leading: const Icon(FeatherIcons.play),
trailing: Text(startPageTitle.capital()),
trailing: Text(
startPageTitle.capital(),
style: const TextStyle(fontSize: 14.0),
),
),
PanelButton(
onPressed: () {
@@ -401,8 +410,10 @@ class _SettingsScreenState extends State<SettingsScreen>
},
title: Text("rounding".i18n),
leading: const Icon(FeatherIcons.gitCommit),
trailing:
Text((settings.rounding / 10).toStringAsFixed(1)),
trailing: Text(
(settings.rounding / 10).toStringAsFixed(1),
style: const TextStyle(fontSize: 14.0),
),
),
PanelButton(
onPressed: () {
@@ -411,7 +422,10 @@ class _SettingsScreenState extends State<SettingsScreen>
},
title: Text("vibrate".i18n),
leading: const Icon(FeatherIcons.radio),
trailing: Text(vibrateTitle),
trailing: Text(
vibrateTitle,
style: const TextStyle(fontSize: 14.0),
),
),
PanelButton(
padding: const EdgeInsets.only(left: 14.0),
@@ -445,50 +459,32 @@ class _SettingsScreenState extends State<SettingsScreen>
contentPadding: const EdgeInsets.only(left: 12.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0)),
title: Row(children: [
Icon(FeatherIcons.messageSquare,
color: settings.notificationsEnabled ? Theme.of(context).colorScheme.secondary : AppColors.of(context).text.withOpacity(.25)),
const SizedBox(width: 14.0),
Text(
"notifications".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(
settings.notificationsEnabled ? 1.0 : .5),
fontWeight: FontWeight.w600,
fontSize: 16.0,
),
),
const SizedBox(
width: 5,
),
SizedBox(
height: 30,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
child: Padding(
padding:
const EdgeInsets.only(left: 10, right: 10),
child: Center(
child: Text("BETA",
style: TextStyle(
fontSize: 9.1,
color: AppColors.of(context)
.text
.withOpacity(
settings.notificationsEnabled
? 1.0
: .5),
fontWeight: FontWeight.w600,
overflow: TextOverflow.ellipsis))),
),
decoration: BoxDecoration(
color: settings.notificationsEnabled
title: Row(
children: [
Icon(FeatherIcons.messageSquare,
color: settings.notificationsEnabled
? Theme.of(context).colorScheme.secondary
: AppColors.of(context).text.withOpacity(.25),
borderRadius: BorderRadius.circular(40)),
: AppColors.of(context)
.text
.withOpacity(.25)),
const SizedBox(width: 14.0),
Text(
"notifications".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(
settings.notificationsEnabled ? 1.0 : .5),
fontWeight: FontWeight.w600,
fontSize: 16.0,
),
),
)
]),
const SizedBox(
width: 5,
),
BetaChip(
disabled: !settings.notificationsEnabled,
),
],
),
onChanged: (value) =>
settings.update(notificationsEnabled: value),
),
@@ -636,7 +632,10 @@ class _SettingsScreenState extends State<SettingsScreen>
},
title: Text("theme".i18n),
leading: const Icon(FeatherIcons.sun),
trailing: Text(themeModeText),
trailing: Text(
themeModeText,
style: const TextStyle(fontSize: 14.0),
),
),
PanelButton(
onPressed: () async {
@@ -805,6 +804,9 @@ class _SettingsScreenState extends State<SettingsScreen>
MenuRenamedSubjects(
settings: settings,
),
MenuRenamedTeachers(
settings: settings,
),
],
),
),
@@ -817,6 +819,19 @@ class _SettingsScreenState extends State<SettingsScreen>
child: Panel(
title: Text("about".i18n),
child: Column(children: [
PanelButton(
leading: const Icon(FeatherIcons.mail),
title: Text("news".i18n),
onPressed: () => _openNews(context),
),
PanelButton(
leading: const Icon(FeatherIcons.lock),
title: Text("privacy".i18n),
// onPressed: () => launchUrl(
// Uri.parse("https://refilc.hu/privacy-policy"),
// mode: LaunchMode.inAppWebView),
onPressed: () => _openPrivacy(context),
),
PanelButton(
leading: const Icon(FeatherIcons.atSign),
title: const Text("Discord"),
@@ -838,16 +853,6 @@ class _SettingsScreenState extends State<SettingsScreen>
Uri.parse("https://github.com/refilc"),
mode: LaunchMode.externalApplication),
),
PanelButton(
leading: const Icon(FeatherIcons.mail),
title: Text("news".i18n),
onPressed: () => _openNews(context),
),
PanelButton(
leading: const Icon(FeatherIcons.lock),
title: Text("privacy".i18n),
onPressed: () => _openPrivacy(context),
),
PanelButton(
leading: const Icon(FeatherIcons.award),
title: Text("licenses".i18n),
@@ -923,7 +928,8 @@ class _SettingsScreenState extends State<SettingsScreen>
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0)),
title: Text("devmode".i18n,
style: const TextStyle(fontWeight: FontWeight.w500)),
style:
const TextStyle(fontWeight: FontWeight.w500)),
onChanged: (v) =>
settings.update(developerMode: false),
value: settings.developerMode,
@@ -998,8 +1004,8 @@ class _SettingsScreenState extends State<SettingsScreen>
if (devmodeCountdown > 0) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
duration: const Duration(milliseconds: 200),
content: Text(
"devmoretaps".i18n.fill([devmodeCountdown])),
content:
Text("devmoretaps".i18n.fill([devmodeCountdown])),
));
setState(() => devmodeCountdown--);

View File

@@ -163,7 +163,7 @@ extension SettingsLocalization on String {
"theme": "Thema",
"color": "Farbe",
"grade_colors": "Grad Farben",
"notifications": "Benachrichtigungen",
"notifications": "Mitteilung",
"news": "Nachrichten",
"extras": "Extras",
"about": "Informationen",

View File

@@ -1,52 +0,0 @@
name: filcnaplo_mobile_ui
publish_to: "none"
environment:
sdk: ">=2.17.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
# Filcnaplo main dep
filcnaplo:
path: ../filcnaplo/
filcnaplo_kreta_api:
path: ../filcnaplo_kreta_api/
filcnaplo_premium:
path: ../filcnaplo_premium/
flutter_feather_icons: ^2.0.0+1
provider: ^5.0.0
fl_chart: ^0.45.1
url_launcher: ^6.0.9
flutter_material_color_picker: ^1.1.0+2
photo_view: ^0.14.0
flutter_linkify: ^5.0.2
flutter_custom_tabs: ^1.0.3
flutter_markdown: ^0.6.5
animations: ^2.0.1
animated_list_plus: ^0.5.0
confetti: ^0.6.0
live_activities: ^1.0.0
animated_flip_counter: ^0.2.5
lottie: ^1.4.3
rive: ^0.9.1
animated_background: ^2.0.0
home_widget: ^0.1.6
dropdown_button2: ^1.8.9
flutter_svg: ^1.1.6
background_fetch: ^1.1.5
wtf_sliding_sheet: ^1.0.0
package_info_plus: ^4.0.2
dotted_border: ^2.0.0+3
screenshot: ^2.1.0
image_gallery_saver: ^2.0.2
rounded_expansion_tile: ^0.0.13
dev_dependencies:
flutter_lints: ^1.0.0
flutter:
uses-material-design: true