Skip to content

Commit

Permalink
Merge pull request #333 from lamarios/feature/work_manager_for_backgr…
Browse files Browse the repository at this point in the history
…ound_refresh

switch from foreground service to use workmanager that uses latest re…
  • Loading branch information
lamarios authored Oct 4, 2023
2 parents c2b95ec + 641507f commit 6ef5978
Show file tree
Hide file tree
Showing 10 changed files with 69 additions and 179 deletions.
8 changes: 8 additions & 0 deletions lib/home/views/screens/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ class _HomeScreenState extends State<HomeScreen> {
onNotificationCreatedMethod: NotificationController.onNotificationCreatedMethod,
onNotificationDisplayedMethod: NotificationController.onNotificationDisplayedMethod,
onDismissActionReceivedMethod: NotificationController.onDismissActionReceivedMethod);

AwesomeNotifications().getInitialNotificationAction().then((initialNotification) {
print('Initial notification ${initialNotification?.payload}');

if (initialNotification != null) {
NotificationController.onActionReceivedMethod(initialNotification);
}
});
}

@override
Expand Down
4 changes: 1 addition & 3 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:invidious/app/states/app.dart';
import 'package:invidious/downloads/states/download_manager.dart';
import 'package:invidious/foreground_service.dart';
import 'package:invidious/workmanager.dart';
import 'package:invidious/globals.dart';
import 'package:invidious/httpOverrides.dart';
import 'package:invidious/mediaHander.dart';
Expand Down Expand Up @@ -54,8 +54,6 @@ Future<void> main() async {
db = await DbClient.create();

initializeNotifications();
var initialNotification = await AwesomeNotifications().getInitialNotificationAction();
print('Initial notification ${initialNotification?.payload}');

isTv = await isDeviceTv();
runApp(MultiBlocProvider(providers: [
Expand Down
13 changes: 2 additions & 11 deletions lib/notifications/views/components/bell_icon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,13 @@ class BellIcon<T> extends StatelessWidget {
var locals = AppLocalizations.of(context)!;
okCancelDialog(context, locals.askToEnableBackgroundServiceTitle, locals.askToEnableBackgroundServiceContent, () async {
var settings = context.read<SettingsCubit>();
var res = await settings.setBackgroundNotifications(true);
await settings.setBackgroundNotifications(true);
if (context.mounted) {
if (res == EnableBackGroundNotificationResponse.needBatteryOptimization) {
showBatteryOptimizationDialog(context);
} else {
cubit.toggle();
}
cubit.toggle();
}
});
}
break;
case TurnOnStatus.needToEnableBatteryOptimization:
if(context.mounted) {
showBatteryOptimizationDialog(context);
}
break;
default:
break;
}
Expand Down
52 changes: 22 additions & 30 deletions lib/settings/states/settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ import 'package:easy_debounce/easy_debounce.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:invidious/app/states/app.dart';
import 'package:invidious/foreground_service.dart';
import 'package:invidious/workmanager.dart';
import 'package:locale_names/locale_names.dart';
import 'package:logging/logging.dart';
import 'package:optimize_battery/optimize_battery.dart';
import 'package:package_info_plus/package_info_plus.dart';

import '../../database.dart';
Expand All @@ -26,6 +25,7 @@ const String searchHistoryDefaultLength = '12';

enum EnableBackGroundNotificationResponse {
ok,
notificationsNotAllowed,
needBatteryOptimization;
}

Expand Down Expand Up @@ -324,28 +324,23 @@ class SettingsCubit extends Cubit<SettingsState> {
if (!b) {
var state = this.state.copyWith();
state.backgroundNotifications = b;
backgroundService.invoke('stopService');
await stopTasks();
emit(state);
} else {
AwesomeNotifications().isNotificationAllowed().then((isAllowed) {
if (!isAllowed) {
// This is just a basic example. For real apps, you must show some
// friendly dialog box before call the request method.
// This is very important to not harm the user experience
AwesomeNotifications().requestPermissionToSendNotifications();
var isAllowed = await AwesomeNotifications().isNotificationAllowed();
if (!isAllowed) {
var allowed = await AwesomeNotifications().requestPermissionToSendNotifications();
if (!allowed) {
return EnableBackGroundNotificationResponse.notificationsNotAllowed;
}
});

var ignoringBatterOptimization = await OptimizeBattery.isIgnoringBatteryOptimizations();
if (!ignoringBatterOptimization) {
return EnableBackGroundNotificationResponse.needBatteryOptimization;
} else {
var state = this.state.copyWith();
state.backgroundNotifications = b;
backgroundService.startService();
emit(state);
return EnableBackGroundNotificationResponse.ok;
}

var state = this.state.copyWith();
state.backgroundNotifications = b;
await configureBackgroundService(this);
await setupTasks(this);
emit(state);
return EnableBackGroundNotificationResponse.ok;
}

return EnableBackGroundNotificationResponse.ok;
Expand All @@ -363,7 +358,7 @@ class SettingsCubit extends Cubit<SettingsState> {
state.backgroundNotificationFrequency = i;
emit(state);
EasyDebounce.debounce('restarting-background-service', const Duration(seconds: 2), () {
backgroundService.invoke(restartTimerMethod);
setupTasks(this);
});
}
}
Expand Down Expand Up @@ -433,9 +428,7 @@ class SettingsState {
Country get country => getCountryFromCode(_get(BROWSING_COUNTRY)?.value ?? 'US');

set country(Country c) {
String code = countryCodes
.firstWhere((element) => element.name == c.name, orElse: () => country)
.code;
String code = countryCodes.firstWhere((element) => element.name == c.name, orElse: () => country).code;
_set(BROWSING_COUNTRY, code);
}

Expand Down Expand Up @@ -475,12 +468,11 @@ class SettingsState {

set useSearchHistory(bool b) => _set(USE_SEARCH_HISTORY, b);

List<HomeDataSource> get appLayout =>
(_get(APP_LAYOUT)?.value ?? '${HomeDataSource.home.name},${HomeDataSource.subscription.name},${HomeDataSource.playlist.name},${HomeDataSource.history.name}')
.split(',')
.where((element) => element.isNotEmpty)
.map((e) => HomeDataSource.values.firstWhere((element) => element.name == e))
.toList();
List<HomeDataSource> get appLayout => (_get(APP_LAYOUT)?.value ?? '${HomeDataSource.home.name},${HomeDataSource.subscription.name},${HomeDataSource.playlist.name},${HomeDataSource.history.name}')
.split(',')
.where((element) => element.isNotEmpty)
.map((e) => HomeDataSource.values.firstWhere((element) => element.name == e))
.toList();

set appLayout(List<HomeDataSource> layout) => _set(APP_LAYOUT, layout.map((e) => e.name).join(","));

Expand Down
3 changes: 0 additions & 3 deletions lib/settings/views/screens/notifications.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ class NotificationSettingsScreen extends StatelessWidget {
enableBackgroundService(BuildContext context, bool enable) async {
var settings = context.read<SettingsCubit>();
var result = await settings.setBackgroundNotifications(enable);
if (result == EnableBackGroundNotificationResponse.needBatteryOptimization && context.mounted) {
showBatteryOptimizationDialog(context);
}
}

@override
Expand Down
2 changes: 1 addition & 1 deletion lib/settings/views/screens/settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ class SettingsScreen extends StatelessWidget {
SettingsTile.navigation(
leading: const Icon(Icons.notifications_outlined),
title: Text('${locals.notifications} (beta)'),
description: Text(locals.notificationsDescription),
description: Text(_.backgroundNotifications ? locals.foregroundServiceNotificationContent(_.backgroundNotificationFrequency.toString()):locals.notificationsDescription),
onPressed: openNotificationSettings,
),
SettingsTile.navigation(
Expand Down
8 changes: 0 additions & 8 deletions lib/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import 'package:invidious/utils/views/tv/components/tv_button.dart';
import 'package:invidious/utils/views/tv/components/tv_overscan.dart';
import 'package:invidious/videos/models/base_video.dart';
import 'package:logging/logging.dart';
import 'package:optimize_battery/optimize_battery.dart';
import 'package:share_plus/share_plus.dart';

import 'utils/models/country.dart';
Expand Down Expand Up @@ -210,13 +209,6 @@ okCancelDialog(BuildContext context, String title, String message, Function() on
);
}

showBatteryOptimizationDialog(BuildContext context) {
if (!context.mounted) return;

var locals = AppLocalizations.of(context)!;
okCancelDialog(context, locals.askForDisableBatteryOptimizationTitle, locals.askForDisableBatteryOptimizationContent,
() => OptimizeBattery.openBatteryOptimizationSettings());
}

showTvAlertdialog(BuildContext context, String title, List<Widget> body) {
var locals = AppLocalizations.of(context)!;
Expand Down
105 changes: 25 additions & 80 deletions lib/foreground_service.dart → lib/workmanager.dart
Original file line number Diff line number Diff line change
@@ -1,112 +1,57 @@
import 'dart:async';
import 'dart:ui';

import 'package:awesome_notifications/awesome_notifications.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_background_service/flutter_background_service.dart';
import 'package:flutter_background_service_android/flutter_background_service_android.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:http/http.dart';
import 'package:intl/intl.dart';
import 'package:invidious/database.dart';
import 'package:invidious/globals.dart';
import 'package:invidious/notifications/models/db/subscription_notifications.dart';
import 'package:invidious/settings/states/settings.dart';
import 'package:invidious/videos/models/video_in_list.dart';
import 'package:logging/logging.dart';
import 'package:workmanager/workmanager.dart';

import 'notifications/notifications.dart';

const restartTimerMethod = 'restart-timer';
var log = Logger('background-task');
const taskName = "Clipious background refresh task";

final backgroundService = FlutterBackgroundService();

final log = Logger('Background service');

const debugMode = kDebugMode;
// const debugMode = true;

Timer? timer;

void configureBackgroundService(SettingsCubit settings) async {
var notif = NotificationTypes.foregroundService;

var locals = await getLocalization();

await backgroundService.configure(
iosConfiguration: IosConfiguration(),
androidConfiguration: AndroidConfiguration(
onStart: onStart,
autoStart: settings.state.backgroundNotifications,
autoStartOnBoot: settings.state.backgroundNotifications,
isForegroundMode: true,
foregroundServiceNotificationId: notif.idSpace,
initialNotificationTitle: locals.foregroundServiceNotificationTitle,
initialNotificationContent: locals.foregroundServiceNotificationContent(refreshRate),
notificationChannelId: notif.id));
}

String get refreshRate => db.getSettings(BACKGROUND_CHECK_FREQUENCY)?.value ?? "1";

@pragma('vm:entry-point')
onStart(ServiceInstance service) async {
print("Background service started");

DartPluginRegistrant.ensureInitialized();

if (service is AndroidServiceInstance) {
service.on('setAsForeground').listen((event) {
service.setAsForegroundService();
});

service.on('setAsBackground').listen((event) {
service.setAsBackgroundService();
});
Future<void> configureBackgroundService(SettingsCubit settings) async {
if (settings.state.backgroundNotifications) {
await Workmanager().initialize(callbackDispatcher, isInDebugMode: kDebugMode);
}
}

service.on('stopService').listen((event) {
print('foreground service stopped');
service.stopSelf();
});

service.on(restartTimerMethod).listen((event) async {
await _restartTimer();
});
Future<void> setupTasks(SettingsCubit settings) async {
await Workmanager().registerPeriodicTask(taskName, taskName,
frequency: kDebugMode ? Duration(seconds: 15) : Duration(hours: settings.state.backgroundNotificationFrequency),
constraints: Constraints(networkType: NetworkType.connected, requiresBatteryNotLow: true));
}

// we run the background stuff once when it starts
_backgroundCheck();
_restartTimer();
Future<void> stopTasks() async {
await Workmanager().cancelAll();
}

_restartTimer() async {
print('setting background timer');
db = await DbClient.create();
var locals = await getLocalization();
var title = locals.foregroundServiceNotificationTitle;
sendNotification(title, locals.foregroundServiceNotificationContent(refreshRate), type: NotificationTypes.foregroundService);
timer?.cancel();
timer = Timer.periodic(debugMode ? const Duration(seconds: 60) : Duration(hours: int.parse(refreshRate)), (timer) {
print('foreground service running');
_backgroundCheck();
@pragma('vm:entry-point') // Mandatory if the App is obfuscated or using Flutter 3.1+
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) async {
log.info("Native called background task: $task"); //simpleTask will be emitted here.
if (task == taskName) {
await _backgroundCheck();
}
return Future.value(true);
});

db.close();
}

_backgroundCheck() async {
try {
db = await DbClient.create();

var locals = await getLocalization();
var title = locals.foregroundServiceNotificationTitle;
print('we have a db ${db.isClosed}');
sendNotification(title, locals.foregroundServiceUpdatingSubscriptions, type: NotificationTypes.foregroundService);
await _handleSubscriptionsNotifications();
sendNotification(title, locals.foregroundServiceUpdatingChannels, type: NotificationTypes.foregroundService);
await _handleChannelNotifications();
sendNotification(title, locals.foregroundServiceUpdatingPlaylist, type: NotificationTypes.foregroundService);
await _handlePlaylistNotifications();
sendNotification(title, locals.foregroundServiceNotificationContent(refreshRate), type: NotificationTypes.foregroundService);

service.syncHistory();
} catch (e) {
Expand Down Expand Up @@ -143,7 +88,7 @@ _handlePlaylistNotifications() async {
var locals = await getLocalization();

print('$videosToNotifyAbout videos from playlist ${n.playlistName} to notify about');
if (debugMode || videosToNotifyAbout > 0) {
if (kDebugMode || videosToNotifyAbout > 0) {
sendNotification(locals.playlistNotificationTitle(n.playlistName), locals.playlistNotificationContent(n.playlistName, videosToNotifyAbout),
type: NotificationTypes.playlist,
payload: {
Expand Down Expand Up @@ -181,7 +126,7 @@ _handleChannelNotifications() async {
var locals = await getLocalization();

print('$videosToNotifyAbout videos from channel ${n.channelName} to notify about');
if (debugMode || videosToNotifyAbout > 0) {
if (kDebugMode || videosToNotifyAbout > 0) {
sendNotification(locals.channelNotificationTitle(n.channelName), locals.channelNotificationContent(n.channelName, videosToNotifyAbout),
type: NotificationTypes.channel, payload: {channelId: n.channelId, lastSeenVideo: videos.videos.first.videoId}, id: n.id);
}
Expand Down Expand Up @@ -223,7 +168,7 @@ _handleSubscriptionsNotifications() async {
var locals = await getLocalization();

print('$videosToNotifyAbout videos to notify about');
if (debugMode || videosToNotifyAbout > 0) {
if (kDebugMode || videosToNotifyAbout > 0) {
sendNotification(locals.subscriptionNotificationTitle, locals.subscriptionNotificationContent(videosToNotifyAbout),
type: NotificationTypes.subscription, payload: {lastSeenVideo: videos.first.videoId});
}
Expand Down
Loading

0 comments on commit 6ef5978

Please sign in to comment.