Skip to content

Commit

Permalink
fix(#154): REFACTOR BACKGROUND NOTIFICATION service
Browse files Browse the repository at this point in the history
  • Loading branch information
JordyHers committed Oct 28, 2023
1 parent 8285445 commit 8840a96
Show file tree
Hide file tree
Showing 17 changed files with 141 additions and 91 deletions.
4 changes: 3 additions & 1 deletion .idea/libraries/Flutter_Plugins.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
compileSdkVersion 33
compileSdkVersion 34

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
Expand Down
1 change: 1 addition & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />

<application
Expand Down
3 changes: 0 additions & 3 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ allprojects {
repositories {
google()
jcenter()
maven {
url "${project(':background_fetch').projectDir}/libs"
}
}
}

Expand Down
35 changes: 0 additions & 35 deletions lib/app/config/android_config.dart
Original file line number Diff line number Diff line change
@@ -1,36 +1 @@
import 'package:background_fetch/background_fetch.dart';
import 'package:times_up_flutter/widgets/show_logger.dart';

@pragma('vm:entry-point')
Future<void> backgroundFetchHeadlessTask(HeadlessTask task) async {
final taskId = task.taskId;
final isTimeout = task.timeout;

JHLogger.$.e('[BackgroundFetch] Headless event received.');

if (isTimeout) {
JHLogger.$.e('[BackgroundFetch] Headless task timed-out: $taskId');
BackgroundFetch.finish(taskId);
return;
}
}

Future<void> configureBackgroundFetch() async {
await BackgroundFetch.configure(
BackgroundFetchConfig(
minimumFetchInterval: 1,
stopOnTerminate: false,
enableHeadless: true,
requiresBatteryNotLow: false,
requiresCharging: false,
requiresStorageNotLow: false,
requiresDeviceIdle: false,
requiredNetworkType: NetworkType.NONE,
),
backgroundFetchHeadlessTask,
).then((int status) {
JHLogger.$.e('[BackgroundFetch] Headless task timed-out- $status');
}).catchError((Object e) {
JHLogger.$.d('[BackgroundFetch] configure ERROR: $e');
});
}
1 change: 0 additions & 1 deletion lib/app/features/child_side/child_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ class _ChildPageState extends State<ChildPage> with WidgetsBindingObserver {
@override
void initState() {
super.initState();

WidgetsBinding.instance.addObserver(this);
}

Expand Down
13 changes: 7 additions & 6 deletions lib/app/features/child_side/set_child_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,26 +56,27 @@ class _SetChildPageState extends State<SetChildPage> {
}

Future<void> _submit(String name, String key, BuildContext context) async {
final database = Provider.of<Database>(context, listen: false);
final databaseStore = Provider.of<Database>(context, listen: false);
final geo = Provider.of<GeoLocatorService>(context, listen: false);
final apps = Provider.of<AppUsageService>(context, listen: false);
final appUsage = Provider.of<AppUsageService>(context, listen: false);
final position = await geo.getInitialLocation();
final battery = await Battery().batteryLevel;

try {
final response = await database.getUserCurrentChild(
final response = await databaseStore.getUserCurrentChild(
key,
apps,
appUsage,
GeoPoint(position.latitude, position.longitude),
battery: battery.toString(),
);
JHLogger.$.d('RESPONSE : $response');

try {
if (mounted) {
await Navigator.of(context).pushReplacement(
MaterialPageRoute<ChildPage>(
fullscreenDialog: true,
builder: (context) =>
ChildPage.create(context, database, response),
ChildPage.create(context, databaseStore, response),
),
);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/app/features/parent_side/parent_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class _ParentPageState extends State<ParentPage>

Widget _buildNotificationPage(AuthBase auth) {
return Provider<NotificationService>(
create: (_) => NotificationService(),
create: (context) => NotificationService(),
builder: (context, __) {
return NotificationPage.create(context, auth);
},
Expand Down
9 changes: 2 additions & 7 deletions lib/main_development.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart';
import 'package:times_up_flutter/app/app.dart';
import 'package:times_up_flutter/app/config/android_config.dart';
import 'package:times_up_flutter/bootstrap.dart';
import 'package:times_up_flutter/firebase_options_dev.dart';
import 'package:times_up_flutter/services/app_info_service.dart';
Expand All @@ -18,12 +17,12 @@ import 'package:times_up_flutter/theme/theme_notifier.dart';

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final packageInfo = await PackageInfo.fromPlatform();
late final packageInfo;
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
).whenComplete(() async {
packageInfo = await PackageInfo.fromPlatform();
await _notificationServiceListener();
await _backgroundListener();
});

await bootstrap(
Expand Down Expand Up @@ -53,10 +52,6 @@ Future<void> main() async {
);
}

Future<void> _backgroundListener() async {
await configureBackgroundFetch();
}

Future<void> _notificationServiceListener() async {
final notificationService = NotificationService();
await notificationService.initialize().whenComplete(
Expand Down
3 changes: 2 additions & 1 deletion lib/main_production.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ import 'package:times_up_flutter/theme/theme_notifier.dart';

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
PackageInfo packageInfo = await PackageInfo.fromPlatform();
late final packageInfo;
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
).whenComplete(() async {
packageInfo = await PackageInfo.fromPlatform();
await _notificationServiceListener();
});

Expand Down
3 changes: 2 additions & 1 deletion lib/main_staging.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ import 'package:times_up_flutter/theme/theme_notifier.dart';

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
PackageInfo packageInfo = await PackageInfo.fromPlatform();
late final packageInfo;
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
).whenComplete(() async {
packageInfo = await PackageInfo.fromPlatform();
await _notificationServiceListener();
});

Expand Down
1 change: 1 addition & 0 deletions lib/services/api_path.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ class APIPath {
'users/$uid/notifications/';

static String mail() => 'mail/';

static String deviceToken() => 'DeviceTokens/';
}
23 changes: 14 additions & 9 deletions lib/services/database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import 'package:times_up_flutter/utils/constants.dart';
import 'package:times_up_flutter/widgets/show_logger.dart';

abstract class Database {
ChildModel? get currentChild;

Future<void> setChild(ChildModel model);

Future<void> updateChild(ChildModel model);
Expand Down Expand Up @@ -46,30 +48,32 @@ abstract class Database {
}

class FireStoreDatabase implements Database {
FireStoreDatabase({
required this.uid,
required this.auth,
}) {
factory FireStoreDatabase({required String uid, required AuthBase auth}) {
return _singleton ??= FireStoreDatabase._internal(uid, auth);
}

FireStoreDatabase._internal(this.uid, this.auth) {
if (auth.isFirstLogin) {
sendEmail(
email: EmailModel(
emailIds: [auth.currentUser!.email!],
subject: EmailConstants.subject,
text: EmailConstants.text,
html: EmailConstants.html(
auth.currentUser!.displayName ?? auth.currentUser!.email!,
),
auth.currentUser!.displayName ?? auth.currentUser!.email!),
),
).then((value) => auth.setFirstLogin(isFirstLogin: false));
}
}

static FireStoreDatabase? _singleton;
GeoLocatorService geo = GeoLocatorService();
final String uid;
final AuthBase auth;
ChildModel? _child;
final _service = FireStoreService.instance;

GeoLocatorService geo = GeoLocatorService();
@override
ChildModel? get currentChild => _child;

@override
Future<void> setChild(ChildModel model) => _service.setData(
Expand Down Expand Up @@ -150,7 +154,7 @@ class FireStoreDatabase implements Database {
) async {
await apps.getAppUsageService();

final point = await geo.getInitialLocation();
final point = await geo.getCurrentLocation.last;
final currentLocation = GeoPoint(point.latitude, point.longitude);

_child = ChildModel(
Expand All @@ -165,6 +169,7 @@ class FireStoreDatabase implements Database {
);

await updateChild(_child!);
JHLogger.$.e('Child Updated : $_child');
}

@override
Expand Down
1 change: 1 addition & 0 deletions lib/services/geo_locator_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class GeoLocatorService {

Future<Position> getInitialLocation() async {
permission = await Geolocator.requestPermission();

if (permission == LocationPermission.denied) {
return Future.error('Location permissions are denied');
}
Expand Down
89 changes: 73 additions & 16 deletions lib/services/notification_service.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,59 @@
// ignore_for_file: avoid_dynamic_calls

import 'dart:async';
import 'dart:ui';

import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_background_service/flutter_background_service.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:times_up_flutter/widgets/show_logger.dart';

const notificationChannelId = 'high_importance_channel';
const notificationChannelTitle = 'High Importance Notifications';
const notificationId = 800;

const AndroidNotificationChannel channel = AndroidNotificationChannel(
'high_importance_channel', // id
'High Importance Notifications', // title
notificationChannelId,
notificationChannelTitle,
description: 'This channel is used for important notifications.',
// description
importance: Importance.max,
);

@pragma('vm:entry-point')
Future<void> onStart(ServiceInstance service) async {
DartPluginRegistrant.ensureInitialized();
service.on('stopService').listen((event) {
service.stopSelf();
});
Timer.periodic(const Duration(minutes: 15), (timer) async {
// TODO(JORDY): IMPLEMENT DATA UPDATE HERE
// final databaseStore = Provider.of<Database>(context!, listen: false);
// final appUsage = Provider.of<AppUsageService>(context, listen: false);
// JHLogger.$.e(' NOT READy ');
// if (databaseStore.currentChild != null) {
// await databaseStore.liveUpdateChild(
// databaseStore.currentChild!,
// appUsage,
// );
// }
await NotificationService.flutterLocalNotificationsPlugin.show(
notificationId,
'Times Up - Monitoring',
'Tracking App Usage and live location',
const NotificationDetails(
android: AndroidNotificationDetails(
notificationChannelId,
notificationChannelTitle,
icon: 'parental_launch',
ongoing: true,
importance: Importance.max,
),
),
);
});
}

class NotificationService {
factory NotificationService() {
return _singleton;
Expand All @@ -22,21 +63,38 @@ class NotificationService {
static final NotificationService _singleton = NotificationService._internal();

// Here the set up for cloud Messaging Android is being configured
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
static final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();

// The LocalPlugin method configures the android channel
Future<void> localPlugin() async => await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);

Future<void> initialize() async {
await flutterLocalNotificationsPlugin.initialize(
const InitializationSettings(
android: AndroidInitializationSettings('@drawable/parental_launch'),
),
);
try {
final service = FlutterBackgroundService();
await flutterLocalNotificationsPlugin
.initialize(
const InitializationSettings(
android: AndroidInitializationSettings('@drawable/parental_launch'),
),
)
.then((value) async {
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);

await service.configure(
iosConfiguration: IosConfiguration(),
androidConfiguration: AndroidConfiguration(
onStart: onStart,
isForegroundMode: true,
initialNotificationTitle: 'Times Up Flutter Launched',
initialNotificationContent: 'The app is tracking metadata',
foregroundServiceNotificationId: notificationId,
),
);
});
} catch (e) {
JHLogger.$.e(e);
}
}

// Create a new instance of Firebase Messaging
Expand All @@ -46,7 +104,6 @@ class NotificationService {
final androidImplementation =
flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();

await androidImplementation?.requestPermission();
}

Expand Down
Loading

0 comments on commit 8840a96

Please sign in to comment.