changed everything from filcnaplo to refilc finally

This commit is contained in:
Kima
2024-02-24 20:12:25 +01:00
parent 0d1c7b7143
commit 1171e3aaaf
655 changed files with 38728 additions and 44967 deletions

View 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});
}

View File

@@ -0,0 +1,198 @@
import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc/ui/date_widget.dart';
import 'package:refilc/ui/filter/widgets.dart';
import 'package:refilc/ui/widgets/message/message_tile.dart';
import 'package:refilc_kreta_api/models/message.dart';
import 'package:refilc_mobile_ui/common/panel/panel.dart';
import 'package:refilc_mobile_ui/common/widgets/absence/absence_viewable.dart';
import 'package:refilc_mobile_ui/common/widgets/absence_group/absence_group_tile.dart';
import 'package:refilc_mobile_ui/common/widgets/cretification/certification_card.dart';
import 'package:refilc_mobile_ui/common/widgets/grade/new_grades.dart';
import 'package:flutter/material.dart';
import 'package:animated_list_plus/animated_list_plus.dart';
import 'package:refilc_mobile_ui/common/widgets/lesson/changed_lesson_tile.dart';
import 'package:refilc/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,
padding: const EdgeInsets.symmetric(horizontal: 4.0),
),
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(
isTransparent: true,
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,
len: elements.length,
isAfterSeparated: index > 0 &&
(elements[index - 1].widget is CertificationCard ||
elements[index - 1].widget is NewGradesSurprise),
isBeforeSeparated: (index < elements.length - 1) &&
(elements[index + 1].widget is CertificationCard ||
elements[index + 1].widget is NewGradesSurprise),
),
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();
}

View File

@@ -0,0 +1,280 @@
import 'package:refilc/api/providers/ad_provider.dart';
import 'package:refilc/api/providers/update_provider.dart';
import 'package:refilc/models/settings.dart';
import 'package:refilc/ui/date_widget.dart';
import 'package:refilc/ui/filter/widgets/grades.dart' as grade_filter;
import 'package:refilc/ui/filter/widgets/certifications.dart'
as certification_filter;
import 'package:refilc/ui/filter/widgets/messages.dart' as message_filter;
import 'package:refilc/ui/filter/widgets/absences.dart' as absence_filter;
import 'package:refilc/ui/filter/widgets/homework.dart' as homework_filter;
import 'package:refilc/ui/filter/widgets/exams.dart' as exam_filter;
import 'package:refilc/ui/filter/widgets/notes.dart' as note_filter;
import 'package:refilc/ui/filter/widgets/events.dart' as event_filter;
import 'package:refilc/ui/filter/widgets/lessons.dart' as lesson_filter;
import 'package:refilc/ui/filter/widgets/update.dart' as update_filter;
import 'package:refilc/ui/filter/widgets/missed_exams.dart'
as missed_exam_filter;
import 'package:refilc/ui/filter/widgets/ads.dart' as ad_filter;
import 'package:refilc_kreta_api/models/week.dart';
import 'package:refilc_kreta_api/providers/absence_provider.dart';
import 'package:refilc_kreta_api/providers/event_provider.dart';
import 'package:refilc_kreta_api/providers/exam_provider.dart';
import 'package:refilc_kreta_api/providers/grade_provider.dart';
import 'package:refilc_kreta_api/providers/homework_provider.dart';
import 'package:refilc_kreta_api/providers/message_provider.dart';
import 'package:refilc_kreta_api/providers/note_provider.dart';
import 'package:refilc_kreta_api/providers/timetable_provider.dart';
import 'package:refilc_mobile_ui/common/widgets/cretification/certification_card.dart';
import 'package:refilc_mobile_ui/common/widgets/grade/new_grades.dart';
import 'package:refilc_mobile_ui/common/widgets/note/note_viewable.dart';
import 'package:refilc_plus/providers/premium_provider.dart';
import 'package:refilc_plus/ui/mobile/premium/premium_inline.dart';
import 'package:refilc_mobile_ui/common/panel/panel.dart';
import 'package:flutter/material.dart';
import 'package:animated_list_plus/transitions.dart';
import 'package:provider/provider.dart';
const List<FilterType> homeFilters = [
FilterType.all,
FilterType.grades,
FilterType.exams,
FilterType.messages,
FilterType.absences
];
enum FilterType {
all,
grades,
newGrades,
messages,
absences,
homework,
exams,
notes,
events,
lessons,
updates,
certifications,
missedExams,
ads,
}
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);
final settingsProvider = Provider.of<SettingsProvider>(context);
final adProvider = Provider.of<AdProvider>(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),
getFilterWidgets(FilterType.ads, context: context),
]);
items = all.expand((x) => x).toList();
break;
// Grades
case FilterType.grades:
if (!settingsProvider.gradeOpeningFun) {
gradeProvider.seenAll();
}
items = grade_filter.getWidgets(
gradeProvider.grades, gradeProvider.lastSeenDate);
if (settingsProvider.gradeOpeningFun) {
items.addAll(
// ignore: use_build_context_synchronously
await getFilterWidgets(FilterType.newGrades, context: context));
}
break;
// Grades
case FilterType.newGrades:
items = grade_filter.getNewWidgets(
gradeProvider.grades, gradeProvider.lastSeenDate);
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, context);
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.getWeek(Week.current()) ?? []);
break;
// Updates
case FilterType.updates:
if (updateProvider.available) {
items = [update_filter.getWidget(updateProvider.releases.first)];
}
break;
// Missed Exams
case FilterType.missedExams:
items = missed_exam_filter
.getWidgets(timetableProvider.getWeek(Week.current()) ?? []);
break;
// Ads
case FilterType.ads:
if (adProvider.available) {
items = ad_filter.getWidgets(adProvider.ads);
}
break;
}
return items;
}
Widget filterItemBuilder(
BuildContext context,
Animation<double> animation,
Widget item,
int index, {
int len = 0,
bool isAfterSeparated = false,
bool isBeforeSeparated = false,
}) {
if (item.key == const Key("\$premium")) {
return Provider.of<PremiumProvider>(context, listen: false).hasPremium ||
DateTime.now().weekday <= 5
? const SizedBox()
: const Padding(
padding: EdgeInsets.only(bottom: 24.0),
child: PremiumInline(features: [
PremiumInlineFeature.nickname,
PremiumInlineFeature.theme,
PremiumInlineFeature.widget,
]),
);
}
final wrappedItem = SizeFadeTransition(
curve: Curves.easeInOutCubic,
animation: animation,
child: item,
);
bool separated = item is CertificationCard || item is NewGradesSurprise;
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 (Provider.of<SettingsProvider>(context, listen: false)
.shadowEffect)
BoxShadow(
offset: const Offset(0, 21),
blurRadius: 23.0,
color: Theme.of(context).shadowColor.withOpacity(
Theme.of(context).shadowColor.opacity *
CurvedAnimation(
parent: CurvedAnimation(
parent: animation,
curve: Curves.easeInOutCubic),
curve: const Interval(2 / 3, 1.0),
).value,
),
),
],
),
child: child,
),
);
})
: (len > 0
? Padding(
padding: EdgeInsets.only(
top: index == 0
? 0.0
: (separated || isAfterSeparated ? 9.0 : 6.0)),
child: Container(
padding: item is NoteViewable
? const EdgeInsets.symmetric(vertical: 8.0)
: const EdgeInsets.symmetric(vertical: 4.0),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background,
borderRadius: BorderRadius.vertical(
top: separated || isAfterSeparated
? const Radius.circular(16.0)
: (index == 0
? const Radius.circular(16.0)
: const Radius.circular(8.0)),
bottom: separated || isBeforeSeparated
? const Radius.circular(16.0)
: (index + 1 == len
? const Radius.circular(16.0)
: const Radius.circular(8.0)),
),
),
child: wrappedItem,
),
)
: wrappedItem);
}

View File

@@ -0,0 +1,19 @@
import 'package:refilc/ui/date_widget.dart';
import 'package:refilc_kreta_api/models/absence.dart';
import 'package:refilc_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;
}

View File

@@ -0,0 +1,24 @@
import 'package:refilc/models/ad.dart';
import 'package:refilc/ui/date_widget.dart';
import 'package:refilc_mobile_ui/common/widgets/ad/ad_viewable.dart' as mobile;
List<DateWidget> getWidgets(List<Ad> providerAds) {
List<DateWidget> items = [];
if (providerAds.isNotEmpty) {
for (var ad in providerAds) {
if (ad.date.isBefore(DateTime.now()) &&
ad.expireDate.isAfter(DateTime.now())) {
providerAds.sort((a, b) => -a.date.compareTo(b.date));
items.add(DateWidget(
key: ad.description,
date: ad.date,
widget: mobile.AdViewable(ad),
));
}
}
}
return items;
}

View File

@@ -0,0 +1,29 @@
import 'package:refilc/ui/date_widget.dart';
import 'package:refilc_kreta_api/models/grade.dart';
import 'package:refilc_mobile_ui/common/widgets/cretification/certification_card.dart'
as mobile;
import 'package:uuid/uuid.dart';
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,
key: 'certification${const Uuid().v4()}',
widget: mobile.CertificationCard(
grades,
gradeType: gradeType,
),
));
}
}
return items;
}

View File

@@ -0,0 +1,16 @@
import 'package:refilc/ui/date_widget.dart';
import 'package:refilc_kreta_api/models/event.dart';
import 'package:refilc_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;
}

View File

@@ -0,0 +1,16 @@
import 'package:refilc/ui/date_widget.dart';
import 'package:refilc_kreta_api/models/exam.dart';
import 'package:refilc_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;
}

View File

@@ -0,0 +1,53 @@
import 'package:refilc/ui/date_widget.dart';
import 'package:refilc/utils/platform.dart';
import 'package:refilc_kreta_api/models/grade.dart';
import 'package:refilc_mobile_ui/common/widgets/grade/grade_viewable.dart'
as mobile;
import 'package:refilc_mobile_ui/common/widgets/grade/new_grades.dart'
as mobile;
import 'package:refilc_desktop_ui/common/widgets/grade/grade_viewable.dart'
as desktop;
List<DateWidget> getWidgets(
List<Grade> providerGrades, DateTime? lastSeenDate) {
List<DateWidget> items = [];
for (var grade in providerGrades) {
final surprise =
(!(lastSeenDate != null && grade.date.isAfter(lastSeenDate)) ||
grade.value.value == 0);
if (grade.type == GradeType.midYear && surprise) {
items.add(DateWidget(
key: grade.id,
date: grade.date,
widget: PlatformUtils.isMobile
? mobile.GradeViewable(grade)
: desktop.GradeViewable(grade),
));
}
}
return items;
}
List<DateWidget> getNewWidgets(
List<Grade> providerGrades, DateTime? lastSeenDate) {
List<DateWidget> items = [];
List<Grade> newGrades = [];
for (var grade in providerGrades) {
final surprise =
!(lastSeenDate != null && !grade.date.isAfter(lastSeenDate)) &&
grade.value.value != 0 &&
grade.value.weight != 0;
if (grade.type == GradeType.midYear && surprise) {
newGrades.add(grade);
}
}
newGrades.sort((a, b) => a.date.compareTo(b.date));
if (newGrades.isNotEmpty) {
items.add(DateWidget(
key: newGrades.last.id,
date: newGrades.last.date,
widget: mobile.NewGradesSurprise(newGrades),
));
}
return items;
}

View File

@@ -0,0 +1,21 @@
import 'package:refilc/ui/date_widget.dart';
import 'package:refilc_kreta_api/models/homework.dart';
import 'package:refilc_mobile_ui/common/widgets/homework/homework_viewable.dart'
as mobile;
import 'package:flutter/material.dart';
List<DateWidget> getWidgets(
List<Homework> providerHomework, BuildContext context) {
List<DateWidget> items = [];
for (var homework in providerHomework) {
items.add(DateWidget(
key: homework.id,
date: homework.deadline.year != 0 ? homework.deadline : homework.date,
widget: mobile.HomeworkViewable(
homework,
),
));
}
return items;
}

View File

@@ -0,0 +1,19 @@
import 'package:refilc/ui/date_widget.dart';
import 'package:refilc_kreta_api/models/lesson.dart';
import 'package:refilc_mobile_ui/common/widgets/lesson/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: DateTime(lesson.date.year, lesson.date.month, lesson.date.day,
lesson.start.hour, lesson.start.minute),
widget: mobile.ChangedLessonViewable(lesson),
));
});
return items;
}

View File

@@ -0,0 +1,25 @@
import 'package:refilc/ui/date_widget.dart';
import 'package:refilc/ui/filter/widgets/notes.dart' as note_filter;
import 'package:refilc/ui/filter/widgets/events.dart' as event_filter;
import 'package:refilc_kreta_api/models/event.dart';
import 'package:refilc_kreta_api/models/message.dart';
import 'package:refilc_kreta_api/models/note.dart';
import 'package:refilc_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;
}

View File

@@ -0,0 +1,35 @@
import 'package:refilc/utils/format.dart';
import 'package:refilc/ui/date_widget.dart';
import 'package:refilc_kreta_api/models/lesson.dart';
import 'package:refilc_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;
}

View File

@@ -0,0 +1,16 @@
import 'package:refilc/ui/date_widget.dart';
import 'package:refilc_kreta_api/models/note.dart';
import 'package:refilc_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;
}

View File

@@ -0,0 +1,11 @@
import 'package:refilc/models/release.dart';
import 'package:refilc/ui/date_widget.dart';
import 'package:refilc_mobile_ui/common/widgets/update/update_viewable.dart'
as mobile;
DateWidget getWidget(Release providerRelease) {
return DateWidget(
date: DateTime.now(),
widget: mobile.UpdateViewable(providerRelease),
);
}

View File

@@ -0,0 +1,151 @@
// FROM: https://pub.dev/packages/flutter_colorpicker
// FROM: https://pub.dev/packages/flutter_colorpicker
// FROM: https://pub.dev/packages/flutter_colorpicker
// FROM: https://pub.dev/packages/flutter_colorpicker
/// Blocky Color Picker
library block_colorpicker;
import 'package:flutter/material.dart';
import 'package:refilc/theme/colors/accent.dart';
import 'utils.dart';
/// Child widget for layout builder.
typedef PickerItem = Widget Function(Color color);
/// Customize the layout.
typedef PickerLayoutBuilder = Widget Function(
BuildContext context, List<Color> colors, PickerItem child);
/// Customize the item shape.
typedef PickerItemBuilder = Widget Function(
Color color, bool isCurrentColor, void Function() changeColor);
// Provide a list of colors for block color picker.
// const List<Color> _defaultColors = [
// Colors.red,
// Colors.pink,
// Colors.purple,
// Colors.deepPurple,
// Colors.indigo,
// Colors.blue,
// Colors.lightBlue,
// Colors.cyan,
// Colors.teal,
// Colors.green,
// Colors.lightGreen,
// Colors.lime,
// Colors.yellow,
// Colors.amber,
// Colors.orange,
// Colors.deepOrange,
// Colors.brown,
// Colors.grey,
// Colors.blueGrey,
// Colors.black,
// ];
// Provide a layout for [BlockPicker].
Widget _defaultLayoutBuilder(
BuildContext context, List<Color> colors, PickerItem child) {
Orientation orientation = MediaQuery.of(context).orientation;
return SizedBox(
width: 300,
height: orientation == Orientation.portrait ? 360 : 200,
child: GridView.count(
crossAxisCount: orientation == Orientation.portrait ? 4 : 6,
crossAxisSpacing: 5,
mainAxisSpacing: 5,
children: [for (Color color in colors) child(color)],
),
);
}
// Provide a shape for [BlockPicker].
Widget _defaultItemBuilder(
Color color, bool isCurrentColor, void Function() changeColor) {
return Container(
margin: const EdgeInsets.all(7),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: color,
boxShadow: [
BoxShadow(
color: color.withOpacity(0.8),
offset: const Offset(1, 2),
blurRadius: 5)
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: changeColor,
borderRadius: BorderRadius.circular(50),
child: AnimatedOpacity(
duration: const Duration(milliseconds: 210),
opacity: isCurrentColor ? 1 : 0,
child: Icon(Icons.done,
color: useWhiteForeground(color) ? Colors.white : Colors.black),
),
),
),
);
}
// The blocky color picker you can alter the layout and shape.
class BlockPicker extends StatefulWidget {
BlockPicker({
super.key,
required this.pickerColor,
required this.onColorChanged,
this.useInShowDialog = true,
this.layoutBuilder = _defaultLayoutBuilder,
this.itemBuilder = _defaultItemBuilder,
});
final Color? pickerColor;
final ValueChanged<Color> onColorChanged;
final List<Color> availableColors = accentColorMap.values.toList();
final bool useInShowDialog;
final PickerLayoutBuilder layoutBuilder;
final PickerItemBuilder itemBuilder;
@override
State<StatefulWidget> createState() => _BlockPickerState();
}
class _BlockPickerState extends State<BlockPicker> {
Color? _currentColor;
@override
void initState() {
_currentColor = widget.pickerColor;
super.initState();
}
void changeColor(Color color) {
setState(() => _currentColor = color);
widget.onColorChanged(color);
}
@override
Widget build(BuildContext context) {
return widget.layoutBuilder(
context,
widget.availableColors,
(Color color) => widget.itemBuilder(
color,
(_currentColor != null &&
(widget.useInShowDialog ? true : widget.pickerColor != null))
? (_currentColor?.value == color.value) &&
(widget.useInShowDialog
? true
: widget.pickerColor?.value == color.value)
: false,
() => changeColor(color),
),
);
}
}

View File

@@ -0,0 +1,470 @@
// FROM: https://pub.dev/packages/flutter_colorpicker
// FROM: https://pub.dev/packages/flutter_colorpicker
// FROM: https://pub.dev/packages/flutter_colorpicker
// FROM: https://pub.dev/packages/flutter_colorpicker
/// HSV(HSB)/HSL Color Picker example
///
/// You can create your own layout by importing `picker.dart`.
// ignore_for_file: use_build_context_synchronously
library hsv_picker;
import 'package:refilc/models/shared_theme.dart';
import 'package:refilc_mobile_ui/common/custom_snack_bar.dart';
import 'package:refilc_kreta_api/providers/share_provider.dart';
import 'package:refilc/ui/flutter_colorpicker/block_picker.dart';
import 'package:refilc/ui/flutter_colorpicker/palette.dart';
import 'package:refilc/ui/flutter_colorpicker/utils.dart';
import 'package:refilc_mobile_ui/screens/settings/theme_screen.dart';
import 'package:refilc_mobile_ui/screens/settings/theme_screen.i18n.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:refilc/theme/colors/colors.dart';
import 'package:provider/provider.dart';
class FilcColorPicker extends StatefulWidget {
const FilcColorPicker({
super.key,
required this.colorMode,
required this.pickerColor,
required this.onColorChanged,
required this.onColorChangeEnd,
this.pickerHsvColor,
this.onHsvColorChanged,
this.paletteType = PaletteType.hsvWithHue,
this.enableAlpha = true,
@Deprecated('Use empty list in [labelTypes] to disable label.')
this.showLabel = true,
this.labelTypes = const [
ColorLabelType.rgb,
ColorLabelType.hsv,
ColorLabelType.hsl
],
@Deprecated(
'Use Theme.of(context).textTheme.bodyText1 & 2 to alter text style.')
this.labelTextStyle,
this.displayThumbColor = false,
this.portraitOnly = false,
this.colorPickerWidth = 300.0,
this.pickerAreaHeightPercent = 1.0,
this.pickerAreaBorderRadius = const BorderRadius.all(Radius.zero),
this.hexInputBar = false,
this.hexInputController,
this.colorHistory,
this.onHistoryChanged,
required this.onThemeIdProvided,
});
final CustomColorMode colorMode;
final Color pickerColor;
final ValueChanged<Color> onColorChanged;
final void Function(Color color, {bool? adaptive}) onColorChangeEnd;
final HSVColor? pickerHsvColor;
final ValueChanged<HSVColor>? onHsvColorChanged;
final PaletteType paletteType;
final bool enableAlpha;
final bool showLabel;
final List<ColorLabelType> labelTypes;
final TextStyle? labelTextStyle;
final bool displayThumbColor;
final bool portraitOnly;
final double colorPickerWidth;
final double pickerAreaHeightPercent;
final BorderRadius pickerAreaBorderRadius;
final bool hexInputBar;
final TextEditingController? hexInputController;
final List<Color>? colorHistory;
final ValueChanged<List<Color>>? onHistoryChanged;
final void Function(SharedTheme theme) onThemeIdProvided;
@override
FilcColorPickerState createState() => FilcColorPickerState();
}
class FilcColorPickerState extends State<FilcColorPicker> {
final idController = TextEditingController();
late final ShareProvider shareProvider;
HSVColor currentHsvColor = const HSVColor.fromAHSV(0.0, 0.0, 0.0, 0.0);
List<Color> colorHistory = [];
bool isAdvancedView = false;
@override
void initState() {
currentHsvColor = (widget.pickerHsvColor != null)
? widget.pickerHsvColor as HSVColor
: HSVColor.fromColor(widget.pickerColor);
// If there's no initial text in `hexInputController`,
if (widget.hexInputController?.text.isEmpty == true) {
// set it to the current's color HEX value.
widget.hexInputController?.text = colorToHex(
currentHsvColor.toColor(),
enableAlpha: widget.enableAlpha,
);
}
// Listen to the text input, If there is an `hexInputController` provided.
widget.hexInputController?.addListener(colorPickerTextInputListener);
if (widget.colorHistory != null && widget.onHistoryChanged != null) {
colorHistory = widget.colorHistory ?? [];
}
shareProvider = Provider.of<ShareProvider>(context, listen: false);
super.initState();
}
@override
void didUpdateWidget(FilcColorPicker oldWidget) {
super.didUpdateWidget(oldWidget);
currentHsvColor = (widget.pickerHsvColor != null)
? widget.pickerHsvColor as HSVColor
: HSVColor.fromColor(widget.pickerColor);
}
void colorPickerTextInputListener() {
// It can't be null really, since it's only listening if the controller
// is provided, but it may help to calm the Dart analyzer in the future.
if (widget.hexInputController == null) return;
// If a user is inserting/typing any text — try to get the color value from it,
// and interpret its transparency, dependent on the widget's settings.
final Color? color = colorFromHex(widget.hexInputController!.text,
enableAlpha: widget.enableAlpha);
// If it's the valid color:
if (color != null) {
// set it as the current color and
setState(() => currentHsvColor = HSVColor.fromColor(color));
// notify with a callback.
widget.onColorChanged(color);
if (widget.onHsvColorChanged != null) {
widget.onHsvColorChanged!(currentHsvColor);
}
}
}
@override
void dispose() {
widget.hexInputController?.removeListener(colorPickerTextInputListener);
super.dispose();
}
Widget colorPickerSlider(TrackType trackType) {
return ColorPickerSlider(
trackType,
currentHsvColor,
(HSVColor color) {
// Update text in `hexInputController` if provided.
widget.hexInputController?.text =
colorToHex(color.toColor(), enableAlpha: widget.enableAlpha);
setState(() => currentHsvColor = color);
widget.onColorChanged(currentHsvColor.toColor());
if (widget.onHsvColorChanged != null) {
widget.onHsvColorChanged!(currentHsvColor);
}
},
() => widget.onColorChangeEnd(currentHsvColor.toColor()),
(p) {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
"Move the ${p == 0 ? 'Saturation (second)' : 'Value (third)'} slider first.",
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.of(context).text,
fontWeight: FontWeight.w600)),
backgroundColor: AppColors.of(context).background));
},
displayThumbColor: widget.displayThumbColor,
);
}
void onColorChanging(HSVColor color) {
// Update text in `hexInputController` if provided.
widget.hexInputController?.text =
colorToHex(color.toColor(), enableAlpha: widget.enableAlpha);
setState(() => currentHsvColor = color);
widget.onColorChanged(currentHsvColor.toColor());
if (widget.onHsvColorChanged != null) {
widget.onHsvColorChanged!(currentHsvColor);
}
}
@override
Widget build(BuildContext context) {
if (MediaQuery.of(context).orientation == Orientation.portrait ||
widget.portraitOnly) {
return Column(
children: [
if (widget.colorMode != CustomColorMode.theme &&
widget.colorMode != CustomColorMode.enterId)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 12.0, right: 12.0),
child: SizedBox(
height: 45.0,
width: double.infinity,
child: colorPickerSlider(TrackType.hue),
),
),
Padding(
padding: const EdgeInsets.only(left: 12.0, right: 12.0),
child: SizedBox(
height: 45.0,
width: double.infinity,
child: colorPickerSlider(TrackType.saturation),
),
),
if (isAdvancedView)
Padding(
padding: const EdgeInsets.only(left: 12.0, right: 12.0),
child: SizedBox(
height: 45.0,
width: double.infinity,
child: colorPickerSlider(TrackType.value),
),
),
],
),
),
if (isAdvancedView &&
widget.colorMode != CustomColorMode.theme &&
widget.colorMode != CustomColorMode.enterId)
Padding(
padding: const EdgeInsets.only(bottom: 6.0),
child: ColorPickerInput(
currentHsvColor.toColor(),
(Color color) {
setState(() => currentHsvColor = HSVColor.fromColor(color));
widget.onColorChanged(currentHsvColor.toColor());
if (widget.onHsvColorChanged != null) {
widget.onHsvColorChanged!(currentHsvColor);
}
},
enableAlpha: false,
embeddedText: false,
),
),
if (widget.colorMode == CustomColorMode.enterId)
Padding(
padding:
const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),
child: Column(
children: [
TextField(
autocorrect: false,
autofocus: true,
onEditingComplete: () async {
SharedTheme? theme = await shareProvider.getThemeById(
context,
id: idController.text.replaceAll(' ', ''),
);
if (theme != null) {
widget.onThemeIdProvided(theme);
idController.clear();
} else {
ScaffoldMessenger.of(context).showSnackBar(
CustomSnackBar(
content: Text("theme_not_found".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red,
context: context,
),
);
}
},
controller: idController,
decoration: InputDecoration(
hintText: 'theme_id'.i18n,
),
),
// MaterialActionButton(
// child: Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// Text('check_id'.i18n),
// ],
// ),
// backgroundColor: AppColors.of(context).filc,
// onPressed: () {},
// ),
],
),
),
if (widget.colorMode != CustomColorMode.enterId)
SizedBox(
height: 70 * (widget.colorMode == CustomColorMode.theme ? 2 : 1),
child: BlockPicker(
pickerColor: Colors.red,
layoutBuilder: (context, colors, child) {
return GridView.count(
shrinkWrap: true,
crossAxisCount:
widget.colorMode == CustomColorMode.theme ? 2 : 1,
scrollDirection: Axis.horizontal,
crossAxisSpacing: 15,
physics: const BouncingScrollPhysics(),
mainAxisSpacing: 15,
padding: const EdgeInsets.symmetric(
horizontal: 12.0, vertical: 8.0),
children: List.generate(
colors.toSet().length +
(widget.colorMode == CustomColorMode.theme ? 1 : 0),
(index) {
if (widget.colorMode == CustomColorMode.theme) {
if (index == 0) {
return GestureDetector(
onTap: () => widget.onColorChangeEnd(
Colors.transparent,
adaptive: true),
child: ColorIndicator(
HSVColor.fromColor(
const Color.fromARGB(255, 255, 238, 177)),
icon: CupertinoIcons.wand_stars,
currentHsvColor: currentHsvColor,
width: 30,
height: 30,
adaptive: true),
);
}
index--;
}
return GestureDetector(
onTap: () => widget.onColorChangeEnd(colors[index]),
child: ColorIndicator(HSVColor.fromColor(colors[index]),
currentHsvColor: currentHsvColor,
width: 30,
height: 30),
);
}),
);
},
onColorChanged: (c) => {},
),
),
if (widget.colorMode != CustomColorMode.theme &&
widget.colorMode != CustomColorMode.enterId)
Material(
color: Colors.transparent,
child: InkWell(
customBorder: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
onTap: () => setState(() {
isAdvancedView = !isAdvancedView;
}),
child: Padding(
padding:
const EdgeInsets.only(left: 8.0, right: 8.0, top: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Switch(
onChanged: (v) => setState(() => isAdvancedView = v),
value: isAdvancedView,
),
const SizedBox(width: 12.0),
Text(
"advanced".i18n,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.0,
color: AppColors.of(context)
.text
.withOpacity(isAdvancedView ? 1.0 : .5),
),
),
],
),
),
),
),
],
);
} else {
return Row(
children: [
//SizedBox(width: widget.colorPickerWidth, height: widget.colorPickerWidth * widget.pickerAreaHeightPercent, child: colorPicker()),
Column(
children: [
Row(
children: <Widget>[
const SizedBox(width: 20.0),
GestureDetector(
onTap: () => setState(() {
if (widget.onHistoryChanged != null &&
!colorHistory.contains(currentHsvColor.toColor())) {
colorHistory.add(currentHsvColor.toColor());
widget.onHistoryChanged!(colorHistory);
}
}),
child: ColorIndicator(currentHsvColor),
),
Column(
children: <Widget>[
//SizedBox(height: 40.0, width: 260.0, child: sliderByPaletteType()),
if (widget.enableAlpha)
SizedBox(
height: 40.0,
width: 260.0,
child: colorPickerSlider(TrackType.alpha)),
],
),
const SizedBox(width: 10.0),
],
),
if (colorHistory.isNotEmpty)
SizedBox(
width: widget.colorPickerWidth,
height: 50,
child: ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
for (Color color in colorHistory)
Padding(
key: Key(color.hashCode.toString()),
padding: const EdgeInsets.fromLTRB(15, 18, 0, 0),
child: Center(
child: GestureDetector(
onTap: () =>
onColorChanging(HSVColor.fromColor(color)),
onLongPress: () {
if (colorHistory.remove(color)) {
widget.onHistoryChanged!(colorHistory);
setState(() {});
}
},
child: ColorIndicator(HSVColor.fromColor(color),
width: 30, height: 30),
),
),
),
const SizedBox(width: 15),
]),
),
const SizedBox(height: 20.0),
if (widget.hexInputBar)
ColorPickerInput(
currentHsvColor.toColor(),
(Color color) {
setState(() => currentHsvColor = HSVColor.fromColor(color));
widget.onColorChanged(currentHsvColor.toColor());
if (widget.onHsvColorChanged != null) {
widget.onHsvColorChanged!(currentHsvColor);
}
},
enableAlpha: widget.enableAlpha,
embeddedText: false,
),
const SizedBox(height: 5),
],
),
],
);
}
}
}

View File

@@ -0,0 +1,174 @@
// FROM: https://pub.dev/packages/flutter_colorpicker
// FROM: https://pub.dev/packages/flutter_colorpicker
// FROM: https://pub.dev/packages/flutter_colorpicker
// FROM: https://pub.dev/packages/flutter_colorpicker
import 'dart:ui';
/// X11 Colors
///
/// https://en.wikipedia.org/wiki/X11_color_names
const Map<String, Color> x11Colors = {
'aliceblue': Color(0xfff0f8ff),
'antiquewhite': Color(0xfffaebd7),
'aqua': Color(0xff00ffff),
'aquamarine': Color(0xff7fffd4),
'azure': Color(0xfff0ffff),
'beige': Color(0xfff5f5dc),
'bisque': Color(0xffffe4c4),
'black': Color(0xff000000),
'blanchedalmond': Color(0xffffebcd),
'blue': Color(0xff0000ff),
'blueviolet': Color(0xff8a2be2),
'brown': Color(0xffa52a2a),
'burlywood': Color(0xffdeb887),
'cadetblue': Color(0xff5f9ea0),
'chartreuse': Color(0xff7fff00),
'chocolate': Color(0xffd2691e),
'coral': Color(0xffff7f50),
'cornflower': Color(0xff6495ed),
'cornflowerblue': Color(0xff6495ed),
'cornsilk': Color(0xfffff8dc),
'crimson': Color(0xffdc143c),
'cyan': Color(0xff00ffff),
'darkblue': Color(0xff00008b),
'darkcyan': Color(0xff008b8b),
'darkgoldenrod': Color(0xffb8860b),
'darkgray': Color(0xffa9a9a9),
'darkgreen': Color(0xff006400),
'darkgrey': Color(0xffa9a9a9),
'darkkhaki': Color(0xffbdb76b),
'darkmagenta': Color(0xff8b008b),
'darkolivegreen': Color(0xff556b2f),
'darkorange': Color(0xffff8c00),
'darkorchid': Color(0xff9932cc),
'darkred': Color(0xff8b0000),
'darksalmon': Color(0xffe9967a),
'darkseagreen': Color(0xff8fbc8f),
'darkslateblue': Color(0xff483d8b),
'darkslategray': Color(0xff2f4f4f),
'darkslategrey': Color(0xff2f4f4f),
'darkturquoise': Color(0xff00ced1),
'darkviolet': Color(0xff9400d3),
'deeppink': Color(0xffff1493),
'deepskyblue': Color(0xff00bfff),
'dimgray': Color(0xff696969),
'dimgrey': Color(0xff696969),
'dodgerblue': Color(0xff1e90ff),
'firebrick': Color(0xffb22222),
'floralwhite': Color(0xfffffaf0),
'forestgreen': Color(0xff228b22),
'fuchsia': Color(0xffff00ff),
'gainsboro': Color(0xffdcdcdc),
'ghostwhite': Color(0xfff8f8ff),
'gold': Color(0xffffd700),
'goldenrod': Color(0xffdaa520),
'gray': Color(0xff808080),
'green': Color(0xff008000),
'greenyellow': Color(0xffadff2f),
'grey': Color(0xff808080),
'honeydew': Color(0xfff0fff0),
'hotpink': Color(0xffff69b4),
'indianred': Color(0xffcd5c5c),
'indigo': Color(0xff4b0082),
'ivory': Color(0xfffffff0),
'khaki': Color(0xfff0e68c),
'laserlemon': Color(0xffffff54),
'lavender': Color(0xffe6e6fa),
'lavenderblush': Color(0xfffff0f5),
'lawngreen': Color(0xff7cfc00),
'lemonchiffon': Color(0xfffffacd),
'lightblue': Color(0xffadd8e6),
'lightcoral': Color(0xfff08080),
'lightcyan': Color(0xffe0ffff),
'lightgoldenrod': Color(0xfffafad2),
'lightgoldenrodyellow': Color(0xfffafad2),
'lightgray': Color(0xffd3d3d3),
'lightgreen': Color(0xff90ee90),
'lightgrey': Color(0xffd3d3d3),
'lightpink': Color(0xffffb6c1),
'lightsalmon': Color(0xffffa07a),
'lightseagreen': Color(0xff20b2aa),
'lightskyblue': Color(0xff87cefa),
'lightslategray': Color(0xff778899),
'lightslategrey': Color(0xff778899),
'lightsteelblue': Color(0xffb0c4de),
'lightyellow': Color(0xffffffe0),
'lime': Color(0xff00ff00),
'limegreen': Color(0xff32cd32),
'linen': Color(0xfffaf0e6),
'magenta': Color(0xffff00ff),
'maroon': Color(0xff800000),
'maroon2': Color(0xff7f0000),
'maroon3': Color(0xffb03060),
'mediumaquamarine': Color(0xff66cdaa),
'mediumblue': Color(0xff0000cd),
'mediumorchid': Color(0xffba55d3),
'mediumpurple': Color(0xff9370db),
'mediumseagreen': Color(0xff3cb371),
'mediumslateblue': Color(0xff7b68ee),
'mediumspringgreen': Color(0xff00fa9a),
'mediumturquoise': Color(0xff48d1cc),
'mediumvioletred': Color(0xffc71585),
'midnightblue': Color(0xff191970),
'mintcream': Color(0xfff5fffa),
'mistyrose': Color(0xffffe4e1),
'moccasin': Color(0xffffe4b5),
'navajowhite': Color(0xffffdead),
'navy': Color(0xff000080),
'oldlace': Color(0xfffdf5e6),
'olive': Color(0xff808000),
'olivedrab': Color(0xff6b8e23),
'orange': Color(0xffffa500),
'orangered': Color(0xffff4500),
'orchid': Color(0xffda70d6),
'palegoldenrod': Color(0xffeee8aa),
'palegreen': Color(0xff98fb98),
'paleturquoise': Color(0xffafeeee),
'palevioletred': Color(0xffdb7093),
'papayawhip': Color(0xffffefd5),
'peachpuff': Color(0xffffdab9),
'peru': Color(0xffcd853f),
'pink': Color(0xffffc0cb),
'plum': Color(0xffdda0dd),
'powderblue': Color(0xffb0e0e6),
'purple': Color(0xff800080),
'purple2': Color(0xff7f007f),
'purple3': Color(0xffa020f0),
'rebeccapurple': Color(0xff663399),
'red': Color(0xffff0000),
'rosybrown': Color(0xffbc8f8f),
'royalblue': Color(0xff4169e1),
'saddlebrown': Color(0xff8b4513),
'salmon': Color(0xfffa8072),
'sandybrown': Color(0xfff4a460),
'seagreen': Color(0xff2e8b57),
'seashell': Color(0xfffff5ee),
'sienna': Color(0xffa0522d),
'silver': Color(0xffc0c0c0),
'skyblue': Color(0xff87ceeb),
'slateblue': Color(0xff6a5acd),
'slategray': Color(0xff708090),
'slategrey': Color(0xff708090),
'snow': Color(0xfffffafa),
'springgreen': Color(0xff00ff7f),
'steelblue': Color(0xff4682b4),
'tan': Color(0xffd2b48c),
'teal': Color(0xff008080),
'thistle': Color(0xffd8bfd8),
'tomato': Color(0xffff6347),
'turquoise': Color(0xff40e0d0),
'violet': Color(0xffee82ee),
'wheat': Color(0xfff5deb3),
'white': Color(0xffffffff),
'whitesmoke': Color(0xfff5f5f5),
'yellow': Color(0xffffff00),
'yellowgreen': Color(0xff9acd32),
};
Color? colorFromName(String val) => x11Colors[val.trim().replaceAll(' ', '').toLowerCase()];
extension ColorExtension on String {
Color? toColor() => colorFromName(this);
}

View File

@@ -0,0 +1,821 @@
// FROM: https://pub.dev/packages/flutter_colorpicker
// FROM: https://pub.dev/packages/flutter_colorpicker
// FROM: https://pub.dev/packages/flutter_colorpicker
// FROM: https://pub.dev/packages/flutter_colorpicker
// ignore: dangling_library_doc_comments
/// The components of HSV Color Picker
///
/// Try to create a Color Picker with other layout on your own :)
import 'package:refilc/models/settings.dart';
import 'package:refilc/theme/colors/accent.dart';
import 'package:refilc/theme/colors/colors.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'utils.dart';
/// Palette types for color picker area widget.
enum PaletteType {
hsv,
hsvWithHue,
hsvWithValue,
hsvWithSaturation,
hsl,
hslWithHue,
hslWithLightness,
hslWithSaturation,
rgbWithBlue,
rgbWithGreen,
rgbWithRed,
hueWheel,
}
/// Track types for slider picker.
enum TrackType {
hue,
saturation,
saturationForHSL,
value,
lightness,
red,
green,
blue,
alpha,
}
enum FilcTrackType {
hue,
saturation,
value,
}
/// Color information label type.
enum ColorLabelType { hex, rgb, hsv, hsl }
/// Types for slider picker widget.
enum ColorModel { rgb, hsv, hsl }
// enum ColorSpace { rgb, hsv, hsl, hsp, okhsv, okhsl, xyz, yuv, lab, lch, cmyk }
/// Painter for SV mixture.
class HSVWithHueColorPainter extends CustomPainter {
const HSVWithHueColorPainter(this.hsvColor, {this.pointerColor});
final HSVColor hsvColor;
final Color? pointerColor;
@override
void paint(Canvas canvas, Size size) {
final Rect rect = Offset.zero & size;
const Gradient gradientV = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.white, Colors.black],
);
final Gradient gradientH = LinearGradient(
colors: [
Colors.white,
HSVColor.fromAHSV(1.0, hsvColor.hue, 1.0, 1.0).toColor(),
],
);
canvas.drawRect(rect, Paint()..shader = gradientV.createShader(rect));
canvas.drawRect(
rect,
Paint()
..blendMode = BlendMode.multiply
..shader = gradientH.createShader(rect),
);
canvas.drawCircle(
Offset(
size.width * hsvColor.saturation, size.height * (1 - hsvColor.value)),
size.height * 0.04,
Paint()
..color = pointerColor ??
(useWhiteForeground(hsvColor.toColor())
? Colors.white
: Colors.black)
..strokeWidth = 1.5
..style = PaintingStyle.stroke,
);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
class _SliderLayout extends MultiChildLayoutDelegate {
static const String track = 'track';
static const String thumb = 'thumb';
static const String gestureContainer = 'gesturecontainer';
@override
void performLayout(Size size) {
layoutChild(
track,
BoxConstraints.tightFor(
width: size.width + 3,
height: size.height / 1.5,
),
);
positionChild(track, const Offset(-2.0, 0));
layoutChild(
thumb,
const BoxConstraints.tightFor(width: 5.5, height: 10.5),
);
positionChild(thumb, Offset(0.0, (size.height / 1.5) / 2 - 4.5));
layoutChild(
gestureContainer,
BoxConstraints.tightFor(width: size.width, height: size.height),
);
positionChild(gestureContainer, Offset.zero);
}
@override
bool shouldRelayout(_SliderLayout oldDelegate) => false;
}
/// Painter for all kinds of track types.
class TrackPainter extends CustomPainter {
const TrackPainter(this.trackType, this.hsvColor);
final TrackType trackType;
final HSVColor hsvColor;
@override
void paint(Canvas canvas, Size size) {
final Rect rect = Offset.zero & size;
if (trackType == TrackType.alpha) {
final Size chessSize = Size(size.height / 2, size.height / 2);
Paint chessPaintB = Paint()..color = const Color(0xffcccccc);
Paint chessPaintW = Paint()..color = Colors.white;
List.generate((size.height / chessSize.height).round(), (int y) {
List.generate((size.width / chessSize.width).round(), (int x) {
canvas.drawRect(
Offset(chessSize.width * x, chessSize.width * y) & chessSize,
(x + y) % 2 != 0 ? chessPaintW : chessPaintB,
);
});
});
}
switch (trackType) {
case TrackType.hue:
final List<Color> colors = [
const HSVColor.fromAHSV(1.0, 0.0, 1.0, 1.0).toColor(),
const HSVColor.fromAHSV(1.0, 60.0, 1.0, 1.0).toColor(),
const HSVColor.fromAHSV(1.0, 120.0, 1.0, 1.0).toColor(),
const HSVColor.fromAHSV(1.0, 180.0, 1.0, 1.0).toColor(),
const HSVColor.fromAHSV(1.0, 240.0, 1.0, 1.0).toColor(),
const HSVColor.fromAHSV(1.0, 300.0, 1.0, 1.0).toColor(),
const HSVColor.fromAHSV(1.0, 360.0, 1.0, 1.0).toColor(),
];
Gradient gradient = LinearGradient(colors: colors);
canvas.drawRect(rect, Paint()..shader = gradient.createShader(rect));
break;
case TrackType.saturation:
final List<Color> colors = [
HSVColor.fromAHSV(1.0, hsvColor.hue, 0.0, 1.0).toColor(),
HSVColor.fromAHSV(1.0, hsvColor.hue, 1.0, 1.0).toColor(),
];
Gradient gradient = LinearGradient(colors: colors);
canvas.drawRect(rect, Paint()..shader = gradient.createShader(rect));
break;
case TrackType.saturationForHSL:
final List<Color> colors = [
HSLColor.fromAHSL(1.0, hsvColor.hue, 0.0, 0.5).toColor(),
HSLColor.fromAHSL(1.0, hsvColor.hue, 1.0, 0.5).toColor(),
];
Gradient gradient = LinearGradient(colors: colors);
canvas.drawRect(rect, Paint()..shader = gradient.createShader(rect));
break;
case TrackType.value:
final List<Color> colors = [
HSVColor.fromAHSV(1.0, hsvColor.hue, 1.0, 0.0).toColor(),
HSVColor.fromAHSV(1.0, hsvColor.hue, 1.0, 1.0).toColor(),
];
Gradient gradient = LinearGradient(colors: colors);
canvas.drawRect(rect, Paint()..shader = gradient.createShader(rect));
break;
case TrackType.lightness:
final List<Color> colors = [
HSLColor.fromAHSL(1.0, hsvColor.hue, 1.0, 0.0).toColor(),
HSLColor.fromAHSL(1.0, hsvColor.hue, 1.0, 0.5).toColor(),
HSLColor.fromAHSL(1.0, hsvColor.hue, 1.0, 1.0).toColor(),
];
Gradient gradient = LinearGradient(colors: colors);
canvas.drawRect(rect, Paint()..shader = gradient.createShader(rect));
break;
case TrackType.red:
final List<Color> colors = [
hsvColor.toColor().withRed(0).withOpacity(1.0),
hsvColor.toColor().withRed(255).withOpacity(1.0),
];
Gradient gradient = LinearGradient(colors: colors);
canvas.drawRect(rect, Paint()..shader = gradient.createShader(rect));
break;
case TrackType.green:
final List<Color> colors = [
hsvColor.toColor().withGreen(0).withOpacity(1.0),
hsvColor.toColor().withGreen(255).withOpacity(1.0),
];
Gradient gradient = LinearGradient(colors: colors);
canvas.drawRect(rect, Paint()..shader = gradient.createShader(rect));
break;
case TrackType.blue:
final List<Color> colors = [
hsvColor.toColor().withBlue(0).withOpacity(1.0),
hsvColor.toColor().withBlue(255).withOpacity(1.0),
];
Gradient gradient = LinearGradient(colors: colors);
canvas.drawRect(rect, Paint()..shader = gradient.createShader(rect));
break;
case TrackType.alpha:
final List<Color> colors = [
hsvColor.toColor().withOpacity(0.0),
hsvColor.toColor().withOpacity(1.0),
];
Gradient gradient = LinearGradient(colors: colors);
canvas.drawRect(rect, Paint()..shader = gradient.createShader(rect));
break;
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
/// Painter for thumb of slider.
class ThumbPainter extends CustomPainter {
const ThumbPainter({this.thumbColor, this.fullThumbColor = false});
final Color? thumbColor;
final bool fullThumbColor;
@override
void paint(Canvas canvas, Size size) {
canvas.drawShadow(
Path()
..addOval(
Rect.fromCircle(
center: const Offset(0.5, 2.0), radius: size.width * 1.8),
),
Colors.black,
3.0,
true,
);
canvas.drawCircle(
Offset(0.0, size.height * 0.4),
size.height,
Paint()
..color = Colors.white
..style = PaintingStyle.fill);
if (thumbColor != null) {
canvas.drawCircle(
Offset(0.0, size.height * 0.4),
size.height * (fullThumbColor ? 1.0 : 0.65),
Paint()
..color = thumbColor!
..style = PaintingStyle.fill);
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
/// Painter for chess type alpha background in color indicator widget.
class IndicatorPainter extends CustomPainter {
const IndicatorPainter(this.color);
final Color color;
@override
void paint(Canvas canvas, Size size) {
final Size chessSize = Size(size.width / 10, size.height / 10);
final Paint chessPaintB = Paint()..color = const Color(0xFFCCCCCC);
final Paint chessPaintW = Paint()..color = Colors.white;
List.generate((size.height / chessSize.height).round(), (int y) {
List.generate((size.width / chessSize.width).round(), (int x) {
canvas.drawRect(
Offset(chessSize.width * x, chessSize.height * y) & chessSize,
(x + y) % 2 != 0 ? chessPaintW : chessPaintB,
);
});
});
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
size.height / 2,
Paint()
..color = color
..style = PaintingStyle.fill);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
/// Provide hex input wiget for 3/6/8 digits.
class ColorPickerInput extends StatefulWidget {
const ColorPickerInput(
this.color,
this.onColorChanged, {
super.key,
this.enableAlpha = true,
this.embeddedText = false,
this.disable = false,
});
final Color color;
final ValueChanged<Color> onColorChanged;
final bool enableAlpha;
final bool embeddedText;
final bool disable;
@override
ColorPickerInputState createState() => ColorPickerInputState();
}
class ColorPickerInputState extends State<ColorPickerInput> {
TextEditingController textEditingController = TextEditingController();
int inputColor = 0;
@override
void dispose() {
textEditingController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (inputColor != widget.color.value) {
textEditingController.text =
'#${widget.color.red.toRadixString(16).toUpperCase().padLeft(2, '0')}${widget.color.green.toRadixString(16).toUpperCase().padLeft(2, '0')}${widget.color.blue.toRadixString(16).toUpperCase().padLeft(2, '0')}${widget.enableAlpha ? widget.color.alpha.toRadixString(16).toUpperCase().padLeft(2, '0') : ''}';
}
return Padding(
padding: const EdgeInsets.only(top: 6.0, left: 12.0, right: 12.0),
child: SizedBox(
width: double.infinity,
child: TextField(
enabled: !widget.disable,
controller: textEditingController,
style: TextStyle(
fontSize: 18,
color: Theme.of(context).colorScheme.onBackground,
),
inputFormatters: [
UpperCaseTextFormatter(),
FilteringTextInputFormatter.allow(RegExp(kValidHexPattern)),
],
decoration: InputDecoration(
isDense: true,
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0),
borderSide:
const BorderSide(color: Colors.transparent, width: 0.0),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0),
borderSide:
const BorderSide(color: Colors.transparent, width: 0.0),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0),
borderSide:
const BorderSide(color: Colors.transparent, width: 0.0),
),
contentPadding:
const EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0),
fillColor: AppColors.of(context).text.withOpacity(.1),
),
onChanged: (String value) {
String input = value;
if (value.length == 9) {
input = value.split('').getRange(7, 9).join() +
value.split('').getRange(1, 7).join();
}
final Color? color = colorFromHex(input);
if (color != null) {
widget.onColorChanged(color);
inputColor = color.value;
}
},
),
),
);
}
}
/*class ValueColorPickerSlider extends StatefulWidget {
ValueColorPickerSlider(this.trackType, this.initialHsvColor, this.onProgressChanged, this.onColorChangeEnd, {Key? key}) : super(key: key);
final TrackType trackType;
final HSVColor initialHsvColor;
final void Function(double progress) onProgressChanged;
final void Function() onColorChangeEnd;
@override
State<ValueColorPickerSlider> createState() => _ValueColorPickerSliderState();
}
class _ValueColorPickerSliderState extends State<ValueColorPickerSlider> {
HSVColor hsvColor = HSVColor.fromColor(Colors.red);
@override
void initState() {
super.initState();
hsvColor = widget.initialHsvColor;
}
void slideEvent(RenderBox getBox, BoxConstraints box, Offset globalPosition) {
double localDx = getBox.globalToLocal(globalPosition).dx - 15.0;
double progress = localDx.clamp(0.0, box.maxWidth - 30.0) / (box.maxWidth - 30.0);
setState(() {
switch (widget.trackType) {
case TrackType.hue:
hsvColor = hsvColor.withHue(progress * 359);
break;
case TrackType.saturation:
hsvColor = hsvColor.withSaturation(progress);
break;
case TrackType.value:
hsvColor = hsvColor.withValue(progress);
break;
default:
break;
}
});
widget.onProgressChanged(progress);
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints box) {
double thumbOffset = 15.0;
Color thumbColor = Colors.white;
switch (widget.trackType) {
case TrackType.hue:
thumbOffset += (box.maxWidth - 30.0) * hsvColor.hue / 360;
break;
case TrackType.saturation:
thumbOffset += (box.maxWidth - 30.0) * hsvColor.saturation;
break;
case TrackType.value:
thumbOffset += (box.maxWidth - 30.0) * hsvColor.value;
break;
default:
break;
}
return CustomMultiChildLayout(
delegate: _SliderLayout(),
children: <Widget>[
LayoutId(
id: _SliderLayout.track,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(50.0)),
child: CustomPaint(
painter: TrackPainter(
TrackType.values.firstWhere((element) => element == widget.trackType),
hsvColor,
)),
),
),
LayoutId(
id: _SliderLayout.thumb,
child: Transform.translate(
offset: Offset(thumbOffset, 0.0),
child: CustomPaint(
painter: ThumbPainter(
thumbColor: thumbColor,
fullThumbColor: false,
),
),
),
),
LayoutId(
id: _SliderLayout.gestureContainer,
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints box) {
RenderBox? getBox = context.findRenderObject() as RenderBox?;
return GestureDetector(
onPanDown: (DragDownDetails details) => getBox != null ? slideEvent(getBox, box, details.globalPosition) : null,
onPanEnd: (details) => widget.onColorChangeEnd(),
onPanUpdate: (DragUpdateDetails details) => getBox != null ? slideEvent(getBox, box, details.globalPosition) : null,
);
},
),
),
],
);
});
}
}*/
/// 9 track types for slider picker widget.
class ColorPickerSlider extends StatelessWidget {
const ColorPickerSlider(
this.trackType,
this.hsvColor,
this.onColorChanged,
this.onColorChangeEnd,
this.onProblem, {
super.key,
this.displayThumbColor = false,
this.fullThumbColor = false,
});
final TrackType trackType;
final HSVColor hsvColor;
final ValueChanged<HSVColor> onColorChanged;
final void Function() onColorChangeEnd;
final void Function(int v) onProblem;
final bool displayThumbColor;
final bool fullThumbColor;
void slideEvent(RenderBox getBox, BoxConstraints box, Offset globalPosition) {
double localDx = getBox.globalToLocal(globalPosition).dx - 15.0;
double progress =
localDx.clamp(0.0, box.maxWidth - 30.0) / (box.maxWidth - 30.0);
switch (trackType) {
case TrackType.hue:
// 360 is the same as zero
// if set to 360, sliding to end goes to zero
final newColor = hsvColor.withHue(progress * 359);
if (newColor.saturation == 0) {
onProblem(0);
return;
}
onColorChanged(newColor);
break;
case TrackType.saturation:
final newColor = hsvColor.withSaturation(progress);
if (newColor.value == 0) {
onProblem(1);
return;
}
onColorChanged(newColor);
break;
case TrackType.value:
onColorChanged(hsvColor.withValue(progress));
break;
default:
break;
}
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints box) {
double thumbOffset = 15.0;
Color thumbColor;
switch (trackType) {
case TrackType.hue:
thumbOffset += (box.maxWidth - 30.0) * hsvColor.hue / 360;
thumbColor = HSVColor.fromAHSV(1.0, hsvColor.hue, 1.0, 1.0).toColor();
break;
case TrackType.saturation:
thumbOffset += (box.maxWidth - 30.0) * hsvColor.saturation;
thumbColor =
HSVColor.fromAHSV(1.0, hsvColor.hue, hsvColor.saturation, 1.0)
.toColor();
break;
case TrackType.saturationForHSL:
thumbOffset += (box.maxWidth - 30.0) * hsvToHsl(hsvColor).saturation;
thumbColor = HSLColor.fromAHSL(
1.0, hsvColor.hue, hsvToHsl(hsvColor).saturation, 0.5)
.toColor();
break;
case TrackType.value:
thumbOffset += (box.maxWidth - 30.0) * hsvColor.value;
thumbColor = HSVColor.fromAHSV(1.0, hsvColor.hue, 1.0, hsvColor.value)
.toColor();
break;
case TrackType.lightness:
thumbOffset += (box.maxWidth - 30.0) * hsvToHsl(hsvColor).lightness;
thumbColor = HSLColor.fromAHSL(
1.0, hsvColor.hue, 1.0, hsvToHsl(hsvColor).lightness)
.toColor();
break;
case TrackType.red:
thumbOffset += (box.maxWidth - 30.0) * hsvColor.toColor().red / 0xff;
thumbColor = hsvColor.toColor().withOpacity(1.0);
break;
case TrackType.green:
thumbOffset +=
(box.maxWidth - 30.0) * hsvColor.toColor().green / 0xff;
thumbColor = hsvColor.toColor().withOpacity(1.0);
break;
case TrackType.blue:
thumbOffset += (box.maxWidth - 30.0) * hsvColor.toColor().blue / 0xff;
thumbColor = hsvColor.toColor().withOpacity(1.0);
break;
case TrackType.alpha:
thumbOffset += (box.maxWidth - 30.0) * hsvColor.toColor().opacity;
thumbColor = hsvColor.toColor().withOpacity(hsvColor.alpha);
break;
}
return CustomMultiChildLayout(
delegate: _SliderLayout(),
children: <Widget>[
LayoutId(
id: _SliderLayout.track,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(50.0)),
child: CustomPaint(
painter: TrackPainter(
trackType,
hsvColor,
)),
),
),
LayoutId(
id: _SliderLayout.thumb,
child: Transform.translate(
offset: Offset(thumbOffset, 0.0),
child: CustomPaint(
painter: ThumbPainter(
thumbColor: displayThumbColor ? thumbColor : null,
fullThumbColor: fullThumbColor,
),
),
),
),
LayoutId(
id: _SliderLayout.gestureContainer,
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints box) {
RenderBox? getBox = context.findRenderObject() as RenderBox?;
return GestureDetector(
onPanDown: (DragDownDetails details) => getBox != null
? slideEvent(getBox, box, details.globalPosition)
: null,
onPanEnd: (details) {
if ((trackType == TrackType.hue &&
hsvColor.saturation == 0) ||
(trackType == TrackType.saturation &&
hsvColor.value == 0)) {
return;
}
onColorChangeEnd();
},
onPanUpdate: (DragUpdateDetails details) => getBox != null
? slideEvent(getBox, box, details.globalPosition)
: null,
);
},
),
),
],
);
});
}
}
/// Simple round color indicator.
class ColorIndicator extends StatelessWidget {
const ColorIndicator(
this.hsvColor, {
super.key,
this.currentHsvColor,
this.icon,
this.width = 50.0,
this.height = 50.0,
this.adaptive = false,
});
final HSVColor hsvColor;
final HSVColor? currentHsvColor;
final double width;
final double height;
final IconData? icon;
final bool adaptive;
@override
Widget build(BuildContext context) {
Color color = hsvColor.toColor();
return Container(
width: width,
height: height,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: color,
boxShadow: [
BoxShadow(
color: useWhiteForeground(color)
? Colors.white.withOpacity(.5)
: Colors.black.withOpacity(.5),
offset: const Offset(0, 0),
blurRadius: 5)
],
),
child: Material(
color: Colors.transparent,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 210),
opacity: (icon != null || currentHsvColor == hsvColor) &&
(adaptive ||
Provider.of<SettingsProvider>(context, listen: false)
.accentColor !=
AccentColor.adaptive)
? 1
: 0,
child: Icon(icon ?? Icons.done,
color: useWhiteForeground(color) ? Colors.white : Colors.black),
),
),
);
}
}
/// Provide Rectangle & Circle 2 categories, 10 variations of palette widget.
class ColorPickerArea extends StatelessWidget {
const ColorPickerArea(
this.hsvColor,
this.onColorChanged,
this.onChangeEnd,
this.paletteType, {
super.key,
});
final HSVColor hsvColor;
final ValueChanged<HSVColor> onColorChanged;
final void Function() onChangeEnd;
final PaletteType paletteType;
/*void _handleColorRectChange(double horizontal, double vertical) {
onColorChanged(hsvColor.withSaturation(horizontal).withValue(vertical));
}*/
void _handleGesture(
Offset position, BuildContext context, double height, double width) {
RenderBox? getBox = context.findRenderObject() as RenderBox?;
if (getBox == null) return;
Offset localOffset = getBox.globalToLocal(position);
double horizontal = localOffset.dx.clamp(0.0, width);
double vertical = localOffset.dy.clamp(0.0, height);
//_handleColorRectChange(horizontal / width, 1 - vertical / height);
onColorChanged(hsvColor.withSaturation(horizontal).withValue(vertical));
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
double width = constraints.maxWidth;
double height = constraints.maxHeight;
return RawGestureDetector(
gestures: {
_AlwaysWinPanGestureRecognizer:
GestureRecognizerFactoryWithHandlers<
_AlwaysWinPanGestureRecognizer>(
() => _AlwaysWinPanGestureRecognizer(),
(_AlwaysWinPanGestureRecognizer instance) {
instance
..onDown = ((details) => _handleGesture(
details.globalPosition, context, height, width))
..onEnd = ((d) => onChangeEnd())
..onUpdate = ((details) => _handleGesture(
details.globalPosition, context, height, width));
},
),
},
child: Builder(
builder: (BuildContext _) {
return CustomPaint(painter: HSVWithHueColorPainter(hsvColor));
},
),
);
},
);
}
}
class _AlwaysWinPanGestureRecognizer extends PanGestureRecognizer {
@override
void addAllowedPointer(event) {
super.addAllowedPointer(event);
resolve(GestureDisposition.accepted);
}
@override
String get debugDescription => 'alwaysWin';
}
/// Uppercase text formater
class UpperCaseTextFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(oldValue, TextEditingValue newValue) =>
TextEditingValue(
text: newValue.text.toUpperCase(), selection: newValue.selection);
}

View File

@@ -0,0 +1,221 @@
// FROM: https://pub.dev/packages/flutter_colorpicker
// FROM: https://pub.dev/packages/flutter_colorpicker
// FROM: https://pub.dev/packages/flutter_colorpicker
// FROM: https://pub.dev/packages/flutter_colorpicker
// ignore: dangling_library_doc_comments
/// Common function lib
import 'dart:math';
import 'package:flutter/painting.dart';
import 'colors.dart';
/// Check if is good condition to use white foreground color by passing
/// the background color, and optional bias.
///
/// Reference:
///
/// Old: https://www.w3.org/TR/WCAG20-TECHS/G18.html
///
/// New: https://github.com/mchome/flutter_statusbarcolor/issues/40
bool useWhiteForeground(Color backgroundColor, {double bias = 0.0}) {
// Old:
// return 1.05 / (color.computeLuminance() + 0.05) > 4.5;
// New:
int v = sqrt(pow(backgroundColor.red, 2) * 0.299 +
pow(backgroundColor.green, 2) * 0.587 +
pow(backgroundColor.blue, 2) * 0.114)
.round();
return v < 130 + bias ? true : false;
}
/// Convert HSV to HSL
///
/// Reference: https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_HSL
HSLColor hsvToHsl(HSVColor color) {
double s = 0.0;
double l = 0.0;
l = (2 - color.saturation) * color.value / 2;
if (l != 0) {
if (l == 1) {
s = 0.0;
} else if (l < 0.5) {
s = color.saturation * color.value / (l * 2);
} else {
s = color.saturation * color.value / (2 - l * 2);
}
}
return HSLColor.fromAHSL(
color.alpha,
color.hue,
s.clamp(0.0, 1.0),
l.clamp(0.0, 1.0),
);
}
/// Convert HSL to HSV
///
/// Reference: https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_HSV
HSVColor hslToHsv(HSLColor color) {
double s = 0.0;
double v = 0.0;
v = color.lightness + color.saturation * (color.lightness < 0.5 ? color.lightness : 1 - color.lightness);
if (v != 0) s = 2 - 2 * color.lightness / v;
return HSVColor.fromAHSV(
color.alpha,
color.hue,
s.clamp(0.0, 1.0),
v.clamp(0.0, 1.0),
);
}
/// [RegExp] pattern for validation HEX color [String] inputs, allows only:
///
/// * exactly 1 to 8 digits in HEX format,
/// * only Latin A-F characters, case insensitive,
/// * and integer numbers 0,1,2,3,4,5,6,7,8,9,
/// * with optional hash (`#`) symbol at the beginning (not calculated in length).
///
/// ```dart
/// final RegExp hexInputValidator = RegExp(kValidHexPattern);
/// if (hexInputValidator.hasMatch(hex)) print('$hex might be a valid HEX color');
/// ```
/// Reference: https://en.wikipedia.org/wiki/Web_colors#Hex_triplet
const String kValidHexPattern = r'^#?[0-9a-fA-F]{1,8}';
/// [RegExp] pattern for validation complete HEX color [String], allows only:
///
/// * exactly 6 or 8 digits in HEX format,
/// * only Latin A-F characters, case insensitive,
/// * and integer numbers 0,1,2,3,4,5,6,7,8,9,
/// * with optional hash (`#`) symbol at the beginning (not calculated in length).
///
/// ```dart
/// final RegExp hexCompleteValidator = RegExp(kCompleteValidHexPattern);
/// if (hexCompleteValidator.hasMatch(hex)) print('$hex is valid HEX color');
/// ```
/// Reference: https://en.wikipedia.org/wiki/Web_colors#Hex_triplet
const String kCompleteValidHexPattern = r'^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$';
/// Try to convert text input or any [String] to valid [Color].
/// The [String] must be provided in one of those formats:
///
/// * RGB
/// * #RGB
/// * RRGGBB
/// * #RRGGBB
/// * AARRGGBB
/// * #AARRGGBB
///
/// Where: A stands for Alpha, R for Red, G for Green, and B for blue color.
/// It will only accept 3/6/8 long HEXs with an optional hash (`#`) at the beginning.
/// Allowed characters are Latin A-F case insensitive and numbers 0-9.
/// Optional [enableAlpha] can be provided (it's `true` by default). If it's set
/// to `false` transparency information (alpha channel) will be removed.
/// ```dart
/// /// // Valid 3 digit HEXs:
/// colorFromHex('abc') == Color(0xffaabbcc)
/// colorFromHex('ABc') == Color(0xffaabbcc)
/// colorFromHex('ABC') == Color(0xffaabbcc)
/// colorFromHex('#Abc') == Color(0xffaabbcc)
/// colorFromHex('#abc') == Color(0xffaabbcc)
/// colorFromHex('#ABC') == Color(0xffaabbcc)
/// // Valid 6 digit HEXs:
/// colorFromHex('aabbcc') == Color(0xffaabbcc)
/// colorFromHex('AABbcc') == Color(0xffaabbcc)
/// colorFromHex('AABBCC') == Color(0xffaabbcc)
/// colorFromHex('#AABbcc') == Color(0xffaabbcc)
/// colorFromHex('#aabbcc') == Color(0xffaabbcc)
/// colorFromHex('#AABBCC') == Color(0xffaabbcc)
/// // Valid 8 digit HEXs:
/// colorFromHex('ffaabbcc') == Color(0xffaabbcc)
/// colorFromHex('ffAABbcc') == Color(0xffaabbcc)
/// colorFromHex('ffAABBCC') == Color(0xffaabbcc)
/// colorFromHex('ffaabbcc', enableAlpha: true) == Color(0xffaabbcc)
/// colorFromHex('FFAAbbcc', enableAlpha: true) == Color(0xffaabbcc)
/// colorFromHex('ffAABBCC', enableAlpha: true) == Color(0xffaabbcc)
/// colorFromHex('FFaabbcc', enableAlpha: true) == Color(0xffaabbcc)
/// colorFromHex('#ffaabbcc') == Color(0xffaabbcc)
/// colorFromHex('#ffAABbcc') == Color(0xffaabbcc)
/// colorFromHex('#FFAABBCC') == Color(0xffaabbcc)
/// colorFromHex('#ffaabbcc', enableAlpha: true) == Color(0xffaabbcc)
/// colorFromHex('#FFAAbbcc', enableAlpha: true) == Color(0xffaabbcc)
/// colorFromHex('#ffAABBCC', enableAlpha: true) == Color(0xffaabbcc)
/// colorFromHex('#FFaabbcc', enableAlpha: true) == Color(0xffaabbcc)
/// // Invalid HEXs:
/// colorFromHex('bc') == null // length 2
/// colorFromHex('aabbc') == null // length 5
/// colorFromHex('#ffaabbccd') == null // length 9 (+#)
/// colorFromHex('aabbcx') == null // x character
/// colorFromHex('#aabbвв') == null // в non-latin character
/// colorFromHex('') == null // empty
/// ```
/// Reference: https://en.wikipedia.org/wiki/Web_colors#Hex_triplet
Color? colorFromHex(String inputString, {bool enableAlpha = true}) {
// Registers validator for exactly 6 or 8 digits long HEX (with optional #).
final RegExp hexValidator = RegExp(kCompleteValidHexPattern);
// Validating input, if it does not match — it's not proper HEX.
if (!hexValidator.hasMatch(inputString)) return null;
// Remove optional hash if exists and convert HEX to UPPER CASE.
String hexToParse = inputString.replaceFirst('#', '').toUpperCase();
// It may allow HEXs with transparency information even if alpha is disabled,
if (!enableAlpha && hexToParse.length == 8) {
// but it will replace this info with 100% non-transparent value (FF).
hexToParse = 'FF${hexToParse.substring(2)}';
}
// HEX may be provided in 3-digits format, let's just duplicate each letter.
if (hexToParse.length == 3) {
hexToParse = hexToParse.split('').expand((i) => [i * 2]).join();
}
// We will need 8 digits to parse the color, let's add missing digits.
if (hexToParse.length == 6) hexToParse = 'FF$hexToParse';
// HEX must be valid now, but as a precaution, it will just "try" to parse it.
final intColorValue = int.tryParse(hexToParse, radix: 16);
// If for some reason HEX is not valid — abort the operation, return nothing.
if (intColorValue == null) return null;
// Register output color for the last step.
final color = Color(intColorValue);
// Decide to return color with transparency information or not.
return enableAlpha ? color : color.withAlpha(255);
}
/// Converts `dart:ui` [Color] to the 6/8 digits HEX [String].
///
/// Prefixes a hash (`#`) sign if [includeHashSign] is set to `true`.
/// The result will be provided as UPPER CASE, it can be changed via [toUpperCase]
/// flag set to `false` (default is `true`). Hex can be returned without alpha
/// channel information (transparency), with the [enableAlpha] flag set to `false`.
String colorToHex(
Color color, {
bool includeHashSign = false,
bool enableAlpha = true,
bool toUpperCase = true,
}) {
final String hex = (includeHashSign ? '#' : '') +
(enableAlpha ? _padRadix(color.alpha) : '') +
_padRadix(color.red) +
_padRadix(color.green) +
_padRadix(color.blue);
return toUpperCase ? hex.toUpperCase() : hex;
}
// Shorthand for padLeft of RadixString, DRY.
String _padRadix(int value) => value.toRadixString(16).padLeft(2, '0');
// Extension for String
extension ColorExtension1 on String {
Color? toColor() {
Color? color = colorFromName(this);
if (color != null) return color;
return colorFromHex(this);
}
}
// Extension from Color
extension ColorExtension2 on Color {
String toHexString({bool includeHashSign = false, bool enableAlpha = true, bool toUpperCase = true}) =>
colorToHex(this, includeHashSign: false, enableAlpha: true, toUpperCase: true);
}

View File

@@ -0,0 +1,358 @@
import 'package:refilc/models/settings.dart';
import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc_kreta_api/models/grade.dart';
import 'package:refilc/helpers/subject.dart';
import 'package:refilc/utils/format.dart';
import 'package:refilc_mobile_ui/pages/grades/calculator/grade_calculator_provider.dart';
import 'package:refilc_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, {
super.key,
this.onTap,
this.padding,
this.censored = false,
this.viewOverride = false,
});
final Grade grade;
final void Function()? onTap;
final EdgeInsetsGeometry? padding;
final bool censored;
final bool viewOverride;
@override
Widget build(BuildContext context) {
String title;
String subtitle;
bool isTitleItalic = false;
bool isSubtitleItalic = false;
// EdgeInsets leadingPadding = EdgeInsets.zero;
bool isSubjectView =
SubjectGradesContainer.of(context) != null || viewOverride;
String subjectName =
grade.subject.renamedTo ?? grade.subject.name.escapeHtml().capital();
String modeDescription = grade.mode.description.escapeHtml().capital();
String description = grade.description.escapeHtml().capital();
GradeCalculatorProvider calculatorProvider =
Provider.of<GradeCalculatorProvider>(context, listen: false);
SettingsProvider settingsProvider = Provider.of<SettingsProvider>(context);
// 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;
isTitleItalic =
grade.subject.isRenamed && settingsProvider.renamedSubjectsItalics;
}
// Test order:
// subject name
// mode + weight != 100
if (grade.type == GradeType.midYear) {
subtitle = isSubjectView
? description != ""
? modeDescription
: ""
: subjectName;
isSubtitleItalic = isSubjectView
? false
: grade.subject.isRenamed && settingsProvider.renamedSubjectsItalics;
} 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)
: GradeValueWidget(
grade.value,
fill: true,
size: 27.5,
),
// leading: isSubjectView
// ? GradeValueWidget(grade.value)
// : SizedBox(
// width: 44,
// height: 44,
// child: censored
// ? Container(
// decoration: BoxDecoration(
// color: AppColors.of(context).text,
// 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,
// ),
// ),
// ),
// ),
title: censored
? Wrap(
children: [
Container(
width: 110,
height: 15,
decoration: BoxDecoration(
color: AppColors.of(context).text,
borderRadius: BorderRadius.circular(8.0),
),
),
],
)
: Text(
title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: FontWeight.w600,
fontStyle: isTitleItalic ? FontStyle.italic : null),
),
subtitle: subtitle != ""
? censored
? Wrap(
children: [
Container(
width: 50,
height: 10,
decoration: BoxDecoration(
color: AppColors.of(context).text,
borderRadius: BorderRadius.circular(8.0),
),
),
],
)
: Text(
subtitle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500,
fontStyle:
isSubtitleItalic ? FontStyle.italic : null),
)
: 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,
borderRadius: BorderRadius.circular(8.0),
),
)
// : GradeValueWidget(grade.value),
: Icon(
SubjectIcon.resolveVariant(
context: context, subject: grade.subject),
color: AppColors.of(context).text.withOpacity(.5),
),
minLeadingWidth: isSubjectView ? 32.0 : 0,
),
),
);
}
}
class GradeValueWidget extends StatelessWidget {
const GradeValueWidget(
this.value, {
super.key,
this.size = 38.0,
this.fill = false,
this.contrast = false,
this.shadow = false,
this.outline = false,
this.complemented = false,
this.nocolor = false,
this.color,
});
final GradeValue value;
final double size;
final bool fill;
final bool contrast;
final bool shadow;
final bool outline;
final bool complemented;
final bool nocolor;
final Color? color;
@override
Widget build(BuildContext context) {
GradeValue value = this.value;
bool isSubjectView = SubjectGradesContainer.of(context) != null;
Color color = this.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 / (isSubjectView ? 1 : 1.5) * 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(1.0, 0.2) : 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 &&
Provider.of<SettingsProvider>(context, listen: false)
.shadowEffect)
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

@@ -0,0 +1,451 @@
import 'package:refilc/models/settings.dart';
import 'package:refilc_kreta_api/providers/exam_provider.dart';
import 'package:refilc_kreta_api/providers/homework_provider.dart';
import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc_kreta_api/models/exam.dart';
import 'package:refilc_kreta_api/models/homework.dart';
import 'package:refilc_kreta_api/models/lesson.dart';
import 'package:refilc/utils/format.dart';
import 'package:refilc_mobile_ui/common/panel/panel.dart';
import 'package:refilc_mobile_ui/common/round_border_icon.dart';
import 'package:refilc_mobile_ui/common/widgets/exam/exam_view.dart';
import 'package:refilc_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, {super.key, this.onTap, this.swapDesc = false});
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 = "";
SettingsProvider settingsProvider = Provider.of<SettingsProvider>(context);
// 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 != null &&
lesson.substituteTeacher?.name != "") {
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
.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?.name != "") {
// description = 'substitution'.i18n;
// }
// }
// room = lesson.room.replaceAll("_", " ");
// } else {
// description = lesson.room.replaceAll("_", " ");
// }
return Padding(
padding: const EdgeInsets.only(bottom: 4.0, top: 7.0),
child: Material(
color: 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: cleanDesc == '' ? 12.0 : 0.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: 16.5,
color: fill
? accent
: AppColors.of(context)
.text
.withOpacity(!lesson.isEmpty ? 1.0 : 0.5),
fontStyle: lesson.subject.isRenamed &&
settingsProvider.renamedSubjectsItalics
? FontStyle.italic
: null),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Row(
// children: [
// Container(
// padding: const EdgeInsets.symmetric(
// horizontal: 6.0, vertical: 3.5),
// decoration: BoxDecoration(
// color: Theme.of(context)
// .colorScheme
// .secondary
// .withOpacity(.15),
// borderRadius: BorderRadius.circular(10.0),
// ),
// child: Text(
// lesson.room,
// style: TextStyle(
// height: 1.1,
// fontSize: 12.5,
// fontWeight: FontWeight.w600,
// color: Theme.of(context)
// .colorScheme
// .secondary
// .withOpacity(.9),
// ),
// ),
// )
// ],
// ),
// if (cleanDesc != '')
// const SizedBox(
// height: 10.0,
// ),
if (cleanDesc != '')
Text(
cleanDesc,
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: 14.0,
color: fill ? accent.withOpacity(0.5) : 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: [
RoundBorderIcon(
color: fill ? accent : AppColors.of(context).text,
width: 1.0,
icon: SizedBox(
width: 25,
height: 25,
child: Center(
child: Padding(
padding: const EdgeInsets.only(left: 3.0),
child: Text(
lesson.lessonIndex + lessonIndexTrailing,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 17.5,
fontWeight: FontWeight.w700,
color: fill ? accent : null,
),
),
),
),
),
),
// 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(-22.0, -1.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),
// ),
// ),
// ),
// ),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6.0, vertical: 3.5),
decoration: BoxDecoration(
color: fill
? accent.withOpacity(.15)
: Theme.of(context)
.colorScheme
.secondary
.withOpacity(.15),
borderRadius: BorderRadius.circular(10.0),
),
child: Text(
lesson.room,
style: TextStyle(
height: 1.1,
fontSize: 12.5,
fontWeight: FontWeight.w600,
color: fill
? accent.withOpacity(0.9)
: Theme.of(context)
.colorScheme
.secondary
.withOpacity(.9),
),
),
),
const SizedBox(
width: 10,
),
Stack(
alignment: Alignment.center,
children: [
// xix alignment hack :p
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: fill
? accent.withOpacity(.9)
: AppColors.of(context)
.text
.withOpacity(.9),
),
),
],
),
],
)
: null,
),
// Homework & Exams
...subtiles,
],
),
),
),
),
);
}
}
enum LessonSubtileType { homework, exam, absence }
class LessonSubtile extends StatelessWidget {
const LessonSubtile(
{super.key, this.onPressed, required this.title, required this.type});
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.only(top: 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: 10.0, top: 2.0),
child: Text(
title.escapeHtml(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: FontWeight.w500,
color: AppColors.of(context).text.withOpacity(.65)),
),
),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +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);
}

View File

@@ -0,0 +1,130 @@
import 'package:refilc/models/settings.dart';
import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc/utils/format.dart';
import 'package:refilc_kreta_api/models/message.dart';
import 'package:refilc_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, {
super.key,
this.messages,
this.padding,
this.onTap,
this.censored = false,
});
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: 19.2,
backgroundColor: Theme.of(context).colorScheme.secondary,
censored: censored,
isNotePfp: true,
)
: ProfileImage(
name: "Béla",
radius: 19.2,
backgroundColor: Theme.of(context).colorScheme.secondary,
censored: censored,
isNotePfp: true,
),
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),
),
),
),
),
);
}
}