implemented scheduled updates for widgets
This commit is contained in:
parent
b0bbad7f97
commit
6bbc75fa94
@ -1,5 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
>
|
||||
|
||||
<!--
|
||||
Required to query activities that can process text, see:
|
||||
@ -19,6 +22,7 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
tools:replace="android:label"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="Marianum Fulda">
|
||||
|
@ -2,6 +2,11 @@ allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
||||
// [required] background_fetch
|
||||
maven {
|
||||
url "${project(':background_fetch').projectDir}/libs"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ import 'storage/base/settingsProvider.dart';
|
||||
import 'view/pages/overhang.dart';
|
||||
|
||||
class App extends StatefulWidget {
|
||||
static GlobalKey appContext = GlobalKey();
|
||||
const App({super.key});
|
||||
|
||||
@override
|
||||
@ -31,7 +32,6 @@ class App extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
|
||||
late Timer refetchChats;
|
||||
late Timer updateTimings;
|
||||
|
||||
|
64
lib/background_tasks/scheduledTask.dart
Normal file
64
lib/background_tasks/scheduledTask.dart
Normal file
@ -0,0 +1,64 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:background_fetch/background_fetch.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../app.dart';
|
||||
import '../homescreen_widgets/timetable/timetableHomeWidget.dart';
|
||||
|
||||
class ScheduledTask {
|
||||
static final String fetchApiLastRunTimestampKey = 'fetchApiLastRunTimestamp';
|
||||
|
||||
static Future<void> configure() async {
|
||||
var status = await BackgroundFetch.configure(BackgroundFetchConfig(
|
||||
minimumFetchInterval: 15,
|
||||
stopOnTerminate: false,
|
||||
enableHeadless: true,
|
||||
requiresBatteryNotLow: false,
|
||||
requiresCharging: false,
|
||||
requiresStorageNotLow: false,
|
||||
requiresDeviceIdle: false,
|
||||
requiredNetworkType: NetworkType.ANY,
|
||||
startOnBoot: true,
|
||||
), (String taskId) async {
|
||||
log('Background fetch started with id $taskId');
|
||||
await ScheduledTask.backgroundFetch();
|
||||
BackgroundFetch.finish(taskId);
|
||||
}, (String taskId) async {
|
||||
log('Background fetch stopped because of an timeout with id $taskId');
|
||||
BackgroundFetch.finish(taskId);
|
||||
});
|
||||
|
||||
log('Background Fetch-API status: $status');
|
||||
}
|
||||
|
||||
// called periodically, iOS and Android
|
||||
static Future<void> backgroundFetch() async {
|
||||
var sp = await SharedPreferences.getInstance();
|
||||
var history = sp.getStringList(fetchApiLastRunTimestampKey) ?? List.empty(growable: true);
|
||||
history.add(DateTime.now().toIso8601String());
|
||||
try {
|
||||
TimetableHomeWidget.update(App.appContext.currentContext!);
|
||||
} on Exception catch(e) {
|
||||
history.add('Got Error:');
|
||||
history.add(e.toString());
|
||||
history.add('--- EXCEPTION END ---');
|
||||
}
|
||||
sp.setStringList(fetchApiLastRunTimestampKey, history.take(100).toList());
|
||||
}
|
||||
|
||||
// only Android, starts when app is terminated
|
||||
@pragma('vm:entry-point')
|
||||
static Future<void> headless(HeadlessTask task) async {
|
||||
var taskId = task.taskId;
|
||||
var isTimeout = task.timeout;
|
||||
if (isTimeout) {
|
||||
log('Background fetch headless task timed-out: $taskId');
|
||||
BackgroundFetch.finish(taskId);
|
||||
return;
|
||||
}
|
||||
log('Background fetch headless event received.');
|
||||
await backgroundFetch();
|
||||
BackgroundFetch.finish(taskId);
|
||||
}
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:home_widget/home_widget.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:screenshot/screenshot.dart';
|
||||
import 'package:syncfusion_flutter_calendar/calendar.dart';
|
||||
|
||||
import '../../model/accountData.dart';
|
||||
import '../../model/timetable/timetableProps.dart';
|
||||
import '../../storage/base/settingsProvider.dart';
|
||||
import '../../theming/darkAppTheme.dart';
|
||||
@ -15,23 +16,31 @@ import '../../theming/lightAppTheme.dart';
|
||||
import '../../view/pages/timetable/calendar.dart';
|
||||
|
||||
class TimetableHomeWidget {
|
||||
static Future<void> update(BuildContext context) async {
|
||||
await AccountData().waitForPopulation();
|
||||
var data = TimetableProps();
|
||||
var settings = SettingsProvider();
|
||||
settings.waitForPopulation();
|
||||
var completer = Completer();
|
||||
|
||||
static void update(BuildContext context) {
|
||||
var data = Provider.of<TimetableProps>(context, listen: false);
|
||||
var settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||
data.addListener(() async {
|
||||
if(completer.isCompleted) return;
|
||||
if(data.primaryLoading()) return;
|
||||
await _generate(data, settings);
|
||||
completer.complete();
|
||||
});
|
||||
|
||||
if(data.primaryLoading()) {
|
||||
log('Could not generate widget screen because no data was found!');
|
||||
return;
|
||||
}
|
||||
data.run();
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
static Future<void> _generate(TimetableProps data, SettingsProvider settings) async {
|
||||
log('Generating widget screen...');
|
||||
var screenshotController = ScreenshotController();
|
||||
var calendarController = CalendarController();
|
||||
calendarController.displayDate = DateTime.now().copyWith(hour: 07, minute: 00);
|
||||
|
||||
screenshotController.captureFromWidget(
|
||||
delay: Duration(milliseconds: 100),
|
||||
var imageData = await screenshotController.captureFromWidget(
|
||||
SizedBox(
|
||||
height: 700,
|
||||
width: 300,
|
||||
@ -55,7 +64,7 @@ class TimetableHomeWidget {
|
||||
home: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Scaffold(
|
||||
body: Calendar(
|
||||
body: Calendar(
|
||||
controller: calendarController,
|
||||
timetableProps: data,
|
||||
settings: settings,
|
||||
@ -67,10 +76,10 @@ class TimetableHomeWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
).then((value) {
|
||||
HomeWidget.saveWidgetData<String>('screen', base64.encode(value));
|
||||
HomeWidget.updateWidget(name: 'TimetableWidget');
|
||||
log('Widget screen successfully updated! (${value.length})');
|
||||
});
|
||||
);
|
||||
|
||||
HomeWidget.saveWidgetData<String>('screen', base64.encode(imageData));
|
||||
HomeWidget.updateWidget(name: 'TimetableWidget');
|
||||
log('Widget screen successfully updated! (${imageData.length})');
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:background_fetch/background_fetch.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@ -17,6 +18,7 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import 'api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
|
||||
import 'app.dart';
|
||||
import 'background_tasks/scheduledTask.dart';
|
||||
import 'firebase_options.dart';
|
||||
import 'model/accountData.dart';
|
||||
import 'model/accountModel.dart';
|
||||
@ -84,6 +86,8 @@ Future<void> main() async {
|
||||
child: const Main(),
|
||||
)
|
||||
);
|
||||
|
||||
BackgroundFetch.registerHeadlessTask(ScheduledTask.headless);
|
||||
}
|
||||
|
||||
class Main extends StatefulWidget {
|
||||
@ -111,6 +115,7 @@ class _MainState extends State<Main> {
|
||||
Provider.of<BreakerProps>(context, listen: false).run();
|
||||
});
|
||||
|
||||
ScheduledTask.configure();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -146,7 +151,7 @@ class _MainState extends State<Main> {
|
||||
child: Consumer<AccountModel>(
|
||||
builder: (context, accountModel, child) {
|
||||
switch(accountModel.state) {
|
||||
case AccountModelState.loggedIn: return const App();
|
||||
case AccountModelState.loggedIn: return App(key: App.appContext);
|
||||
case AccountModelState.loggedOut: return const Login();
|
||||
case AccountModelState.undefined: return const PlaceholderView(icon: Icons.timer, text: 'Daten werden geladen');
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
@ -13,6 +14,8 @@ class SettingsProvider extends ChangeNotifier {
|
||||
late SharedPreferences _storage;
|
||||
late Settings _settings = DefaultSettings.get();
|
||||
|
||||
final Completer<void> _populated = Completer();
|
||||
|
||||
Settings val({bool write = false}) {
|
||||
if(write) {
|
||||
notifyListeners();
|
||||
@ -56,6 +59,7 @@ class SettingsProvider extends ChangeNotifier {
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
_populated.complete();
|
||||
}
|
||||
|
||||
Future<void> update() async {
|
||||
@ -77,4 +81,8 @@ class SettingsProvider extends ChangeNotifier {
|
||||
|
||||
return mergedMap;
|
||||
}
|
||||
|
||||
Future<void> waitForPopulation() async {
|
||||
await _populated.future;
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,18 @@
|
||||
|
||||
import 'package:background_fetch/background_fetch.dart';
|
||||
import 'package:filesize/filesize.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../background_tasks/scheduledTask.dart';
|
||||
import '../../storage/base/settingsProvider.dart';
|
||||
import '../../widget/centeredLeading.dart';
|
||||
import '../../widget/confirmDialog.dart';
|
||||
import '../../widget/debug/cacheView.dart';
|
||||
import '../../widget/debug/jsonViewer.dart';
|
||||
import '../../widget/infoDialog.dart';
|
||||
|
||||
class DevToolsSettings extends StatefulWidget {
|
||||
final SettingsProvider settings;
|
||||
@ -22,6 +26,84 @@ class _DevToolsSettingsState extends State<DevToolsSettings> {
|
||||
@override
|
||||
Widget build(BuildContext context) => Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const CenteredLeading(Icon(Icons.task_outlined)),
|
||||
title: const Text('Background app fetch task'),
|
||||
trailing: const Icon(Icons.arrow_right),
|
||||
onTap: () {
|
||||
showDialog(context: context, builder: (context) => AlertDialog(
|
||||
title: Text('Background fetch task'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
FutureBuilder(future: BackgroundFetch.status, builder: (context, snapshot) {
|
||||
if(snapshot.hasData) {
|
||||
var fetchStatus = switch(snapshot.data) {
|
||||
BackgroundFetch.STATUS_AVAILABLE => 'STATUS_AVAILABLE, Background updates are available for the app.',
|
||||
BackgroundFetch.STATUS_DENIED => 'STATUS_DENIED, The user explicitly disabled background behavior for this app or for the whole system.',
|
||||
BackgroundFetch.STATUS_RESTRICTED => 'STATUS_RESTRICTED, Background updates are unavailable and the user cannot enable them again. For example, this status can occur when parental controls are in effect for the current user.',
|
||||
_ => 'UNKNOWN',
|
||||
};
|
||||
return Text('(${snapshot.data}): $fetchStatus');
|
||||
}
|
||||
return LinearProgressIndicator();
|
||||
}),
|
||||
const Divider(),
|
||||
const Text('There is no indicator if the Fetch-API is currently running or not!'),
|
||||
const Divider(),
|
||||
FutureBuilder(
|
||||
future: SharedPreferences.getInstance(),
|
||||
builder: (context, snapshot) {
|
||||
if(!snapshot.hasData) return LinearProgressIndicator();
|
||||
return Text('Last fetch timestamp: ${snapshot.data?.getStringList(ScheduledTask.fetchApiLastRunTimestampKey)?.last ?? 'No entry'}');
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
FutureBuilder(future: SharedPreferences.getInstance(), builder: (context, snapshot) {
|
||||
if(!snapshot.hasData) return LinearProgressIndicator();
|
||||
return TextButton(
|
||||
onPressed: () {
|
||||
InfoDialog.show(
|
||||
context,
|
||||
(snapshot.data!.getStringList(ScheduledTask.fetchApiLastRunTimestampKey) ?? []).reversed.join('\n')
|
||||
);
|
||||
},
|
||||
child: Text('Fetch history')
|
||||
);
|
||||
}),
|
||||
TextButton(
|
||||
onPressed: () => ConfirmDialog(
|
||||
title: 'Warning',
|
||||
content: 'Background Fetch worker will be started! This basically happens on every app startup.',
|
||||
onConfirm: BackgroundFetch.start
|
||||
).asDialog(context),
|
||||
child: Text('Fetch-API Start')
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => ConfirmDialog(
|
||||
title: 'Warning',
|
||||
content: 'Background Fetch worker will be terminated. This will result in outdated Information when App is not in foreground!',
|
||||
onConfirm: BackgroundFetch.stop
|
||||
).asDialog(context),
|
||||
child: Text('Fetch-API Stop')
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => ConfirmDialog(
|
||||
title: 'Warning',
|
||||
content: 'Background fetch will run now! This happens in the application layer and does not interact with the Fetch-API!',
|
||||
confirmButton: 'Run',
|
||||
onConfirm: ScheduledTask.backgroundFetch
|
||||
).asDialog(context),
|
||||
child: Text('Run task manually')
|
||||
),
|
||||
TextButton(onPressed: () => Navigator.of(context).pop(), child: Text('Zurück'))
|
||||
],
|
||||
));
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const CenteredLeading(Icon(Icons.speed_outlined)),
|
||||
title: const Text('Performance overlays'),
|
||||
|
@ -103,6 +103,7 @@ dependencies:
|
||||
uuid: ^4.5.1
|
||||
home_widget: ^0.7.0+1
|
||||
screenshot: ^3.0.0
|
||||
background_fetch: ^1.3.7
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Loading…
x
Reference in New Issue
Block a user