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,65 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:io';
import 'dart:typed_data';
import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc/helpers/storage_helper.dart';
import 'package:refilc_kreta_api/client/client.dart';
import 'package:refilc_kreta_api/models/attachment.dart';
import 'package:refilc_kreta_api/models/homework.dart';
import 'package:flutter/widgets.dart';
import 'package:open_filex/open_filex.dart';
import 'package:provider/provider.dart';
extension AttachmentHelper on Attachment {
Future<String> download(BuildContext context,
{bool overwrite = false}) async {
String downloads = await StorageHelper.downloadsPath();
if (!overwrite && await File("$downloads/$name").exists()) {
return "$downloads/$name";
}
Uint8List data = await Provider.of<KretaClient>(context, listen: false)
.getAPI(downloadUrl, rawResponse: true);
if (!await StorageHelper.write("$downloads/$name", data)) return "";
return "$downloads/$name";
}
Future<bool> open(BuildContext context) async {
String downloads = await StorageHelper.downloadsPath();
if (!await File("$downloads/$name").exists()) await download(context);
var result = await OpenFilex.open("$downloads/$name");
return result.type == ResultType.done;
}
}
extension HomeworkAttachmentHelper on HomeworkAttachment {
Future<String> download(BuildContext context,
{bool overwrite = false}) async {
String downloads = await StorageHelper.downloadsPath();
if (!overwrite && await File("$downloads/$name").exists()) {
return "$downloads/$name";
}
String url = downloadUrl(
Provider.of<UserProvider>(context, listen: false).instituteCode ?? "");
Uint8List data = await Provider.of<KretaClient>(context, listen: false)
.getAPI(url, rawResponse: true);
if (!await StorageHelper.write("$downloads/$name", data)) return "";
return "$downloads/$name";
}
Future<bool> open(BuildContext context) async {
String downloads = await StorageHelper.downloadsPath();
if (!await File("$downloads/$name").exists()) await download(context);
var result = await OpenFilex.open("$downloads/$name");
return result.type == ResultType.done;
}
}

View File

@@ -0,0 +1,25 @@
import 'package:refilc_kreta_api/models/grade.dart';
class AverageHelper {
static double averageEvals(List<Grade> grades, {bool finalAvg = false}) {
double average = 0.0;
List<String> ignoreInFinal = ["5,SzorgalomErtek", "4,MagatartasErtek"];
if (finalAvg) {
grades.removeWhere((e) =>
(e.value.value == 0) || (ignoreInFinal.contains(e.gradeType?.id)));
}
for (var e in grades) {
average += e.value.value * ((finalAvg ? 100 : e.value.weight) / 100);
}
average = average /
grades
.map((e) => (finalAvg ? 100 : e.value.weight) / 100)
.fold(0.0, (a, b) => a + b);
return average.isNaN ? 0.0 : average;
}
}

View File

@@ -0,0 +1,608 @@
import 'package:refilc/api/providers/database_provider.dart';
import 'package:refilc/api/providers/status_provider.dart';
import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc/models/settings.dart';
import 'package:refilc/helpers/notification_helper.i18n.dart';
import 'package:refilc_kreta_api/client/api.dart';
import 'package:refilc_kreta_api/client/client.dart';
import 'package:refilc_kreta_api/models/absence.dart';
import 'package:refilc_kreta_api/models/grade.dart';
import 'package:refilc_kreta_api/models/lesson.dart';
import 'package:refilc_kreta_api/models/week.dart';
import 'package:refilc_kreta_api/providers/grade_provider.dart';
import 'package:refilc_kreta_api/providers/timetable_provider.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'
hide Message;
import 'package:i18n_extension/i18n_widget.dart';
import 'package:intl/intl.dart';
import 'package:refilc_kreta_api/models/message.dart';
class NotificationsHelper {
late DatabaseProvider database;
late SettingsProvider settingsProvider;
late UserProvider userProvider;
late KretaClient kretaClient;
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
List<T> combineLists<T, K>(
List<T> list1,
List<T> list2,
K Function(T) keyExtractor,
) {
Set<K> uniqueKeys = <K>{};
List<T> combinedList = [];
for (T item in list1) {
K key = keyExtractor(item);
if (!uniqueKeys.contains(key)) {
uniqueKeys.add(key);
combinedList.add(item);
}
}
for (T item in list2) {
K key = keyExtractor(item);
if (!uniqueKeys.contains(key)) {
uniqueKeys.add(key);
combinedList.add(item);
}
}
return combinedList;
}
String dayTitle(DateTime date) {
try {
return DateFormat("EEEE", I18n.locale.languageCode).format(date);
} catch (e) {
return "Unknown";
}
}
@pragma('vm:entry-point')
void backgroundJob() async {
// initialize providers
database = DatabaseProvider();
await database.init();
settingsProvider = await database.query.getSettings(database);
userProvider = await database.query.getUsers(settingsProvider);
if (userProvider.id != null && settingsProvider.notificationsEnabled) {
// refresh kreta login
final status = StatusProvider();
kretaClient = KretaClient(
user: userProvider, settings: settingsProvider, status: status);
kretaClient.refreshLogin();
if (settingsProvider.notificationsGradesEnabled) gradeNotification();
if (settingsProvider.notificationsAbsencesEnabled) absenceNotification();
if (settingsProvider.notificationsMessagesEnabled) messageNotification();
if (settingsProvider.notificationsLessonsEnabled) lessonNotification();
}
}
void gradeNotification() async {
// fetch grades
GradeProvider gradeProvider = GradeProvider(
settings: settingsProvider,
user: userProvider,
database: database,
kreta: kretaClient);
gradeProvider.fetch();
List<Grade> grades =
await database.userQuery.getGrades(userId: userProvider.id ?? "");
DateTime lastSeenGrade =
await database.userQuery.lastSeenGrade(userId: userProvider.id ?? "");
// loop through grades and see which hasn't been seen yet
for (Grade grade in grades) {
// if grade is not a normal grade (1-5), don't show it
if ([1, 2, 3, 4, 5].contains(grade.value.value)) {
// if the grade was added over a week ago, don't show it to avoid notification spam
// it worked in reverse, cuz someone added a * -1 to it, but it has been fixed now :D
// old code below
// if (grade.seenDate.isAfter(lastSeenGrade) &&
// grade.date.difference(DateTime.now()).inDays * -1 < 7) {
// new code from here :P
if (grade.seenDate.isAfter(lastSeenGrade) &&
grade.date.difference(DateTime.now()).inDays < 7) {
// send notificiation about new grade
AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
'GRADES',
'Jegyek',
channelDescription: 'Értesítés jegyek beírásakor',
importance: Importance.max,
priority: Priority.max,
color: settingsProvider.customAccentColor,
ticker: 'Jegyek',
);
NotificationDetails notificationDetails =
NotificationDetails(android: androidNotificationDetails);
if (userProvider.getUsers().length == 1) {
await flutterLocalNotificationsPlugin.show(
grade.id.hashCode,
"title_grade".i18n,
"body_grade".i18n.fill(
[
grade.value.value.toString(),
grade.subject.isRenamed &&
settingsProvider.renamedSubjectsEnabled
? grade.subject.renamedTo!
: grade.subject.name
],
),
notificationDetails,
);
} else {
// multiple users are added, also display student name
await flutterLocalNotificationsPlugin.show(
grade.id.hashCode,
"title_grade".i18n,
"body_grade_multiuser".i18n.fill(
[
userProvider.displayName!,
grade.value.value.toString(),
grade.subject.isRenamed &&
settingsProvider.renamedSubjectsEnabled
? grade.subject.renamedTo!
: grade.subject.name
],
),
notificationDetails,
);
}
}
}
}
// set grade seen status
gradeProvider.seenAll();
}
void absenceNotification() async {
// get absences from api
List? absenceJson = await kretaClient
.getAPI(KretaAPI.absences(userProvider.instituteCode ?? ""));
List<Absence> storedAbsences =
await database.userQuery.getAbsences(userId: userProvider.id!);
if (absenceJson == null) {
return;
}
// format api absences to correct format while preserving isSeen value
List<Absence> absences = absenceJson.map((e) {
Absence apiAbsence = Absence.fromJson(e);
Absence storedAbsence = storedAbsences.firstWhere(
(stored) => stored.id == apiAbsence.id,
orElse: () => apiAbsence);
apiAbsence.isSeen = storedAbsence.isSeen;
return apiAbsence;
}).toList();
List<Absence> modifiedAbsences = [];
if (absences != storedAbsences) {
// remove absences that are not new
absences.removeWhere((element) => storedAbsences.contains(element));
for (Absence absence in absences) {
if (!absence.isSeen) {
absence.isSeen = true;
modifiedAbsences.add(absence);
AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
'ABSENCES',
'Hiányzások',
channelDescription: 'Értesítés hiányzások beírásakor',
importance: Importance.max,
priority: Priority.max,
color: settingsProvider.customAccentColor,
ticker: 'Hiányzások',
);
NotificationDetails notificationDetails =
NotificationDetails(android: androidNotificationDetails);
if (userProvider.getUsers().length == 1) {
await flutterLocalNotificationsPlugin.show(
absence.id.hashCode,
"title_absence"
.i18n, // https://discord.com/channels/1111649116020285532/1153273625206591528
"body_absence".i18n.fill(
[
DateFormat("yyyy-MM-dd").format(absence.date),
absence.subject.isRenamed &&
settingsProvider.renamedSubjectsEnabled
? absence.subject.renamedTo!
: absence.subject.name
],
),
notificationDetails,
);
} else {
await flutterLocalNotificationsPlugin.show(
absence.id.hashCode,
"title_absence"
.i18n, // https://discord.com/channels/1111649116020285532/1153273625206591528
"body_absence_multiuser".i18n.fill(
[
userProvider.displayName!,
DateFormat("yyyy-MM-dd").format(absence.date),
absence.subject.isRenamed &&
settingsProvider.renamedSubjectsEnabled
? absence.subject.renamedTo!
: absence.subject.name
],
),
notificationDetails,
);
}
}
}
}
// combine modified absences and storedabsences list and save them to the database
List<Absence> combinedAbsences = combineLists(
modifiedAbsences,
storedAbsences,
(Absence absence) => absence.id,
);
await database.userStore
.storeAbsences(combinedAbsences, userId: userProvider.id!);
}
void messageNotification() async {
// get messages from api
List? messageJson =
await kretaClient.getAPI(KretaAPI.messages("beerkezett"));
List<Message> storedmessages =
await database.userQuery.getMessages(userId: userProvider.id!);
if (messageJson == null) {
return;
}
// format api messages to correct format while preserving isSeen value
// Parse messages
List<Message> messages = [];
await Future.wait(List.generate(messageJson.length, (index) {
return () async {
Map message = messageJson.cast<Map>()[index];
Map? innerMessageJson = await kretaClient
.getAPI(KretaAPI.message(message["azonosito"].toString()));
if (innerMessageJson != null) {
messages.add(
Message.fromJson(innerMessageJson, forceType: MessageType.inbox));
}
}();
}));
for (Message message in messages) {
for (Message storedMessage in storedmessages) {
if (message.id == storedMessage.id) {
message.isSeen = storedMessage.isSeen;
}
}
}
List<Message> modifiedmessages = [];
if (messages != storedmessages) {
// remove messages that are not new
messages.removeWhere((element) => storedmessages.contains(element));
for (Message message in messages) {
if (!message.isSeen) {
message.isSeen = true;
modifiedmessages.add(message);
AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
'MESSAGES',
'Üzenetek',
channelDescription: 'Értesítés kapott üzenetekkor',
importance: Importance.max,
priority: Priority.max,
color: settingsProvider.customAccentColor,
ticker: 'Üzenetek',
);
NotificationDetails notificationDetails =
NotificationDetails(android: androidNotificationDetails);
if (userProvider.getUsers().length == 1) {
await flutterLocalNotificationsPlugin.show(
message.id.hashCode,
message.author,
message.content.replaceAll(RegExp(r'<[^>]*>'), ''),
notificationDetails,
);
} else {
await flutterLocalNotificationsPlugin.show(
message.id.hashCode,
"(${userProvider.displayName!}) ${message.author}",
message.content.replaceAll(RegExp(r'<[^>]*>'), ''),
notificationDetails,
);
}
}
}
}
// combine modified messages and storedmessages list and save them to the database
List<Message> combinedmessages = combineLists(
modifiedmessages,
storedmessages,
(Message message) => message.id,
);
await database.userStore
.storeMessages(combinedmessages, userId: userProvider.id!);
}
void lessonNotification() async {
// get lesson from api
TimetableProvider timetableProvider = TimetableProvider(
user: userProvider, database: database, kreta: kretaClient);
List<Lesson> storedlessons =
timetableProvider.lessons[Week.current()] ?? [];
List? apilessons = timetableProvider.getWeek(Week.current()) ?? [];
for (Lesson lesson in apilessons) {
for (Lesson storedLesson in storedlessons) {
if (lesson.id == storedLesson.id) {
lesson.isSeen = storedLesson.isSeen;
}
}
}
List<Lesson> modifiedlessons = [];
if (apilessons != storedlessons) {
// remove lessons that are not new
apilessons.removeWhere((element) => storedlessons.contains(element));
for (Lesson lesson in apilessons) {
if (!lesson.isSeen && lesson.isChanged) {
lesson.isSeen = true;
modifiedlessons.add(lesson);
AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
'LESSONS',
'Órák',
channelDescription:
'Értesítés órák elmaradásáról, helyettesítésről',
importance: Importance.max,
priority: Priority.max,
color: settingsProvider.customAccentColor,
ticker: 'Órák',
);
NotificationDetails notificationDetails =
NotificationDetails(android: androidNotificationDetails);
if (userProvider.getUsers().length == 1) {
if (lesson.status?.name == "Elmaradt") {
switch (I18n.localeStr) {
case "en_en":
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_canceled".i18n.fill(
[
lesson.lessonIndex,
lesson.name,
dayTitle(lesson.date)
],
),
notificationDetails,
);
break;
}
case "hu_hu":
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_canceled".i18n.fill(
[
dayTitle(lesson.date),
lesson.lessonIndex,
lesson.name
],
),
notificationDetails,
);
break;
}
default:
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_canceled".i18n.fill(
[
lesson.lessonIndex,
lesson.name,
dayTitle(lesson.date)
],
),
notificationDetails,
);
break;
}
}
} else if (lesson.substituteTeacher?.name != "") {
switch (I18n.localeStr) {
case "en_en":
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_substituted".i18n.fill(
[
lesson.lessonIndex,
lesson.name,
dayTitle(lesson.date),
lesson.substituteTeacher!.isRenamed
? lesson.substituteTeacher!.renamedTo!
: lesson.substituteTeacher!.name
],
),
notificationDetails,
);
break;
}
case "hu_hu":
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_substituted".i18n.fill(
[
dayTitle(lesson.date),
lesson.lessonIndex,
lesson.name,
lesson.substituteTeacher!.isRenamed
? lesson.substituteTeacher!.renamedTo!
: lesson.substituteTeacher!.name
],
),
notificationDetails,
);
break;
}
default:
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_substituted".i18n.fill(
[
lesson.lessonIndex,
lesson.name,
dayTitle(lesson.date),
lesson.substituteTeacher!.isRenamed
? lesson.substituteTeacher!.renamedTo!
: lesson.substituteTeacher!.name
],
),
notificationDetails,
);
break;
}
}
}
} else {
if (lesson.status?.name == "Elmaradt") {
switch (I18n.localeStr) {
case "en_en":
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_canceled".i18n.fill(
[
userProvider.displayName!,
lesson.lessonIndex,
lesson.name,
dayTitle(lesson.date)
],
),
notificationDetails,
);
break;
}
case "hu_hu":
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_canceled".i18n.fill(
[
userProvider.displayName!,
dayTitle(lesson.date),
lesson.lessonIndex,
lesson.name
],
),
notificationDetails,
);
break;
}
default:
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_canceled".i18n.fill(
[
userProvider.displayName!,
lesson.lessonIndex,
lesson.name,
dayTitle(lesson.date)
],
),
notificationDetails,
);
break;
}
}
} else if (lesson.substituteTeacher?.name != "") {
switch (I18n.localeStr) {
case "en_en":
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_substituted".i18n.fill(
[
userProvider.displayName!,
lesson.lessonIndex,
lesson.name,
dayTitle(lesson.date),
lesson.substituteTeacher!.isRenamed
? lesson.substituteTeacher!.renamedTo!
: lesson.substituteTeacher!.name
],
),
notificationDetails,
);
break;
}
case "hu_hu":
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_substituted".i18n.fill(
[
userProvider.displayName!,
dayTitle(lesson.date),
lesson.lessonIndex,
lesson.name,
lesson.substituteTeacher!.isRenamed
? lesson.substituteTeacher!.renamedTo!
: lesson.substituteTeacher!.name
],
),
notificationDetails,
);
break;
}
default:
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_substituted".i18n.fill(
[
userProvider.displayName!,
lesson.lessonIndex,
lesson.name,
dayTitle(lesson.date),
lesson.substituteTeacher!.isRenamed
? lesson.substituteTeacher!.renamedTo!
: lesson.substituteTeacher!.name
],
),
notificationDetails,
);
break;
}
}
}
}
}
}
// combine modified lesson and storedlesson list and save them to the database
List<Lesson> combinedlessons = combineLists(
modifiedlessons,
storedlessons,
(Lesson message) => message.id,
);
Map<Week, List<Lesson>> timetableLessons = timetableProvider.lessons;
timetableLessons[Week.current()] = combinedlessons;
await database.userStore
.storeLessons(timetableLessons, userId: userProvider.id!);
}
}
}

View File

@@ -0,0 +1,51 @@
import 'package:i18n_extension/i18n_extension.dart';
extension Localization on String {
static final _t = Translations.byLocale("hu_hu") +
{
"en_en": {
"title_grade": "New grade",
"body_grade": "You got a %s in %s",
"body_grade_multiuser": "%s got a %s in %s",
"title_absence": "Absence recorded",
"body_absence": "An absence was recorded on %s for %s",
"body_absence_multiuser": "An absence was recorded for %s on %s for the subject %s",
"title_lesson": "Timetable modified",
"body_lesson_canceled": "Lesson #%s (%s) has been canceled on %s",
"body_lesson_canceled_multiuser": "(%s) Lesson #%s (%s) has been canceled on %s",
"body_lesson_substituted": "Lesson #%s (%s) on %s will be substituted by %s",
"body_lesson_substituted_multiuser": "(%s) Lesson #%s (%s) on %s will be substituted by %s"
},
"hu_hu": {
"title_grade": "Új jegy",
"body_grade": "%s-st kaptál %s tantárgyból",
"body_grade_multiuser": "%s tanuló %s-st kapott %s tantárgyból",
"title_absence": "Új hiányzás",
"body_absence": "Új hiányzást kaptál %s napon %s tantárgyból",
"body_absence_multiuser": "%s tanuló új hiányzást kapott %s napon %s tantárgyból",
"title_lesson": "Órarend szerkesztve",
"body_lesson_canceled": "%s-i %s. óra (%s) elmarad",
"body_lesson_canceled_multiuser": "(%s) %s-i %s. óra (%s) elmarad",
"body_lesson_substituted": "%s-i %s. (%s) órát %s helyettesíti",
"body_lesson_substituted_multiuser": "(%s) %s-i %s. (%s) órát %s helyettesíti"
},
"de_de": {
"title_grade": "Neue Note",
"body_grade": "Du hast eine %s in %s",
"body_grade_multiuser": "%s hast eine %s in %s",
"title_absence": "Abwesenheit aufgezeichnet",
"body_absence": "Auf %s für %s wurde eine Abwesenheit aufgezeichnet",
"body_absence_multiuser": "Für %s wurde am %s für das Thema Mathematik eine Abwesenheit aufgezeichnet",
"title_lesson": "Fahrplan geändert",
"body_lesson_canceled": "Lektion Nr. %s (%s) wurde am %s abgesagt",
"body_lesson_canceled_multiuser": "(%s) Lektion Nr. %s (%s) wurde am %s abgesagt",
"body_lesson_substituted": "Lektion Nr. %s (%s) wird am %s durch %s ersetzt",
"body_lesson_substituted_multiuser": "(%s) Lektion Nr. %s (%s) wird am %s durch %s ersetzt"
},
};
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,48 @@
import 'package:flutter/cupertino.dart';
import 'package:quick_actions/quick_actions.dart';
import 'package:refilc_mobile_ui/common/screens.i18n.dart';
const QuickActions quickActions = QuickActions();
void setupQuickActions() {
quickActions.setShortcutItems(<ShortcutItem>[
ShortcutItem(
type: 'action_grades',
localizedTitle: 'grades'.i18n,
icon: 'ic_grades'),
ShortcutItem(
type: 'action_timetable',
localizedTitle: 'timetable'.i18n,
icon: 'ic_timetable'),
ShortcutItem(
type: 'action_messages',
localizedTitle: 'messages'.i18n,
icon: 'ic_messages'),
ShortcutItem(
type: 'action_absences',
localizedTitle: 'absences'.i18n,
icon: 'ic_absences')
]);
}
void handleQuickActions(BuildContext context, void Function(String) callback) {
quickActions.initialize((shortcutType) {
switch (shortcutType) {
case 'action_home':
callback("home");
break;
case 'action_grades':
callback("grades");
break;
case 'action_timetable':
callback("timetable");
break;
case 'action_messages':
callback("messages");
break;
case 'action_absences':
callback("absences");
break;
}
});
}

View File

@@ -0,0 +1,18 @@
import 'package:refilc/helpers/attachment_helper.dart';
import 'package:refilc_kreta_api/models/attachment.dart';
import 'package:flutter/widgets.dart';
import 'package:share_plus/share_plus.dart';
class ShareHelper {
static Future<void> shareText(String text, {String? subject}) =>
Share.share(text, subject: subject);
// ignore: deprecated_member_use
static Future<void> shareFile(String path, {String? text, String? subject}) =>
Share.shareFiles([path], text: text, subject: subject);
static Future<void> shareAttachment(Attachment attachment,
{required BuildContext context}) async {
String path = await attachment.download(context);
await shareFile(path);
}
}

View File

@@ -0,0 +1,39 @@
// ignore_for_file: avoid_print
import 'dart:io';
import 'dart:typed_data';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
class StorageHelper {
static Future<bool> write(String path, Uint8List data) async {
try {
if (await Permission.manageExternalStorage.request().isGranted) {
await File(path).writeAsBytes(data);
return true;
} else {
if (await Permission.storage.isPermanentlyDenied) {
openAppSettings();
}
return false;
}
} catch (error) {
print("ERROR: StorageHelper.write: $error");
return false;
}
}
static Future<String> downloadsPath() async {
String downloads;
if (Platform.isAndroid) {
downloads = "/storage/self/primary/Download";
} else {
downloads = (await getTemporaryDirectory()).path;
}
return downloads;
// return (await getTemporaryDirectory()).path;
}
}

View File

@@ -0,0 +1,292 @@
import 'package:refilc/icons/filc_icons.dart';
import 'package:refilc/models/icon_pack.dart';
import 'package:refilc/models/settings.dart';
import 'package:refilc/utils/format.dart';
import 'package:refilc_kreta_api/models/subject.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
typedef SubjectIconVariants = Map<IconPack, IconData>;
class SubjectIconData {
final SubjectIconVariants data;
final String name; // for iOS live activities compatibilty
SubjectIconData({
this.data = const {
IconPack.material: Icons.widgets_outlined,
IconPack.cupertino: CupertinoIcons.rectangle_grid_2x2,
},
this.name = "square.grid.2x2",
});
}
SubjectIconVariants createIcon(
{required IconData material, required IconData cupertino}) {
return {
IconPack.material: material,
IconPack.cupertino: cupertino,
};
}
class SubjectIcon {
static String resolveName({GradeSubject? subject, String? subjectName}) =>
_resolve(subject: subject, subjectName: subjectName).name;
static IconData resolveVariant(
{GradeSubject? subject,
String? subjectName,
required BuildContext context}) =>
_resolve(subject: subject, subjectName: subjectName).data[
Provider.of<SettingsProvider>(context, listen: false).iconPack]!;
static SubjectIconData _resolve(
{GradeSubject? subject, String? subjectName}) {
assert(!(subject == null && subjectName == null));
String name = (subject?.name ?? subjectName ?? "")
.toLowerCase()
.specialChars()
.trim();
String category =
subject?.category.description.toLowerCase().specialChars() ?? "";
// todo: check for categories
if (RegExp("mate(k|matika)").hasMatch(name) || category == "matematika") {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.function,
material: Icons.calculate_outlined),
name: "function");
} else if (RegExp("magyar nyelv|nyelvtan").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.textformat_alt,
material: Icons.spellcheck_outlined),
name: "textformat.alt");
} else if (RegExp("irodalom").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.book,
material: Icons.menu_book_outlined),
name: "book");
} else if (RegExp("tor(i|tenelem)").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.compass,
material: Icons.hourglass_empty_outlined),
name: "safari");
} else if (RegExp("foldrajz").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.map, material: Icons.public_outlined),
name: "map");
} else if (RegExp("rajz|muvtori|muveszet|vizualis").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.paintbrush,
material: Icons.palette_outlined),
name: "paintbrush");
} else if (RegExp("fizika").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.lightbulb,
material: Icons.emoji_objects_outlined),
name: "lightbulb");
} else if (RegExp("^enek|zene|szolfezs|zongora|korus").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.music_note,
material: Icons.music_note_outlined),
name: "music.note");
} else if (RegExp("^tes(i|tneveles)|sport").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.sportscourt,
material: Icons.sports_soccer_outlined),
name: "sportscourt");
} else if (RegExp("kemia").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.lab_flask,
material: Icons.science_outlined),
name: "testtube.2");
} else if (RegExp("biologia").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.paw, material: Icons.pets_outlined),
name: "pawprint");
} else if (RegExp(
"kornyezet|termeszet ?(tudomany|ismeret)|hon( es nep)?ismeret")
.hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.arrow_3_trianglepath,
material: Icons.eco_outlined),
name: "arrow.3.trianglepath");
} else if (RegExp("(hit|erkolcs)tan|vallas|etika").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.heart,
material: Icons.favorite_border_outlined),
name: "heart");
} else if (RegExp("penzugy").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.money_dollar,
material: Icons.savings_outlined),
name: "dollarsign");
} else if (RegExp("informatika|szoftver|iroda|digitalis").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.device_laptop,
material: Icons.computer_outlined),
name: "laptopcomputer");
} else if (RegExp("prog").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.chevron_left_slash_chevron_right,
material: Icons.code_outlined),
name: "chevron.left.forwardslash.chevron.right");
} else if (RegExp("halozat").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.antenna_radiowaves_left_right,
material: Icons.wifi_tethering_outlined),
name: "antenna.radiowaves.left.and.right");
} else if (RegExp("szinhaz").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.hifispeaker,
material: Icons.theater_comedy_outlined),
name: "hifispeaker");
} else if (RegExp("film|media").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.film,
material: Icons.theaters_outlined),
name: "film");
} else if (RegExp("elektro(tech)?nika").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.bolt,
material: Icons.electrical_services_outlined),
name: "bolt");
} else if (RegExp("gepesz|mernok|ipar").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.wrench,
material: Icons.precision_manufacturing_outlined),
name: "wrench");
} else if (RegExp("technika").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.hammer, material: Icons.build_outlined),
name: "hammer");
} else if (RegExp("tanc").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.music_mic,
material: Icons.speaker_outlined),
name: "music.mic");
} else if (RegExp("filozofia").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.bubble_left,
material: Icons.psychology_outlined),
name: "bubble.left");
} else if (RegExp("osztaly(fonoki|kozosseg)").hasMatch(name) ||
name == "ofo") {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.group, material: Icons.groups_outlined),
name: "person.3");
} else if (RegExp("gazdasag").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.chart_pie,
material: Icons.account_balance_outlined),
name: "chart.pie");
} else if (RegExp("szorgalom").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.checkmark_seal,
material: Icons.verified_outlined),
name: "checkmark.seal");
} else if (RegExp("magatartas").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.smiley,
material: Icons.emoji_people_outlined),
name: "face.smiling");
} else if (RegExp(
"angol|nemet|francia|olasz|orosz|spanyol|latin|kinai|nyelv")
.hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.globe,
material: Icons.translate_outlined),
name: "globe");
} else if (RegExp("linux").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
material: FilcIcons.linux, cupertino: FilcIcons.linux));
} else if (RegExp("adatbazis").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.table_badge_more,
material: Icons.table_chart),
name: "table.badge.more");
} else if (RegExp("asztali alkalmazasok").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.macwindow,
material: Icons.desktop_windows_outlined),
name: "macwindow");
} else if (RegExp("projekt").hasMatch(name)) {
return SubjectIconData(
data: createIcon(
cupertino: CupertinoIcons.person_3_fill,
material: Icons.groups_3),
name: "person.3.fill");
}
return SubjectIconData();
}
}
class ShortSubject {
static String resolve({GradeSubject? subject, String? subjectName}) {
assert(!(subject == null && subjectName == null));
String name = (subject?.name ?? subjectName ?? "")
.toLowerCase()
.specialChars()
.trim();
// String category = subject?.category.description.toLowerCase().specialChars() ?? "";
if (RegExp("magyar irodalom").hasMatch(name)) {
return "Irodalom";
} else if (RegExp("magyar nyelv").hasMatch(name)) {
return "Nyelvtan";
} else if (RegExp("matematika").hasMatch(name)) {
return "Matek";
} else if (RegExp("digitalis kultura").hasMatch(name)) {
return "Dig. kult.";
} else if (RegExp("testneveles").hasMatch(name)) {
return "Tesi";
} else if (RegExp("tortenelem").hasMatch(name)) {
return "Töri";
} else if (RegExp(
"(angol|nemet|francia|olasz|orosz|spanyol|latin|kinai) nyelv")
.hasMatch(name)) {
return (subject?.name ?? subjectName ?? "?").replaceAll(" nyelv", "");
} else if (RegExp("informatika").hasMatch(name)) {
return "Infó";
} else if (RegExp("osztalyfonoki").hasMatch(name)) {
return "Ofő";
}
return subject?.name.capital() ?? subjectName?.capital() ?? "?";
}
}

View File

@@ -0,0 +1,80 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:refilc/api/client.dart';
import 'package:refilc/helpers/storage_helper.dart';
import 'package:refilc/models/release.dart';
import 'package:open_filex/open_filex.dart';
import 'package:permission_handler/permission_handler.dart';
enum UpdateState { none, preparing, downloading, installing }
typedef UpdateCallback = Function(double progress, UpdateState state);
// ignore: todo
// TODO: cleanup old apk files
extension UpdateHelper on Release {
Future<void> install({UpdateCallback? updateCallback}) async {
updateCallback!(-1, UpdateState.preparing);
String downloads = await StorageHelper.downloadsPath();
File apk = File("$downloads/refilc-v$version.apk");
if (!await apk.exists()) {
updateCallback(-1, UpdateState.downloading);
var bytes = await download(updateCallback: updateCallback);
if (!await StorageHelper.write(apk.path, bytes)) {
throw "failed to write apk: permission denied";
}
}
updateCallback(-1, UpdateState.installing);
var installPerms =
(await Permission.manageExternalStorage.request().isGranted &&
await Permission.requestInstallPackages.request().isGranted);
if (installPerms) {
var result = await OpenFilex.open(apk.path);
if (result.type != ResultType.done) {
// ignore: avoid_print
print("ERROR: installUpdate.openFile: ${result.message}");
throw result.message;
}
}
updateCallback(-1, UpdateState.none);
}
Future<Uint8List> download({UpdateCallback? updateCallback}) async {
var response = await FilcAPI.downloadRelease(downloads.first);
List<List<int>> chunks = [];
int downloaded = 0;
var completer = Completer<Uint8List>();
response?.stream.listen((List<int> chunk) {
updateCallback!(
downloaded / (response.contentLength ?? 0), UpdateState.downloading);
chunks.add(chunk);
downloaded += chunk.length;
}, onDone: () {
// Save the file
final Uint8List bytes = Uint8List(response.contentLength ?? 0);
int offset = 0;
for (List<int> chunk in chunks) {
bytes.setRange(offset, offset + chunk.length, chunk);
offset += chunk.length;
}
completer.complete(bytes);
});
return completer.future;
}
}