forked from firka/student-legacy
common filter
This commit is contained in:
parent
fd7793a20d
commit
8a39086ca6
8
filcnaplo/lib/ui/date_widget.dart
Normal file
8
filcnaplo/lib/ui/date_widget.dart
Normal file
@ -0,0 +1,8 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class DateWidget {
|
||||
final DateTime date;
|
||||
final Widget widget;
|
||||
final String? key;
|
||||
const DateWidget({required this.date, required this.widget, this.key});
|
||||
}
|
159
filcnaplo/lib/ui/filter/sort.dart
Normal file
159
filcnaplo/lib/ui/filter/sort.dart
Normal file
@ -0,0 +1,159 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/ui/date_widget.dart';
|
||||
import 'package:filcnaplo/ui/filter/widgets.dart';
|
||||
import 'package:filcnaplo/ui/widgets/message/message_tile.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/message.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_viewable.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence_group/absence_group_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/timetable/changed_lesson_tile.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:implicitly_animated_reorderable_list/implicitly_animated_reorderable_list.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
|
||||
// difference.inDays is not reliable
|
||||
bool _sameDate(DateTime a, DateTime b) => (a.year == b.year && a.month == b.month && a.day == b.day);
|
||||
|
||||
List<Widget> sortDateWidgets(
|
||||
BuildContext context, {
|
||||
required List<DateWidget> dateWidgets,
|
||||
bool showTitle = true,
|
||||
bool showDivider = false,
|
||||
bool hasShadow = false,
|
||||
EdgeInsetsGeometry? padding,
|
||||
}) {
|
||||
dateWidgets.sort((a, b) => -a.date.compareTo(b.date));
|
||||
|
||||
List<Conversation> conversations = [];
|
||||
List<DateWidget> convMessages = [];
|
||||
|
||||
// Group messages into conversations
|
||||
for (var w in dateWidgets) {
|
||||
if (w.widget.runtimeType == MessageTile) {
|
||||
Message message = (w.widget as MessageTile).message;
|
||||
|
||||
if (message.conversationId != null) {
|
||||
convMessages.add(w);
|
||||
|
||||
Conversation conv = conversations.firstWhere((e) => e.id == message.conversationId, orElse: () => Conversation(id: message.conversationId!));
|
||||
conv.add(message);
|
||||
if (conv.messages.length == 1) conversations.add(conv);
|
||||
}
|
||||
|
||||
if (conversations.any((c) => c.id == message.messageId)) {
|
||||
Conversation conv = conversations.firstWhere((e) => e.id == message.messageId);
|
||||
convMessages.add(w);
|
||||
conv.add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove individual messages
|
||||
for (var e in convMessages) {
|
||||
dateWidgets.remove(e);
|
||||
}
|
||||
|
||||
// Add conversations
|
||||
for (var conv in conversations) {
|
||||
conv.sort();
|
||||
|
||||
dateWidgets.add(DateWidget(
|
||||
key: "${conv.newest.date.millisecondsSinceEpoch}-msg",
|
||||
date: conv.newest.date,
|
||||
widget: MessageTile(
|
||||
conv.newest,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
dateWidgets.sort((a, b) => -a.date.compareTo(b.date));
|
||||
|
||||
List<List<DateWidget>> groupedDateWidgets = [[]];
|
||||
for (var element in dateWidgets) {
|
||||
if (groupedDateWidgets.last.isNotEmpty) {
|
||||
if (!_sameDate(element.date, groupedDateWidgets.last.last.date)) {
|
||||
groupedDateWidgets.add([element]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
groupedDateWidgets.last.add(element);
|
||||
}
|
||||
|
||||
List<DateWidget> items = [];
|
||||
|
||||
if (groupedDateWidgets.first.isNotEmpty) {
|
||||
for (var elements in groupedDateWidgets) {
|
||||
bool cst = showTitle;
|
||||
|
||||
// Group Absence Tiles
|
||||
List<DateWidget> absenceTileWidgets = elements.where((element) {
|
||||
return element.widget is AbsenceViewable && (element.widget as AbsenceViewable).absence.delay == 0;
|
||||
}).toList();
|
||||
List<AbsenceViewable> absenceTiles = absenceTileWidgets.map((e) => e.widget as AbsenceViewable).toList();
|
||||
if (absenceTiles.length > 1) {
|
||||
elements.removeWhere((element) => element.widget.runtimeType == AbsenceViewable && (element.widget as AbsenceViewable).absence.delay == 0);
|
||||
if (elements.isEmpty) {
|
||||
cst = false;
|
||||
}
|
||||
elements.add(DateWidget(
|
||||
widget: AbsenceGroupTile(absenceTiles, showDate: !cst),
|
||||
date: absenceTileWidgets.first.date,
|
||||
key: "${absenceTileWidgets.first.date.millisecondsSinceEpoch}-absence-group"));
|
||||
}
|
||||
|
||||
// Bring Lesson Tiles to front & sort by index asc
|
||||
List<DateWidget> lessonTiles = elements.where((element) {
|
||||
return element.widget.runtimeType == ChangedLessonTile;
|
||||
}).toList();
|
||||
lessonTiles.sort((a, b) => (a.widget as ChangedLessonTile).lesson.lessonIndex.compareTo((b.widget as ChangedLessonTile).lesson.lessonIndex));
|
||||
elements.removeWhere((element) => element.widget.runtimeType == ChangedLessonTile);
|
||||
elements.insertAll(0, lessonTiles);
|
||||
|
||||
final date = (elements + absenceTileWidgets).first.date;
|
||||
items.add(DateWidget(
|
||||
date: date,
|
||||
widget: Panel(
|
||||
key: ValueKey(date),
|
||||
padding: padding ?? const EdgeInsets.symmetric(vertical: 6.0),
|
||||
title: cst ? Text(date.format(context, forceToday: true)) : null,
|
||||
hasShadow: hasShadow,
|
||||
child: ImplicitlyAnimatedList<DateWidget>(
|
||||
areItemsTheSame: (a, b) => a.key == b.key,
|
||||
spawnIsolate: false,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, animation, item, index) => filterItemBuilder(context, animation, item.widget, index),
|
||||
items: elements,
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
final nh = DateTime.now();
|
||||
final now = DateTime(nh.year, nh.month, nh.day).subtract(const Duration(seconds: 1));
|
||||
|
||||
if (showDivider && items.any((i) => i.date.isBefore(now)) && items.any((i) => i.date.isAfter(now))) {
|
||||
items.add(
|
||||
DateWidget(
|
||||
date: now,
|
||||
widget: Center(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 12.0),
|
||||
height: 3.0,
|
||||
width: 150.0,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
color: AppColors.of(context).text.withOpacity(.25),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Sort future dates asc, past dates desc
|
||||
items.sort((a, b) => (a.date.isAfter(now) && b.date.isAfter(now) ? 1 : -1) * a.date.compareTo(b.date));
|
||||
|
||||
return items.map((e) => e.widget).toList();
|
||||
}
|
160
filcnaplo/lib/ui/filter/widgets.dart
Normal file
160
filcnaplo/lib/ui/filter/widgets.dart
Normal file
@ -0,0 +1,160 @@
|
||||
import 'package:filcnaplo/api/providers/update_provider.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/ui/date_widget.dart';
|
||||
import 'package:filcnaplo/ui/filter/widgets/grades.dart' as grade_filter;
|
||||
import 'package:filcnaplo/ui/filter/widgets/certifications.dart' as certification_filter;
|
||||
import 'package:filcnaplo/ui/filter/widgets/messages.dart' as message_filter;
|
||||
import 'package:filcnaplo/ui/filter/widgets/absences.dart' as absence_filter;
|
||||
import 'package:filcnaplo/ui/filter/widgets/homework.dart' as homework_filter;
|
||||
import 'package:filcnaplo/ui/filter/widgets/exams.dart' as exam_filter;
|
||||
import 'package:filcnaplo/ui/filter/widgets/notes.dart' as note_filter;
|
||||
import 'package:filcnaplo/ui/filter/widgets/events.dart' as event_filter;
|
||||
import 'package:filcnaplo/ui/filter/widgets/lessons.dart' as lesson_filter;
|
||||
import 'package:filcnaplo/ui/filter/widgets/update.dart' as update_filter;
|
||||
import 'package:filcnaplo/ui/filter/widgets/missed_exams.dart' as missed_exam_filter;
|
||||
import 'package:filcnaplo_kreta_api/providers/absence_provider.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/event_provider.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/exam_provider.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/homework_provider.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/message_provider.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/note_provider.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:implicitly_animated_reorderable_list/transitions.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
const List<FilterType> homeFilters = [FilterType.all, FilterType.grades, FilterType.messages, FilterType.absences];
|
||||
|
||||
enum FilterType { all, grades, messages, absences, homework, exams, notes, events, lessons, updates, certifications, missedExams }
|
||||
|
||||
Future<List<DateWidget>> getFilterWidgets(FilterType activeData, {bool absencesNoExcused = false, required BuildContext context}) async {
|
||||
final gradeProvider = Provider.of<GradeProvider>(context);
|
||||
final timetableProvider = Provider.of<TimetableProvider>(context);
|
||||
final messageProvider = Provider.of<MessageProvider>(context);
|
||||
final absenceProvider = Provider.of<AbsenceProvider>(context);
|
||||
final homeworkProvider = Provider.of<HomeworkProvider>(context);
|
||||
final examProvider = Provider.of<ExamProvider>(context);
|
||||
final noteProvider = Provider.of<NoteProvider>(context);
|
||||
final eventProvider = Provider.of<EventProvider>(context);
|
||||
final updateProvider = Provider.of<UpdateProvider>(context);
|
||||
|
||||
List<DateWidget> items = [];
|
||||
|
||||
switch (activeData) {
|
||||
// All
|
||||
case FilterType.all:
|
||||
final all = await Future.wait<List<DateWidget>>([
|
||||
getFilterWidgets(FilterType.grades, context: context),
|
||||
getFilterWidgets(FilterType.lessons, context: context),
|
||||
getFilterWidgets(FilterType.messages, context: context),
|
||||
getFilterWidgets(FilterType.absences, context: context, absencesNoExcused: true),
|
||||
getFilterWidgets(FilterType.homework, context: context),
|
||||
getFilterWidgets(FilterType.exams, context: context),
|
||||
getFilterWidgets(FilterType.updates, context: context),
|
||||
getFilterWidgets(FilterType.certifications, context: context),
|
||||
getFilterWidgets(FilterType.missedExams, context: context),
|
||||
]);
|
||||
items = all.expand((x) => x).toList();
|
||||
|
||||
break;
|
||||
|
||||
// Grades
|
||||
case FilterType.grades:
|
||||
items = grade_filter.getWidgets(gradeProvider.grades);
|
||||
break;
|
||||
|
||||
// Certifications
|
||||
case FilterType.certifications:
|
||||
items = certification_filter.getWidgets(gradeProvider.grades);
|
||||
break;
|
||||
|
||||
// Messages
|
||||
case FilterType.messages:
|
||||
items = message_filter.getWidgets(
|
||||
messageProvider.messages,
|
||||
noteProvider.notes,
|
||||
eventProvider.events,
|
||||
);
|
||||
break;
|
||||
|
||||
// Absences
|
||||
case FilterType.absences:
|
||||
items = absence_filter.getWidgets(absenceProvider.absences, noExcused: absencesNoExcused);
|
||||
break;
|
||||
|
||||
// Homework
|
||||
case FilterType.homework:
|
||||
items = homework_filter.getWidgets(homeworkProvider.homework);
|
||||
break;
|
||||
|
||||
// Exams
|
||||
case FilterType.exams:
|
||||
items = exam_filter.getWidgets(examProvider.exams);
|
||||
break;
|
||||
|
||||
// Notes
|
||||
case FilterType.notes:
|
||||
items = note_filter.getWidgets(noteProvider.notes);
|
||||
break;
|
||||
|
||||
// Events
|
||||
case FilterType.events:
|
||||
items = event_filter.getWidgets(eventProvider.events);
|
||||
break;
|
||||
|
||||
// Changed Lessons
|
||||
case FilterType.lessons:
|
||||
items = lesson_filter.getWidgets(timetableProvider.lessons);
|
||||
break;
|
||||
|
||||
// Updates
|
||||
case FilterType.updates:
|
||||
if (updateProvider.releases.isNotEmpty) items = [update_filter.getWidget(updateProvider.releases.first)];
|
||||
break;
|
||||
|
||||
// Missed Exams
|
||||
case FilterType.missedExams:
|
||||
items = missed_exam_filter.getWidgets(timetableProvider.lessons);
|
||||
break;
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
Widget filterItemBuilder(BuildContext context, Animation<double> animation, Widget item, int index) {
|
||||
final wrappedItem = SizeFadeTransition(
|
||||
curve: Curves.easeInOutCubic,
|
||||
animation: animation,
|
||||
child: item,
|
||||
);
|
||||
return item is Panel
|
||||
// Re-add & animate shadow
|
||||
? AnimatedBuilder(
|
||||
animation: animation,
|
||||
child: wrappedItem,
|
||||
builder: (context, child) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
if (Theme.of(context).brightness == Brightness.light)
|
||||
BoxShadow(
|
||||
offset: const Offset(0, 21),
|
||||
blurRadius: 23.0,
|
||||
color: AppColors.of(context).shadow.withOpacity(
|
||||
CurvedAnimation(
|
||||
parent: CurvedAnimation(parent: animation, curve: Curves.easeInOutCubic),
|
||||
curve: const Interval(2 / 3, 1.0),
|
||||
).value,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
})
|
||||
: wrappedItem;
|
||||
}
|
15
filcnaplo/lib/ui/filter/widgets/absences.dart
Normal file
15
filcnaplo/lib/ui/filter/widgets/absences.dart
Normal file
@ -0,0 +1,15 @@
|
||||
import 'package:filcnaplo/ui/date_widget.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/absence.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_viewable.dart' as mobile;
|
||||
|
||||
List<DateWidget> getWidgets(List<Absence> providerAbsences, {bool noExcused = false}) {
|
||||
List<DateWidget> items = [];
|
||||
providerAbsences.where((a) => !noExcused || a.state != Justification.excused).forEach((absence) {
|
||||
items.add(DateWidget(
|
||||
key: absence.id,
|
||||
date: absence.date,
|
||||
widget: mobile.AbsenceViewable(absence),
|
||||
));
|
||||
});
|
||||
return items;
|
||||
}
|
24
filcnaplo/lib/ui/filter/widgets/certifications.dart
Normal file
24
filcnaplo/lib/ui/filter/widgets/certifications.dart
Normal file
@ -0,0 +1,24 @@
|
||||
import 'package:filcnaplo/ui/date_widget.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/cretification/certification_card.dart' as mobile;
|
||||
|
||||
List<DateWidget> getWidgets(List<Grade> providerGrades) {
|
||||
List<DateWidget> items = [];
|
||||
for (var gradeType in GradeType.values) {
|
||||
if ([GradeType.midYear, GradeType.unknown, GradeType.levelExam].contains(gradeType)) continue;
|
||||
|
||||
List<Grade> grades = providerGrades.where((grade) => grade.type == gradeType).toList();
|
||||
if (grades.isNotEmpty) {
|
||||
grades.sort((a, b) => -a.date.compareTo(b.date));
|
||||
|
||||
items.add(DateWidget(
|
||||
date: grades.first.date,
|
||||
widget: mobile.CertificationCard(
|
||||
grades,
|
||||
gradeType: gradeType,
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
15
filcnaplo/lib/ui/filter/widgets/events.dart
Normal file
15
filcnaplo/lib/ui/filter/widgets/events.dart
Normal file
@ -0,0 +1,15 @@
|
||||
import 'package:filcnaplo/ui/date_widget.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/event.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/event/event_viewable.dart' as mobile;
|
||||
|
||||
List<DateWidget> getWidgets(List<Event> providerEvents) {
|
||||
List<DateWidget> items = [];
|
||||
for (var event in providerEvents) {
|
||||
items.add(DateWidget(
|
||||
key: event.id,
|
||||
date: event.start,
|
||||
widget: mobile.EventViewable(event),
|
||||
));
|
||||
}
|
||||
return items;
|
||||
}
|
15
filcnaplo/lib/ui/filter/widgets/exams.dart
Normal file
15
filcnaplo/lib/ui/filter/widgets/exams.dart
Normal file
@ -0,0 +1,15 @@
|
||||
import 'package:filcnaplo/ui/date_widget.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/exam.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/exam/exam_viewable.dart' as mobile;
|
||||
|
||||
List<DateWidget> getWidgets(List<Exam> providerExams) {
|
||||
List<DateWidget> items = [];
|
||||
for (var exam in providerExams) {
|
||||
items.add(DateWidget(
|
||||
key: exam.id,
|
||||
date: exam.writeDate.year != 0 ? exam.writeDate : exam.date,
|
||||
widget: mobile.ExamViewable(exam),
|
||||
));
|
||||
}
|
||||
return items;
|
||||
}
|
19
filcnaplo/lib/ui/filter/widgets/grades.dart
Normal file
19
filcnaplo/lib/ui/filter/widgets/grades.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'package:filcnaplo/ui/date_widget.dart';
|
||||
import 'package:filcnaplo/utils/platform.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/grade/grade_viewable.dart' as mobile;
|
||||
import 'package:filcnaplo_desktop_ui/common/widgets/grade/grade_viewable.dart' as desktop;
|
||||
|
||||
List<DateWidget> getWidgets(List<Grade> providerGrades) {
|
||||
List<DateWidget> items = [];
|
||||
for (var grade in providerGrades) {
|
||||
if (grade.type == GradeType.midYear) {
|
||||
items.add(DateWidget(
|
||||
key: grade.id,
|
||||
date: grade.date,
|
||||
widget: PlatformUtils.isMobile ? mobile.GradeViewable(grade) : desktop.GradeViewable(grade),
|
||||
));
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
18
filcnaplo/lib/ui/filter/widgets/homework.dart
Normal file
18
filcnaplo/lib/ui/filter/widgets/homework.dart
Normal file
@ -0,0 +1,18 @@
|
||||
import 'package:filcnaplo/ui/date_widget.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/homework.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/homework/homework_viewable.dart' as mobile;
|
||||
|
||||
List<DateWidget> getWidgets(List<Homework> providerHomework) {
|
||||
List<DateWidget> items = [];
|
||||
final now = DateTime.now();
|
||||
providerHomework.where((h) => h.deadline.hour == 0 ? _sameDate(h.deadline, now) : h.deadline.isAfter(now)).forEach((homework) {
|
||||
items.add(DateWidget(
|
||||
key: homework.id,
|
||||
date: homework.deadline.year != 0 ? homework.deadline : homework.date,
|
||||
widget: mobile.HomeworkViewable(homework),
|
||||
));
|
||||
});
|
||||
return items;
|
||||
}
|
||||
|
||||
bool _sameDate(DateTime a, DateTime b) => (a.year == b.year && a.month == b.month && a.day == b.day);
|
15
filcnaplo/lib/ui/filter/widgets/lessons.dart
Normal file
15
filcnaplo/lib/ui/filter/widgets/lessons.dart
Normal file
@ -0,0 +1,15 @@
|
||||
import 'package:filcnaplo/ui/date_widget.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/lesson.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/timetable/changed_lesson_viewable.dart' as mobile;
|
||||
|
||||
List<DateWidget> getWidgets(List<Lesson> providerLessons) {
|
||||
List<DateWidget> items = [];
|
||||
providerLessons.where((l) => l.isChanged && l.start.isAfter(DateTime.now())).forEach((lesson) {
|
||||
items.add(DateWidget(
|
||||
key: lesson.id,
|
||||
date: lesson.date,
|
||||
widget: mobile.ChangedLessonViewable(lesson),
|
||||
));
|
||||
});
|
||||
return items;
|
||||
}
|
23
filcnaplo/lib/ui/filter/widgets/messages.dart
Normal file
23
filcnaplo/lib/ui/filter/widgets/messages.dart
Normal file
@ -0,0 +1,23 @@
|
||||
import 'package:filcnaplo/ui/date_widget.dart';
|
||||
import 'package:filcnaplo/ui/filter/widgets/notes.dart' as note_filter;
|
||||
import 'package:filcnaplo/ui/filter/widgets/events.dart' as event_filter;
|
||||
import 'package:filcnaplo_kreta_api/models/event.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/message.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/note.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/message/message_viewable.dart' as mobile;
|
||||
|
||||
List<DateWidget> getWidgets(List<Message> providerMessages, List<Note> providerNotes, List<Event> providerEvents) {
|
||||
List<DateWidget> items = [];
|
||||
for (var message in providerMessages) {
|
||||
if (message.type == MessageType.inbox) {
|
||||
items.add(DateWidget(
|
||||
key: "${message.id}",
|
||||
date: message.date,
|
||||
widget: mobile.MessageViewable(message),
|
||||
));
|
||||
}
|
||||
}
|
||||
items.addAll(note_filter.getWidgets(providerNotes));
|
||||
items.addAll(event_filter.getWidgets(providerEvents));
|
||||
return items;
|
||||
}
|
35
filcnaplo/lib/ui/filter/widgets/missed_exams.dart
Normal file
35
filcnaplo/lib/ui/filter/widgets/missed_exams.dart
Normal file
@ -0,0 +1,35 @@
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo/ui/date_widget.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/lesson.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/missed_exam/missed_exam_viewable.dart';
|
||||
|
||||
List<DateWidget> getWidgets(List<Lesson> providerLessons) {
|
||||
List<DateWidget> items = [];
|
||||
List<Lesson> missedExams = [];
|
||||
|
||||
for (var lesson in providerLessons) {
|
||||
final desc = lesson.description.toLowerCase().specialChars();
|
||||
// Check if lesson description includes hints for an exam written during the lesson
|
||||
if (!lesson.studentPresence &&
|
||||
(lesson.exam != "" ||
|
||||
desc.contains("dolgozat") ||
|
||||
desc.contains("feleles") ||
|
||||
desc.contains("temazaro") ||
|
||||
desc.contains("szamonkeres") ||
|
||||
desc == "tz") &&
|
||||
!(desc.contains("felkeszules") || desc.contains("gyakorlas"))) {
|
||||
missedExams.add(lesson);
|
||||
}
|
||||
}
|
||||
|
||||
if (missedExams.isNotEmpty) {
|
||||
missedExams.sort((a, b) => -a.date.compareTo(b.date));
|
||||
|
||||
items.add(DateWidget(
|
||||
date: missedExams.first.date,
|
||||
widget: MissedExamViewable(missedExams),
|
||||
));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
15
filcnaplo/lib/ui/filter/widgets/notes.dart
Normal file
15
filcnaplo/lib/ui/filter/widgets/notes.dart
Normal file
@ -0,0 +1,15 @@
|
||||
import 'package:filcnaplo/ui/date_widget.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/note.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/note/note_viewable.dart' as mobile;
|
||||
|
||||
List<DateWidget> getWidgets(List<Note> providerNotes) {
|
||||
List<DateWidget> items = [];
|
||||
for (var note in providerNotes) {
|
||||
items.add(DateWidget(
|
||||
key: note.id,
|
||||
date: note.date,
|
||||
widget: mobile.NoteViewable(note),
|
||||
));
|
||||
}
|
||||
return items;
|
||||
}
|
10
filcnaplo/lib/ui/filter/widgets/update.dart
Normal file
10
filcnaplo/lib/ui/filter/widgets/update.dart
Normal file
@ -0,0 +1,10 @@
|
||||
import 'package:filcnaplo/models/release.dart';
|
||||
import 'package:filcnaplo/ui/date_widget.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/update/update_viewable.dart' as mobile;
|
||||
|
||||
DateWidget getWidget(Release providerRelease) {
|
||||
return DateWidget(
|
||||
date: DateTime.now(),
|
||||
widget: mobile.UpdateViewable(providerRelease),
|
||||
);
|
||||
}
|
229
filcnaplo/lib/ui/widgets/grade/grade_tile.dart
Normal file
229
filcnaplo/lib/ui/widgets/grade/grade_tile.dart
Normal file
@ -0,0 +1,229 @@
|
||||
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_icon.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}) : super(key: key);
|
||||
|
||||
final Grade grade;
|
||||
final void Function()? onTap;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String title;
|
||||
String subtitle;
|
||||
EdgeInsets leadingPadding = EdgeInsets.zero;
|
||||
bool isSubjectView = SubjectGradesContainer.of(context) != null;
|
||||
String subjectName = 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: Center(
|
||||
child: Padding(
|
||||
padding: leadingPadding,
|
||||
child: Icon(SubjectIcon.lookup(subject: grade.subject), size: 28.0, color: AppColors.of(context).text.withOpacity(.75)),
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: subtitle != ""
|
||||
? 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);
|
||||
},
|
||||
)
|
||||
: 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.complemented = false}) : super(key: key);
|
||||
|
||||
final GradeValue value;
|
||||
final double size;
|
||||
final bool fill;
|
||||
final bool complemented;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool isSubjectView = SubjectGradesContainer.of(context) != null;
|
||||
|
||||
Color color = gradeColor(context: context, value: value.value);
|
||||
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.value != 0) {
|
||||
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.w600 : FontWeight.bold,
|
||||
fontSize: size,
|
||||
color: color,
|
||||
shadows: [
|
||||
if (value.weight >= 200)
|
||||
Shadow(
|
||||
color: 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),
|
||||
),
|
||||
),
|
||||
]);
|
||||
} else if (value.valueName.toLowerCase().specialChars() == 'nem irt') {
|
||||
valueText = const Icon(FeatherIcons.slash);
|
||||
} else {
|
||||
valueText = const Icon(FeatherIcons.type);
|
||||
}
|
||||
|
||||
return fill
|
||||
? Container(
|
||||
width: size * 1.4,
|
||||
height: size * 1.4,
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(.25),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Center(child: valueText),
|
||||
)
|
||||
: valueText;
|
||||
}
|
||||
}
|
||||
|
||||
Color gradeColor({required BuildContext context, required num value}) {
|
||||
int valueInt = 0;
|
||||
|
||||
var settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||
|
||||
try {
|
||||
if (value > value.floor() + settings.rounding / 10) {
|
||||
valueInt = value.ceil();
|
||||
} else {
|
||||
valueInt = value.floor();
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
79
filcnaplo/lib/ui/widgets/message/message_tile.dart
Normal file
79
filcnaplo/lib/ui/widgets/message/message_tile.dart
Normal file
@ -0,0 +1,79 @@
|
||||
import 'package:animations/animations.dart';
|
||||
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,
|
||||
}) : super(key: key);
|
||||
|
||||
final Message message;
|
||||
final List<Message>? messages;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final Function()? onTap;
|
||||
|
||||
@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),
|
||||
)
|
||||
: ProfileImage(
|
||||
name: "Béla",
|
||||
radius: 22.0,
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
title: 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: Text(
|
||||
message.subject,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14.0),
|
||||
),
|
||||
trailing: Text(
|
||||
message.date.format(context),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14.0,
|
||||
color: AppColors.of(context).text.withOpacity(.75),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
6
filcnaplo/lib/utils/platform.dart
Normal file
6
filcnaplo/lib/utils/platform.dart
Normal file
@ -0,0 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
class PlatformUtils {
|
||||
static bool get isDesktop => Platform.isWindows || Platform.isMacOS || Platform.isLinux;
|
||||
static bool get isMobile => !isDesktop;
|
||||
}
|
@ -45,6 +45,7 @@ dependencies:
|
||||
material_color_utilities: ^0.1.3
|
||||
crypto: ^3.0.2
|
||||
elegant_notification: ^1.6.1
|
||||
flutter_feather_icons: ^2.0.0+1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 351c241efb3aad1ba4f7384ee63475ab74399f5d
|
||||
Subproject commit 98ae9b4139ca795ac9b39bcbb1d9ef2713d98a63
|
@ -1 +1 @@
|
||||
Subproject commit 2ed17a62b3d463c95452a9fa5bd406d87856668f
|
||||
Subproject commit 5600330a166e274c06cbbc5678ca2a40db919eb1
|
Loading…
x
Reference in New Issue
Block a user