- data backup

- I can't test it because I'm stuck on the login screen.
This commit is contained in:
Horváth Gergely
2024-05-02 01:41:04 +02:00
parent b6fbfd5756
commit b7b3a37b52
15 changed files with 817 additions and 395 deletions

View File

@@ -3,6 +3,7 @@
import 'dart:async';
import 'dart:io';
import 'package:refilc/api/providers/liveactivity/platform_channel.dart';
import 'package:refilc/helpers/subject.dart';
import 'package:refilc/models/settings.dart';
import 'package:refilc_kreta_api/models/lesson.dart';
@@ -10,7 +11,6 @@ import 'package:refilc_kreta_api/models/week.dart';
import 'package:refilc/utils/format.dart';
import 'package:refilc_kreta_api/providers/timetable_provider.dart';
import 'package:flutter/foundation.dart';
import 'package:live_activities/live_activities.dart';
import 'package:refilc_mobile_ui/pages/home/live_card/live_card.i18n.dart';
enum LiveCardState {
@@ -29,6 +29,15 @@ class LiveCardProvider extends ChangeNotifier {
Lesson? prevLesson;
List<Lesson>? nextLessons;
// new variables
static bool hasActivityStarted = false;
static bool hasDayEnd = false;
static DateTime? storeFirstRunDate;
static bool hasActivitySettingsChanged = false;
static Map<String, String> LAData = {};
static DateTime? now;
//
LiveCardState currentState = LiveCardState.empty;
late Timer _timer;
late final TimetableProvider _timetable;
@@ -36,9 +45,6 @@ class LiveCardProvider extends ChangeNotifier {
late Duration _delay;
final _liveActivitiesPlugin = LiveActivities();
String? _latestActivityId;
Map<String, String> _lastActivity = {};
bool _hasCheckedTimetable = false;
@@ -47,23 +53,6 @@ class LiveCardProvider extends ChangeNotifier {
required SettingsProvider settings,
}) : _timetable = timetable,
_settings = settings {
if (Platform.isIOS) {
_liveActivitiesPlugin.areActivitiesEnabled().then((value) {
// Console log
if (kDebugMode) {
print("iOS LiveActivity enabled: $value");
}
if (value) {
_liveActivitiesPlugin.init(appGroupId: "group.refilc2.livecard");
_liveActivitiesPlugin.getAllActivitiesIds().then((value) {
_latestActivityId = value.isNotEmpty ? value.first : null;
});
}
});
}
_timer = Timer.periodic(const Duration(seconds: 1), (timer) => update());
_delay = settings.bellDelayEnabled
? Duration(seconds: settings.bellDelay)
@@ -71,21 +60,6 @@ class LiveCardProvider extends ChangeNotifier {
update();
}
@override
void dispose() {
_timer.cancel();
if (Platform.isIOS) {
_liveActivitiesPlugin.areActivitiesEnabled().then((value) {
if (value) {
if (_latestActivityId != null) {
_liveActivitiesPlugin.endActivity(_latestActivityId!);
}
}
});
}
super.dispose();
}
// Debugging
static DateTime _now() {
// return DateTime(2023, 9, 27, 9, 30);
@@ -110,31 +84,88 @@ class LiveCardProvider extends ChangeNotifier {
Map<String, String> toMap() {
switch (currentState) {
case LiveCardState.morning:
return {
"color":
'#${_settings.liveActivityColor.toString().substring(10, 16)}',
"icon": nextLesson != null
? SubjectIcon.resolveName(subject: nextLesson?.subject)
: "book",
"title": "Első órádig:",
"subtitle": "",
"description": "",
"startDate": storeFirstRunDate != null ? ((storeFirstRunDate?.millisecondsSinceEpoch ?? 0) - (_delay.inMilliseconds)).toString(): "",
"endDate": ((nextLesson?.start.millisecondsSinceEpoch ?? 0) -
_delay.inMilliseconds)
.toString(),
"nextSubject": nextLesson != null
? nextLesson?.subject.renamedTo ?? ShortSubject.resolve(subject: nextLesson?.subject).capital()
: "",
"nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "",
};
case LiveCardState.afternoon:
return {
"color":
'#${_settings.liveActivityColor.toString().substring(10, 16)}',
"icon": nextLesson != null
? SubjectIcon.resolveName(subject: nextLesson?.subject)
: "book",
"title": "Első órádig:",
"subtitle": "",
"description": "",
"startDate": storeFirstRunDate != null ? ((storeFirstRunDate?.millisecondsSinceEpoch ?? 0) - (_delay.inMilliseconds)).toString(): "",
"endDate": ((nextLesson?.start.millisecondsSinceEpoch ?? 0) -
_delay.inMilliseconds)
.toString(),
"nextSubject": nextLesson != null
? nextLesson?.subject.renamedTo ?? ShortSubject.resolve(subject: nextLesson?.subject).capital()
: "",
"nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "",
};
case LiveCardState.night:
return {
"color":
'#${_settings.liveActivityColor.toString().substring(10, 16)}',
"icon": nextLesson != null
? SubjectIcon.resolveName(subject: nextLesson?.subject)
: "book",
"title": "Első órádig:",
"subtitle": "",
"description": "",
"startDate": storeFirstRunDate != null ? ((storeFirstRunDate?.millisecondsSinceEpoch ?? 0) - (_delay.inMilliseconds)).toString(): "",
"endDate": ((nextLesson?.start.millisecondsSinceEpoch ?? 0) -
_delay.inMilliseconds)
.toString(),
"nextSubject": nextLesson != null
? nextLesson?.subject.renamedTo ?? ShortSubject.resolve(subject: nextLesson?.subject).capital()
: "",
"nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "",
};
case LiveCardState.duringLesson:
return {
"color":
'#${_settings.liveActivityColor.toString().substring(10, 16)}',
'#${_settings.liveActivityColor.toString().substring(10, 16)}',
"icon": currentLesson != null
? SubjectIcon.resolveName(subject: currentLesson?.subject)
: "book",
"index":
currentLesson != null ? '${currentLesson!.lessonIndex}. ' : "",
currentLesson != null ? '${currentLesson!.lessonIndex}. ' : "",
"title": currentLesson != null
? currentLesson?.subject.renamedTo ??
ShortSubject.resolve(subject: currentLesson?.subject)
.capital()
? currentLesson?.subject.renamedTo ?? ShortSubject.resolve(subject: currentLesson?.subject).capital()
: "",
"subtitle": currentLesson?.room.replaceAll("_", " ") ?? "",
"description": currentLesson?.description ?? "",
"startDate": ((currentLesson?.start.millisecondsSinceEpoch ?? 0) -
_delay.inMilliseconds)
_delay.inMilliseconds)
.toString(),
"endDate": ((currentLesson?.end.millisecondsSinceEpoch ?? 0) -
_delay.inMilliseconds)
_delay.inMilliseconds)
.toString(),
"nextSubject": nextLesson != null
? nextLesson?.subject.renamedTo ??
ShortSubject.resolve(subject: nextLesson?.subject).capital()
? nextLesson?.subject.renamedTo ?? ShortSubject.resolve(subject: nextLesson?.subject).capital()
: "",
"nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "",
};
@@ -150,23 +181,21 @@ class LiveCardProvider extends ChangeNotifier {
return {
"color":
'#${_settings.liveActivityColor.toString().substring(10, 16)}',
'#${_settings.liveActivityColor.toString().substring(10, 16)}',
"icon": iconFloorMap[diff] ?? "cup.and.saucer",
"title": "Szünet",
"description": "go $diff".i18n.fill([
diff != "to room" ? (nextLesson!.getFloor() ?? 0) : nextLesson!.room
]),
"startDate": ((prevLesson?.end.millisecondsSinceEpoch ?? 0) -
_delay.inMilliseconds)
_delay.inMilliseconds)
.toString(),
"endDate": ((nextLesson?.start.millisecondsSinceEpoch ?? 0) -
_delay.inMilliseconds)
_delay.inMilliseconds)
.toString(),
"nextSubject": (nextLesson != null
? nextLesson?.subject.renamedTo ??
ShortSubject.resolve(subject: nextLesson?.subject)
.capital()
: "")
? nextLesson?.subject.renamedTo ?? ShortSubject.resolve(subject: nextLesson?.subject).capital()
: "")
.capital(),
"nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "",
"index": "",
@@ -178,37 +207,6 @@ class LiveCardProvider extends ChangeNotifier {
}
void update() async {
if (Platform.isIOS) {
_liveActivitiesPlugin.areActivitiesEnabled().then((value) {
if (value) {
final cmap = toMap();
if (!mapEquals(cmap, _lastActivity)) {
_lastActivity = cmap;
try {
if (_lastActivity.isNotEmpty) {
if (_latestActivityId == null) {
_liveActivitiesPlugin
.createActivity(_lastActivity)
.then((value) => _latestActivityId = value);
} else {
_liveActivitiesPlugin.updateActivity(
_latestActivityId!, _lastActivity);
}
} else {
if (_latestActivityId != null) {
_liveActivitiesPlugin.endActivity(_latestActivityId!);
}
}
} catch (e) {
if (kDebugMode) {
print('ERROR: Unable to create or update iOS LiveActivity!');
}
}
}
}
});
}
List<Lesson> today = _today(_timetable);
if (today.isEmpty && !_hasCheckedTimetable) {
@@ -221,15 +219,22 @@ class LiveCardProvider extends ChangeNotifier {
? Duration(seconds: _settings.bellDelay)
: Duration.zero;
final now = _now().add(_delay);
DateTime now = _now().add(_delay);
if ((currentState == LiveCardState.morning ||
currentState == LiveCardState.afternoon ||
currentState == LiveCardState.night) && storeFirstRunDate == null) {
storeFirstRunDate = now;
}
// Filter cancelled lessons #20
// Filter label lessons #128
today = today
.where((lesson) =>
lesson.status?.name != "Elmaradt" &&
lesson.subject.id != '' &&
!lesson.isEmpty)
lesson.status?.name != "Elmaradt" &&
lesson.subject.id != '' &&
!lesson.isEmpty)
.toList();
if (today.isNotEmpty) {
@@ -237,7 +242,7 @@ class LiveCardProvider extends ChangeNotifier {
today.sort((a, b) => a.start.compareTo(b.start));
final _lesson = today.firstWhere(
(l) => l.start.isBefore(now) && l.end.isAfter(now),
(l) => l.start.isBefore(now) && l.end.isAfter(now),
orElse: () => Lesson.fromJson({}));
if (_lesson.start.year != 0) {
@@ -283,11 +288,65 @@ class LiveCardProvider extends ChangeNotifier {
currentState = LiveCardState.empty;
}
//LIVE ACTIVITIES
//CREATE
if (!hasActivityStarted && nextLesson != null && nextLesson!
.start
.difference(now)
.inMinutes <= 60 && (currentState == LiveCardState.morning ||
currentState == LiveCardState.afternoon ||
currentState == LiveCardState.night)) {
debugPrint(
"Az első óra előtt állunk, kevesebb mint egy órával. Létrehozás...");
PlatformChannel.createLiveActivity(toMap());
hasActivityStarted = true;
}
else if (!hasActivityStarted && ((currentState == LiveCardState.duringLesson &&
currentLesson != null) ||
currentState == LiveCardState.duringBreak)) {
debugPrint(
"Óra van, vagy szünet, de nincs LiveActivity. létrehozás...");
PlatformChannel.createLiveActivity(toMap());
hasActivityStarted = true;
}
//UPDATE
else if (hasActivityStarted) {
if (hasActivitySettingsChanged) {
debugPrint("Valamelyik beállítás megváltozott. Frissítés...");
PlatformChannel.updateLiveActivity(toMap());
hasActivitySettingsChanged = false;
}
else if (nextLesson != null || currentLesson != null) {
bool afterPrevLessonEnd = prevLesson != null &&
now.subtract(const Duration(seconds: 1)).isBefore(
prevLesson!.end) && now.isAfter(prevLesson!.end);
bool afterCurrentLessonStart = currentLesson != null &&
now.subtract(const Duration(seconds: 1)).isBefore(
currentLesson!.start) && now.isAfter(currentLesson!.start);
if (afterPrevLessonEnd || afterCurrentLessonStart) {
debugPrint(
"Óra kezdete/vége után 1 másodperccel vagyunk. Frissítés...");
PlatformChannel.updateLiveActivity(toMap());
}
}
}
//END
if (hasActivityStarted && !hasDayEnd && nextLesson == null &&
now.isAfter(prevLesson!.end)) {
debugPrint("Az utolsó óra véget ért. Befejezés...");
PlatformChannel.endLiveActivity();
hasDayEnd = true;
hasActivityStarted = false;
}
LAData = toMap();
notifyListeners();
}
bool get show => currentState != LiveCardState.empty;
Duration get delay => _delay;
bool _sameDate(DateTime a, DateTime b) =>
@@ -296,4 +355,4 @@ class LiveCardProvider extends ChangeNotifier {
List<Lesson> _today(TimetableProvider p) => (p.getWeek(Week.current()) ?? [])
.where((l) => _sameDate(l.date, _now()))
.toList();
}
}

View File

@@ -0,0 +1,43 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
class PlatformChannel {
static const MethodChannel _channel = MethodChannel('hu.refilc/liveactivity');
static Future<void> createLiveActivity(
Map<String, dynamic> activityData) async {
if (Platform.isIOS) {
try {
debugPrint("creating...");
await _channel.invokeMethod('createLiveActivity', activityData);
} on PlatformException catch (e) {
debugPrint("Hiba történt a Live Activity létrehozásakor: ${e.message}");
}
}
}
static Future<void> updateLiveActivity(
Map<String, dynamic> activityData) async {
if (Platform.isIOS) {
try {
debugPrint("updating...");
await _channel.invokeMethod('updateLiveActivity', activityData);
} on PlatformException catch (e) {
debugPrint("Hiba történt a Live Activity frissítésekor: ${e.message}");
}
}
}
static Future<void> endLiveActivity() async {
if (Platform.isIOS) {
try {
debugPrint("finishing...");
await _channel.invokeMethod('endLiveActivity');
} on PlatformException catch (e) {
debugPrint("Hiba történt a Live Activity befejezésekor: ${e.message}");
}
}
}
}

View File

@@ -22,6 +22,9 @@ import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
import 'package:home_widget/home_widget.dart';
import 'live_card_provider.dart';
import 'liveactivity/platform_channel.dart';
// Mutex
bool lock = false;
@@ -86,10 +89,17 @@ Future<void> syncAll(BuildContext context) {
return false;
}
return Future.wait(tasks).then((value) {
// Unlock
lock = false;
if(Platform.isIOS && LiveCardProvider.hasActivityStarted == true){
PlatformChannel.endLiveActivity();
LiveCardProvider.hasActivityStarted = false;
}
// Update Widget
if (Platform.isAndroid) updateWidget();
});

View File

@@ -0,0 +1,15 @@
import 'package:refilc/api/providers/live_card_provider.dart';
import '../api/providers/liveactivity/platform_channel.dart';
class LiveActivityHelper {
@pragma('vm:entry-point')
void backgroundJob() async {
// initialize provider
if (!LiveCardProvider.hasDayEnd) {
await PlatformChannel.updateLiveActivity(LiveCardProvider.LAData);
} else {
await PlatformChannel.endLiveActivity();
}
}
}

View File

@@ -16,6 +16,8 @@ import 'package:refilc_mobile_ui/screens/error_screen.dart';
import 'package:refilc_mobile_ui/screens/error_report_screen.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'helpers/live_activity_helper.dart';
// days without touching grass: 5,843 (16 yrs)
void main() async {
@@ -84,6 +86,7 @@ class Startup {
// Notifications setup
if (!kIsWeb) {
initPlatformState();
initAdditionalBackgroundFetch();
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
}
@@ -196,7 +199,12 @@ Future<void> initPlatformState() async {
if (kDebugMode) {
print("[BackgroundFetch] Event received $taskId");
}
NotificationsHelper().backgroundJob();
if (taskId == "com.transistorsoft.refilcliveactivity") {
if (!Platform.isIOS) return;
LiveActivityHelper().backgroundJob();
} else {
NotificationsHelper().backgroundJob();
}
BackgroundFetch.finish(taskId);
}, (String taskId) async {
// <-- Task timeout handler.
@@ -231,6 +239,50 @@ void backgroundHeadlessTask(HeadlessTask task) {
if (kDebugMode) {
print('[BackgroundFetch] Headless event received.');
}
NotificationsHelper().backgroundJob();
BackgroundFetch.finish(task.taskId);
if (taskId == "com.transistorsoft.refilcliveactivity") {
if (!Platform.isIOS) return;
LiveActivityHelper().backgroundJob();
} else {
NotificationsHelper().backgroundJob();
} BackgroundFetch.finish(task.taskId);
}
Future<void> initAdditionalBackgroundFetch() async {
int status = await BackgroundFetch.configure(
BackgroundFetchConfig(
minimumFetchInterval: 1, // 1 minute
stopOnTerminate: false,
enableHeadless: true,
requiresBatteryNotLow: false,
requiresCharging: false,
requiresStorageNotLow: false,
requiresDeviceIdle: false,
requiredNetworkType: NetworkType.ANY,
startOnBoot: true), (String taskId) async {
// <-- Event handler
if (kDebugMode) {
print("[BackgroundFetch] Event received $taskId");
}
LiveActivityHelper liveActivityHelper = LiveActivityHelper();
liveActivityHelper.backgroundJob();
BackgroundFetch.finish(taskId);
}, (String taskId) async {
// <-- Task timeout handler.
if (kDebugMode) {
print("[BackgroundFetch] TASK TIMEOUT taskId: $taskId");
}
BackgroundFetch.finish(taskId);
});
if (kDebugMode) {
print('[BackgroundFetch] configure success: $status');
}
BackgroundFetch.scheduleTask(TaskConfig(
taskId: "com.transistorsoft.refilcliveactivity",
delay: 300000, // 5 minute
periodic: true,
forceAlarmManager: true,
stopOnTerminate: false,
enableHeadless: true));
}

View File

@@ -1,5 +1,6 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:refilc/api/providers/database_provider.dart';
@@ -10,6 +11,8 @@ import 'package:refilc/theme/colors/dark_mobile.dart';
import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart';
import '../api/providers/live_card_provider.dart';
enum Pages { home, grades, timetable, notes, absences }
enum UpdateChannel { stable, beta, dev }
@@ -666,6 +669,9 @@ class SettingsProvider extends ChangeNotifier {
if (bellDelay != null && bellDelay != _bellDelay) _bellDelay = bellDelay;
if (bellDelayEnabled != null && bellDelayEnabled != _bellDelayEnabled) {
_bellDelayEnabled = bellDelayEnabled;
if(Platform.isIOS){
LiveCardProvider.hasActivitySettingsChanged = true;
}
}
if (gradeOpeningFun != null && gradeOpeningFun != _gradeOpeningFun) {
_gradeOpeningFun = gradeOpeningFun;