Experimental support for background notifications

This commit is contained in:
Christian Pauly 2020-05-05 12:51:38 +02:00
parent 481dfeaf75
commit 6f281bc0cd
7 changed files with 209 additions and 109 deletions

View File

@ -95,6 +95,7 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.google.firebase:firebase-messaging:20.1.6'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'

View File

@ -11,7 +11,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:name="io.flutter.app.FlutterApplication"
android:name=".Application"
android:label="FluffyChat"
android:icon="@mipmap/launcher_icon">
<activity

View File

@ -0,0 +1,31 @@
package chat.fluffy.fluffychat
import io.flutter.app.FlutterApplication
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
import io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin
import io.flutter.view.FlutterMain
import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService
import com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin
import com.tekartik.sqflite.SqflitePlugin
import com.it_nomads.fluttersecurestorage.FlutterSecureStoragePlugin
import io.flutter.plugins.pathprovider.PathProviderPlugin
class Application : FlutterApplication(), PluginRegistrantCallback {
override fun onCreate() {
super.onCreate()
FlutterFirebaseMessagingService.setPluginRegistrant(this);
FlutterMain.startInitialization(this)
}
override fun registerWith(registry: PluginRegistry?) {
if (!registry!!.hasPlugin("io.flutter.plugins.firebasemessaging")) {
FirebaseMessagingPlugin.registerWith(registry!!.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin"));
FlutterLocalNotificationsPlugin.registerWith(registry.registrarFor("com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin"));
SqflitePlugin.registerWith(registry.registrarFor("com.tekartik.sqflite.SqflitePlugin"));
PathProviderPlugin.registerWith(registry.registrarFor("io.flutter.plugins.pathprovider.PathProviderPlugin"));
FlutterSecureStoragePlugin.registerWith(registry.registrarFor("com.it_nomads.fluttersecurestorage"));
}
}
}

View File

@ -36,6 +36,7 @@ class I18n {
}
static I18n of(BuildContext context) {
if (context == null) return I18n('en');
return Localizations.of<I18n>(context, I18n);
}

View File

@ -14,6 +14,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:famedlysdk/famedlysdk.dart';
import '../utils/event_extension.dart';
import '../utils/room_extension.dart';
import 'famedlysdk_store.dart';
abstract class FirebaseController {
static FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
@ -40,7 +41,7 @@ abstract class FirebaseController {
await client.setPushers(
token,
"http",
"chat.fluffy.fluffychat",
"chat.fluffy.fluffychat.data",
clientName,
client.deviceName,
"en",
@ -83,115 +84,181 @@ abstract class FirebaseController {
onSelectNotification: goToRoom);
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
try {
print(message);
final data = message['data'] ?? message;
final String roomId = data["room_id"];
final String eventId = data["event_id"];
final int unread = json.decode(data["counts"])["unread"];
if ((roomId?.isEmpty ?? true) ||
(eventId?.isEmpty ?? true) ||
unread == 0) {
await _flutterLocalNotificationsPlugin.cancelAll();
return null;
}
if (Matrix.of(context).activeRoomId == roomId) return null;
// Get the room
Room room = client.getRoomById(roomId);
if (room == null) {
await client.onRoomUpdate.stream
.where((u) => u.id == roomId)
.first
.timeout(Duration(seconds: 10));
room = client.getRoomById(roomId);
if (room == null) return null;
}
// Get the event
Event event = await client.store.getEventById(eventId, room);
if (event == null) {
final EventUpdate eventUpdate = await client.onEvent.stream
.where((u) => u.content["event_id"] == eventId)
.first
.timeout(Duration(seconds: 10));
event = Event.fromJson(eventUpdate.content, room);
if (room == null) return null;
}
// Count all unread events
int unreadEvents = 0;
client.rooms
.forEach((Room room) => unreadEvents += room.notificationCount);
// Calculate title
final String title = unread > 1
? I18n.of(context).unreadMessagesInChats(
unreadEvents.toString(), unread.toString())
: I18n.of(context).unreadMessages(unreadEvents.toString());
// Calculate the body
final String body = event.getLocalizedBody(context,
withSenderNamePrefix: true, hideReply: true);
// The person object for the android message style notification
final person = Person(
name: room.getLocalizedDisplayname(context),
icon: room.avatar == null
? null
: await downloadAndSaveAvatar(
room.avatar,
client,
width: 126,
height: 126,
),
iconSource: IconSource.FilePath,
);
// Show notification
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
'fluffychat_push',
'FluffyChat push channel',
'Push notifications for FluffyChat',
style: AndroidNotificationStyle.Messaging,
styleInformation: MessagingStyleInformation(
person,
conversationTitle: title,
messages: [
Message(
body,
event.time,
person,
)
],
),
importance: Importance.Max,
priority: Priority.High,
ticker: I18n.of(context).newMessageInFluffyChat);
var iOSPlatformChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await _flutterLocalNotificationsPlugin.show(
0,
room.getLocalizedDisplayname(context),
body,
platformChannelSpecifics,
payload: roomId);
} catch (exception) {
debugPrint("[Push] Error while processing notification: " +
exception.toString());
}
return null;
},
onMessage: _onMessage,
onBackgroundMessage: _onMessage,
onResume: goToRoom,
// Currently fires unexpectetly... https://github.com/FirebaseExtended/flutterfire/issues/1060
//onLaunch: goToRoom,
onLaunch: goToRoom,
);
debugPrint("[Push] Firebase initialized");
return;
}
static Future<dynamic> _onMessage(Map<String, dynamic> message) async {
try {
final data = message['data'] ?? message;
final String roomId = data["room_id"];
final String eventId = data["event_id"];
final int unread = json.decode(data["counts"])["unread"];
if ((roomId?.isEmpty ?? true) ||
(eventId?.isEmpty ?? true) ||
unread == 0) {
await _flutterLocalNotificationsPlugin.cancelAll();
return null;
}
if (context != null && Matrix.of(context).activeRoomId == roomId) {
return null;
}
// Get the client
print("Get client");
Client client;
if (context != null) {
client = Matrix.of(context).client;
} else {
final platform = kIsWeb ? "Web" : Platform.operatingSystem;
final clientName = "FluffyChat $platform";
print("Clientname: $clientName");
client = Client(clientName, debug: false);
client.storeAPI = ExtendedStore(client);
await client.onLoginStateChanged.stream
.firstWhere((l) => l == LoginState.logged)
.timeout(
Duration(seconds: 2),
);
}
// Get the room
print("Get room");
Room room = client.getRoomById(roomId);
if (room == null) {
await client.onRoomUpdate.stream
.where((u) => u.id == roomId)
.first
.timeout(Duration(seconds: 10));
room = client.getRoomById(roomId);
if (room == null) return null;
}
// Get the event
print("Get event");
Event event = await client.store.getEventById(eventId, room);
if (event == null) {
final EventUpdate eventUpdate = await client.onEvent.stream
.where((u) => u.content["event_id"] == eventId)
.first
.timeout(Duration(seconds: 10));
event = Event.fromJson(eventUpdate.content, room);
if (room == null) return null;
}
// Count all unread events
int unreadEvents = 0;
client.rooms
.forEach((Room room) => unreadEvents += room.notificationCount);
// Calculate title
final String title = unread > 1
? I18n.of(context)
.unreadMessagesInChats(unreadEvents.toString(), unread.toString())
: I18n.of(context).unreadMessages(unreadEvents.toString());
// Calculate the body
final String body = event.getLocalizedBody(context,
withSenderNamePrefix: true, hideReply: true);
// The person object for the android message style notification
final person = Person(
name: room.getLocalizedDisplayname(context),
icon: room.avatar == null
? null
: await downloadAndSaveAvatar(
room.avatar,
client,
width: 126,
height: 126,
),
iconSource: IconSource.FilePath,
);
// Show notification
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
'fluffychat_push',
'FluffyChat push channel',
'Push notifications for FluffyChat',
style: AndroidNotificationStyle.Messaging,
styleInformation: MessagingStyleInformation(
person,
conversationTitle: title,
messages: [
Message(
body,
event.time,
person,
)
],
),
importance: Importance.Max,
priority: Priority.High,
ticker: I18n.of(context).newMessageInFluffyChat);
var iOSPlatformChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await _flutterLocalNotificationsPlugin.show(0,
room.getLocalizedDisplayname(context), body, platformChannelSpecifics,
payload: roomId);
} catch (exception) {
debugPrint("[Push] Error while processing notification: " +
exception.toString());
}
return null;
}
static Future<dynamic> _handleOnBackgroundMessage(
Map<String, dynamic> message) async {
try {
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
// Init notifications framework
var initializationSettingsAndroid =
AndroidInitializationSettings('notifications_icon');
var initializationSettingsIOS = IOSInitializationSettings();
var initializationSettings = InitializationSettings(
initializationSettingsAndroid, initializationSettingsIOS);
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
// Notification data and matrix data
Map<dynamic, dynamic> data = message['data'] ?? message;
String eventID = data["event_id"];
String roomID = data["room_id"];
final int unread = data.containsKey("counts")
? json.decode(data["counts"])["unread"]
: 1;
await flutterLocalNotificationsPlugin.cancelAll();
if (unread == 0 || roomID == null || eventID == null) {
return;
}
// Display notification
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
'fluffychat_push',
'FluffyChat push channel',
'Push notifications for FluffyChat',
importance: Importance.Max,
priority: Priority.High);
var iOSPlatformChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
final String title = "$unread ungelesene Unterhaltungen";
await flutterLocalNotificationsPlugin.show(1, title,
'App öffnen, um Nachricht zu entschlüsseln', platformChannelSpecifics,
payload: roomID);
} catch (exception) {
debugPrint("[Push] Error while processing background notification: " +
exception.toString());
}
return Future<void>.value();
}
static Future<String> downloadAndSaveAvatar(Uri content, Client client,
{int width, int height}) async {
final bool thumbnail = width == null && height == null ? false : true;

View File

@ -142,7 +142,7 @@ packages:
name: firebase_messaging
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.9"
version: "6.0.13"
flutter:
dependency: "direct main"
description: flutter
@ -168,7 +168,7 @@ packages:
name: flutter_local_notifications
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.1+2"
version: "0.8.4+3"
flutter_localizations:
dependency: "direct main"
description: flutter

View File

@ -38,8 +38,8 @@ dependencies:
url_launcher_web: ^0.1.0
sqflite: ^1.2.0
flutter_advanced_networkimage: any
firebase_messaging: ^6.0.9
flutter_local_notifications: ^0.9.1+2
firebase_messaging: ^6.0.13
flutter_local_notifications: ^0.8.4
link_text: ^0.1.1
path_provider: ^1.5.1
webview_flutter: ^0.3.19+9