feat: Add unified push as push provider

onboarding
Sorunome 5 years ago
parent c4cc6f4fe5
commit d5a2ee2f3f

@ -146,6 +146,45 @@ Example B:
3. For testing just run a regular build without extras 3. For testing just run a regular build without extras
# Android push notifications without FCM
Fluffychat has the ability to receive push notifications on android without FCM via the
[UnifiedPush](https://github.com/UnifiedPush) project, e.g. using gotify as push backend. As the project is still pretty new
there might still be some bugs, overall it seems to be working, though.
While UnifiedPush also supports p2p push via [NoProvider2Push](https://github.com/NoProvider2Push/android)
here the gotify setup will be outlined. Adapt re-write proxies accordingly, if you want to use a different push provider.
For self-hosted push with gotify you have to install and configure [gotify-server](https://github.com/gotify/server)
with [UnifiedPush](https://github.com/UnifiedPush/contrib/blob/main/providers/gotify.md) support.
Next, you add the `repo.unifiedpush.org` repository to fdroid and install the gotify client via it. Log into your gotify account and push notifications should work!
## Matrix-specific re-write proxy
Until [MSC2970](https://github.com/matrix-org/matrix-doc/pull/2970) is figured out we unfortunately
need another simple re-write proxy. By default the one at https://matrix.gateway.unifiedpush.org
is used, however you can easily self-host it. For that, add to your nginx config on the same domain you serve gotify the following:
```
resolver 8.8.8.8;
location /_matrix/push/v1/notify {
set $target '';
if ($request_method = GET ) {
return 200 '{"gateway":"matrix"}';
}
access_by_lua_block {
local cjson = require("cjson")
ngx.req.read_body()
local body = ngx.req.get_body_data()
local parsedBody = cjson.decode(body)
ngx.var.target = parsedBody["notification"]["devices"][1]["pushkey"]
ngx.req.set_body_data(body)
}
proxy_set_header Content-Type application/json;
proxy_set_header Host $host;
proxy_pass $target;
}
```
# Special thanks to # Special thanks to
* <a href="https://github.com/fabiyamada">Fabiyamada</a> is a graphics designer from Brasil and has made the fluffychat logo and the banner. Big thanks for her great designs. * <a href="https://github.com/fabiyamada">Fabiyamada</a> is a graphics designer from Brasil and has made the fluffychat logo and the banner. Big thanks for her great designs.

@ -6,7 +6,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.5.0' classpath 'com.android.tools.build:gradle:4.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.2' classpath 'com.google.gms:google-services:4.3.2'
} }

@ -1,6 +1,5 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip

@ -0,0 +1 @@
include ':app'

@ -340,10 +340,9 @@ class MatrixState extends State<Matrix> {
widget.apl.currentState.pushNamedAndRemoveAllOthers('/'); widget.apl.currentState.pushNamedAndRemoveAllOthers('/');
if (loginState == LoginState.logged) { if (loginState == LoginState.logged) {
FirebaseController.context = context; FirebaseController.context = context;
FirebaseController.setupFirebase( FirebaseController.matrix = this;
this, FirebaseController.setupFirebase(clientName)
clientName, .catchError(SentryController.captureException);
).catchError(SentryController.captureException);
} }
} }
}); });

@ -12,4 +12,7 @@ abstract class SettingKeys {
static const String showNoPid = 'chat.fluffy.show_no_pid'; static const String showNoPid = 'chat.fluffy.show_no_pid';
static const String databasePassword = 'database-password'; static const String databasePassword = 'database-password';
static const String appLockKey = 'chat.fluffy.app_lock'; static const String appLockKey = 'chat.fluffy.app_lock';
static const String unifiedPushRegistered =
'chat.fluffy.unifiedpush.registered';
static const String unifiedPushEndpoint = 'chat.fluffy.unifiedpush.endpoint';
} }

@ -92,6 +92,10 @@ class Store {
return await secureStorage.write(key: key, value: value); return await secureStorage.write(key: key, value: value);
} }
Future<void> setItemBool(String key, bool value) async {
await setItem(key, value.toString());
}
Future<void> deleteItem(String key) async { Future<void> deleteItem(String key) async {
if (!PlatformInfos.isMobile) { if (!PlatformInfos.isMobile) {
await _setupLocalStorage(); await _setupLocalStorage();

@ -13,6 +13,8 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_gen/gen_l10n/l10n_en.dart'; import 'package:flutter_gen/gen_l10n/l10n_en.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:unifiedpush/unifiedpush.dart';
import 'package:http/http.dart' as http;
import '../components/matrix.dart'; import '../components/matrix.dart';
import '../config/setting_keys.dart'; import '../config/setting_keys.dart';
@ -26,38 +28,114 @@ abstract class FirebaseController {
static BuildContext context; static BuildContext context;
static MatrixState matrix; static MatrixState matrix;
static Future<void> setupFirebase( static Future<void> setupFirebase(String clientName) async {
MatrixState matrix, String clientName) async {
FirebaseController.matrix = matrix;
if (!PlatformInfos.isMobile) return; if (!PlatformInfos.isMobile) return;
final client = matrix.client;
if (Platform.isIOS) iOS_Permission(); if (Platform.isIOS) iOS_Permission();
String token; Function goToRoom = (dynamic message) async {
try { try {
token = await _firebaseMessaging.getToken(); String roomId;
if (token?.isEmpty ?? true) { if (message is String && message[0] == '{') {
message = json.decode(message);
}
if (message is String) {
roomId = message;
} else if (message is Map) {
roomId = (message['data'] ??
message['notification'] ??
message)['room_id'];
}
if (roomId?.isEmpty ?? true) throw ('Bad roomId');
await matrix.widget.apl.currentState
.pushNamedAndRemoveUntilIsFirst('/rooms/${roomId}');
} catch (_) {
await FlushbarHelper.createError(message: 'Failed to open chat...')
.show(context);
rethrow;
}
};
// initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project
var initializationSettingsAndroid =
AndroidInitializationSettings('notifications_icon');
var initializationSettingsIOS =
IOSInitializationSettings(onDidReceiveLocalNotification: (i, a, b, c) {
return null;
});
var initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
await _flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: goToRoom);
// ignore: unawaited_futures
_flutterLocalNotificationsPlugin
.getNotificationAppLaunchDetails()
.then((details) {
if (details == null || !details.didNotificationLaunchApp) {
return;
}
goToRoom(details.payload);
});
if ((await UnifiedPush.getDistributors()).isNotEmpty) {
await setupUnifiedPush(clientName);
return;
}
String fcmToken;
try {
fcmToken = await _firebaseMessaging.getToken();
if (fcmToken?.isEmpty ?? true) {
throw '_firebaseMessaging.getToken() has not thrown an exception but returned no token'; throw '_firebaseMessaging.getToken() has not thrown an exception but returned no token';
} }
} catch (e, s) { } catch (e, s) {
Logs().w('Unable to get firebase token', e, s); Logs().w('Unable to get firebase token', e, s);
final storeItem = await matrix.store.getItem(SettingKeys.showNoGoogle); fcmToken = null;
final configOptionMissing = storeItem == null || storeItem.isEmpty; }
if (configOptionMissing || (!configOptionMissing && storeItem == '1')) { if (fcmToken?.isEmpty ?? true) {
// no google services warning
if (await matrix.store.getItemBool(SettingKeys.showNoGoogle, true)) {
await FlushbarHelper.createError( await FlushbarHelper.createError(
message: L10n.of(context).noGoogleServicesWarning, message: L10n.of(context).noGoogleServicesWarning,
duration: Duration(seconds: 15), duration: Duration(seconds: 15),
).show(context); ).show(context);
if (configOptionMissing) { if (null == await matrix.store.getItemBool(SettingKeys.showNoGoogle)) {
await matrix.store.setItem(SettingKeys.showNoGoogle, '0'); await matrix.store.setItemBool(SettingKeys.showNoGoogle, false);
} }
} }
return; return;
} }
await setupPusher(
clientName: clientName,
gatewayUrl: AppConfig.pushNotificationsGatewayUrl,
token: fcmToken,
);
_firebaseMessaging.configure(
onMessage: _onFcmMessage,
onBackgroundMessage: _onFcmMessage,
onResume: goToRoom,
onLaunch: goToRoom,
);
Logs().i('[Push] Firebase initialized');
}
static Future<void> setupPusher({
String clientName,
String gatewayUrl,
String token,
Set<String> oldTokens,
}) async {
oldTokens ??= <String>{};
final client = matrix.client;
final pushers = await client.requestPushers().catchError((e) { final pushers = await client.requestPushers().catchError((e) {
Logs().w('[Push] Unable to request pushers', e); Logs().w('[Push] Unable to request pushers', e);
return <Pusher>[]; return <Pusher>[];
}); });
var setNewPusher = false;
if (gatewayUrl != null && token != null && clientName != null) {
final currentPushers = pushers.where((pusher) => pusher.pushkey == token); final currentPushers = pushers.where((pusher) => pusher.pushkey == token);
if (currentPushers.length == 1 && if (currentPushers.length == 1 &&
currentPushers.first.kind == 'http' && currentPushers.first.kind == 'http' &&
@ -65,25 +143,32 @@ abstract class FirebaseController {
currentPushers.first.appDisplayName == clientName && currentPushers.first.appDisplayName == clientName &&
currentPushers.first.deviceDisplayName == client.deviceName && currentPushers.first.deviceDisplayName == client.deviceName &&
currentPushers.first.lang == 'en' && currentPushers.first.lang == 'en' &&
currentPushers.first.data.url.toString() == currentPushers.first.data.url.toString() == gatewayUrl &&
AppConfig.pushNotificationsGatewayUrl &&
currentPushers.first.data.format == currentPushers.first.data.format ==
AppConfig.pushNotificationsPusherFormat) { AppConfig.pushNotificationsPusherFormat) {
Logs().i('[Push] Pusher already set'); Logs().i('[Push] Pusher already set');
} else { } else {
if (currentPushers.isNotEmpty) { oldTokens.add(token);
for (final currentPusher in currentPushers) { setNewPusher = true;
currentPusher.pushkey = token; }
currentPusher.kind = null; }
for (final pusher in pushers) {
if (oldTokens.contains(pusher.pushkey)) {
pusher.kind = null;
try {
await client.setPusher( await client.setPusher(
currentPusher, pusher,
append: true, append: true,
); );
Logs().i('[Push] Remove legacy pusher for this device'); Logs().i('[Push] Removed legacy pusher for this device');
} catch (err) {
Logs().w('[Push] Failed to remove old pusher', err);
}
} }
} }
await client if (setNewPusher) {
.setPusher( try {
await client.setPusher(
Pusher( Pusher(
token, token,
AppConfig.pushNotificationsAppId, AppConfig.pushNotificationsAppId,
@ -91,75 +176,139 @@ abstract class FirebaseController {
client.deviceName, client.deviceName,
'en', 'en',
PusherData( PusherData(
url: Uri.parse(AppConfig.pushNotificationsGatewayUrl), url: Uri.parse(gatewayUrl),
format: AppConfig.pushNotificationsPusherFormat, format: AppConfig.pushNotificationsPusherFormat,
), ),
kind: 'http', kind: 'http',
), ),
append: false, append: false,
) );
.catchError((e, s) { } catch (e, s) {
Logs().e('[Push] Unable to set pushers', e, s); Logs().e('[Push] Unable to set pushers', e, s);
return []; }
}); }
} }
Function goToRoom = (dynamic message) async { static Future<void> onUnifiedPushMessage(String payload) async {
Map<String, dynamic> data;
try { try {
String roomId; data = Map<String, dynamic>.from(json.decode(payload)['notification']);
if (message is String) { await _onMessage(data);
roomId = message; } catch (e, s) {
} else if (message is Map) { Logs().e('[Push] Failed to display message', e, s);
roomId = (message['data'] ?? message)['room_id']; await _showDefaultNotification(data);
} }
if (roomId?.isEmpty ?? true) throw ('Bad roomId');
await matrix.widget.apl.currentState
.pushNamedAndRemoveUntilIsFirst('/rooms/${roomId}');
} catch (_) {
await FlushbarHelper.createError(message: 'Failed to open chat...')
.show(context);
rethrow;
} }
};
// initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project static Future<void> onRemoveEndpoint() async {
var initializationSettingsAndroid = Logs().i('[Push] Removing UnifiedPush endpoint...');
AndroidInitializationSettings('notifications_icon'); // we need our own store object as it is likely that we don't have a context here
var initializationSettingsIOS = final store = Store();
IOSInitializationSettings(onDidReceiveLocalNotification: (i, a, b, c) { final oldEndpoint = await store.getItem(SettingKeys.unifiedPushEndpoint);
return null; await store.setItemBool(SettingKeys.unifiedPushRegistered, false);
}); await store.deleteItem(SettingKeys.unifiedPushEndpoint);
var initializationSettings = InitializationSettings( if (matrix != null && (oldEndpoint?.isNotEmpty ?? false)) {
android: initializationSettingsAndroid, // remove the old pusher
iOS: initializationSettingsIOS, await setupPusher(
oldTokens: {oldEndpoint},
); );
await _flutterLocalNotificationsPlugin.initialize(initializationSettings, }
onSelectNotification: goToRoom); }
_firebaseMessaging.configure( static Future<void> onBackgroundNewEndpoint(String endpoint) async {
onMessage: _onMessage, // just remove the old endpoint. we'll deal with this when the app next starts up
onBackgroundMessage: _onMessage, await onRemoveEndpoint();
onResume: goToRoom, }
onLaunch: goToRoom,
); static Future<void> setupUnifiedPush(String clientName) async {
Logs().i('[Push] Firebase initialized'); final onNewEndpoint = (String newEndpoint) async {
if (newEndpoint?.isEmpty ?? true) {
await onRemoveEndpoint();
return; return;
} }
var endpoint =
'https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify';
try {
final url = Uri.parse(newEndpoint)
.replace(
path: '/_matrix/push/v1/notify',
query: '',
)
.toString()
.split('?')
.first;
final res = json.decode(utf8.decode((await http.get(url)).bodyBytes));
if (res['gateway'] == 'matrix') {
endpoint = url;
}
} catch (e) {
Logs().i('[Push] No self-hosted unified push gateway present: ' +
newEndpoint);
}
Logs().i('[Push] UnifiedPush using endpoint ' + endpoint);
final oldTokens = <String>{};
try {
final fcmToken = await _firebaseMessaging.getToken();
oldTokens.add(fcmToken);
} catch (_) {}
await setupPusher(
clientName: clientName,
gatewayUrl: endpoint,
token: newEndpoint,
oldTokens: oldTokens,
);
await matrix.store.setItem(SettingKeys.unifiedPushEndpoint, newEndpoint);
await matrix.store.setItemBool(SettingKeys.unifiedPushRegistered, true);
};
await UnifiedPush.initialize(
onNewEndpoint, // new endpoint
onRemoveEndpoint, // registration failed
onRemoveEndpoint, // registration removed
onRemoveEndpoint, // unregistered
onUnifiedPushMessage, // foreground message
onBackgroundNewEndpoint, // background new endpoint (be static)
onRemoveEndpoint, // background unregistered (be static)
onUnifiedPushMessage, // background push message (be static)
);
if (!(await matrix.store
.getItemBool(SettingKeys.unifiedPushRegistered, false))) {
Logs().i('[Push] UnifiedPush not registered, attempting to do so...');
await UnifiedPush.registerAppWithDialog();
} else {
// make sure the endpoint is up-to-date etc.
await onNewEndpoint(
await matrix.store.getItem(SettingKeys.unifiedPushEndpoint));
}
}
static Future<dynamic> _onMessage(Map<String, dynamic> message) async { static Future<dynamic> _onFcmMessage(Map<String, dynamic> message) async {
Map<String, dynamic> data;
try { try {
final data = message['data'] ?? message; data = Map<String, dynamic>.from(message['data'] ?? message);
await _onMessage(data);
} catch (e, s) {
Logs().e('[Push] Error while processing notification', e, s);
await _showDefaultNotification(data);
}
return null;
}
static Future<dynamic> _onMessage(Map<String, dynamic> data) async {
final String roomId = data['room_id']; final String roomId = data['room_id'];
final String eventId = data['event_id']; final String eventId = data['event_id'];
final int unread = json.decode(data['counts'])['unread']; final unread = ((data['counts'] is String
? json.decode(data.tryGet<String>('counts', '{}'))
: data.tryGet<Map<String, dynamic>>(
'counts', <String, dynamic>{})) as Map<String, dynamic>)
.tryGet<int>('unread');
if ((roomId?.isEmpty ?? true) || if ((roomId?.isEmpty ?? true) ||
(eventId?.isEmpty ?? true) || (eventId?.isEmpty ?? true) ||
unread == 0) { unread == 0) {
Logs().i('[Push] New clearing push');
await _flutterLocalNotificationsPlugin.cancelAll(); await _flutterLocalNotificationsPlugin.cancelAll();
return null; return null;
} }
if (context != null && matrix.activeRoomId == roomId) { if (matrix != null && matrix?.activeRoomId == roomId) {
Logs().i('[Push] New clearing push');
return null; return null;
} }
Logs().i('[Push] New message received'); Logs().i('[Push] New message received');
@ -168,13 +317,8 @@ abstract class FirebaseController {
final i18n = context == null ? L10nEn() : L10n.of(context); final i18n = context == null ? L10nEn() : L10n.of(context);
// Get the client // Get the client
Client client; var client = matrix?.client;
var tempClient = false; var tempClient = false;
try {
client = matrix.client;
} catch (_) {
client = null;
}
if (client == null) { if (client == null) {
tempClient = true; tempClient = true;
final platform = kIsWeb ? 'Web' : Platform.operatingSystem; final platform = kIsWeb ? 'Web' : Platform.operatingSystem;
@ -184,45 +328,51 @@ abstract class FirebaseController {
await client.onLoginStateChanged.stream await client.onLoginStateChanged.stream
.firstWhere((l) => l == LoginState.logged) .firstWhere((l) => l == LoginState.logged)
.timeout( .timeout(
Duration(seconds: 5), Duration(seconds: 20),
); );
} }
final roomFuture =
client.onRoomUpdate.stream.where((u) => u.id == roomId).first;
final eventFuture = client.onEvent.stream
.where((u) => u.content['event_id'] == eventId)
.first;
// Get the room // Get the room
var room = client.getRoomById(roomId); var room = client.getRoomById(roomId);
if (room == null) { if (room == null) {
Logs().i('[Push] Wait for the room'); Logs().i('[Push] Wait for the room');
await client.onRoomUpdate.stream await roomFuture.timeout(Duration(seconds: 5));
.where((u) => u.id == roomId)
.first
.timeout(Duration(seconds: 5));
Logs().i('[Push] Room found'); Logs().i('[Push] Room found');
room = client.getRoomById(roomId); room = client.getRoomById(roomId);
if (room == null) return null; if (room == null) return null;
} else {
// cancel the future
// ignore: unawaited_futures
roomFuture.timeout(Duration(seconds: 0)).catchError((_) => null);
} }
// Get the event // Get the event
var event = await client.database.getEventById(client.id, eventId, room); var event = await client.database.getEventById(client.id, eventId, room);
if (event == null) { if (event == null) {
Logs().i('[Push] Wait for the event'); Logs().i('[Push] Wait for the event');
final eventUpdate = await client.onEvent.stream final eventUpdate = await eventFuture.timeout(Duration(seconds: 5));
.where((u) => u.content['event_id'] == eventId)
.first
.timeout(Duration(seconds: 5));
Logs().i('[Push] Event found'); Logs().i('[Push] Event found');
event = Event.fromJson(eventUpdate.content, room); event = Event.fromJson(eventUpdate.content, room);
if (room == null) return null; if (room == null) return null;
} else {
// cancel the future
// ignore: unawaited_futures
eventFuture.timeout(Duration(seconds: 0)).catchError((_) => null);
} }
// Count all unread events // Count all unread events
var unreadEvents = 0; var unreadEvents = 0;
client.rooms client.rooms.forEach((Room room) => unreadEvents += room.notificationCount);
.forEach((Room room) => unreadEvents += room.notificationCount);
// Calculate title // Calculate title
final title = unread > 1 final title = unread > 1
? i18n.unreadMessagesInChats( ? i18n.unreadMessagesInChats(unreadEvents.toString(), unread.toString())
unreadEvents.toString(), unread.toString())
: i18n.unreadMessages(unreadEvents.toString()); : i18n.unreadMessages(unreadEvents.toString());
// Calculate the body // Calculate the body
@ -248,10 +398,7 @@ abstract class FirebaseController {
); );
// Show notification // Show notification
var androidPlatformChannelSpecifics = AndroidNotificationDetails( var androidPlatformChannelSpecifics = _getAndroidNotificationDetails(
AppConfig.pushNotificationsChannelId,
AppConfig.pushNotificationsChannelName,
AppConfig.pushNotificationsChannelDescription,
styleInformation: MessagingStyleInformation( styleInformation: MessagingStyleInformation(
person, person,
conversationTitle: title, conversationTitle: title,
@ -263,9 +410,8 @@ abstract class FirebaseController {
) )
], ],
), ),
importance: Importance.max, ticker: i18n.newMessageInFluffyChat,
priority: Priority.high, );
ticker: i18n.newMessageInFluffyChat);
var iOSPlatformChannelSpecifics = IOSNotificationDetails(); var iOSPlatformChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails( var platformChannelSpecifics = NotificationDetails(
android: androidPlatformChannelSpecifics, android: androidPlatformChannelSpecifics,
@ -283,15 +429,11 @@ abstract class FirebaseController {
client = null; client = null;
Logs().i('[Push] Temp client disposed'); Logs().i('[Push] Temp client disposed');
} }
} catch (e, s) {
Logs().e('[Push] Error while processing notification', e, s);
await _showDefaultNotification(message);
}
return null; return null;
} }
static Future<dynamic> _showDefaultNotification( static Future<dynamic> _showDefaultNotification(
Map<String, dynamic> message) async { Map<String, dynamic> data) async {
try { try {
var flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); var flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
// Init notifications framework // Init notifications framework
@ -308,26 +450,20 @@ abstract class FirebaseController {
// Locked on en for now // Locked on en for now
//final l10n = L10n(Platform.localeName); //final l10n = L10n(Platform.localeName);
final l10n = L10nEn(); final l10n = L10nEn();
// Notification data and matrix data
Map<dynamic, dynamic> data = message['data'] ?? message;
String eventID = data['event_id']; String eventID = data['event_id'];
String roomID = data['room_id']; String roomID = data['room_id'];
final int unread = data.containsKey('counts') final unread = ((data['counts'] is String
? json.decode(data['counts'])['unread'] ? json.decode(data.tryGet<String>('counts', '{}'))
: 1; : data.tryGet<Map<String, dynamic>>(
'counts', <String, dynamic>{})) as Map<String, dynamic>)
.tryGet<int>('unread', 1);
await flutterLocalNotificationsPlugin.cancelAll(); await flutterLocalNotificationsPlugin.cancelAll();
if (unread == 0 || roomID == null || eventID == null) { if (unread == 0 || roomID == null || eventID == null) {
return; return;
} }
// Display notification // Display notification
var androidPlatformChannelSpecifics = AndroidNotificationDetails( var androidPlatformChannelSpecifics = _getAndroidNotificationDetails();
AppConfig.pushNotificationsChannelId,
AppConfig.pushNotificationsChannelName,
AppConfig.pushNotificationsChannelDescription,
importance: Importance.max,
priority: Priority.high);
var iOSPlatformChannelSpecifics = IOSNotificationDetails(); var iOSPlatformChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails( var platformChannelSpecifics = NotificationDetails(
android: androidPlatformChannelSpecifics, android: androidPlatformChannelSpecifics,
@ -372,4 +508,21 @@ abstract class FirebaseController {
Logs().i('Settings registered: $settings'); Logs().i('Settings registered: $settings');
}); });
} }
static AndroidNotificationDetails _getAndroidNotificationDetails(
{MessagingStyleInformation styleInformation, String ticker}) {
final color =
context != null ? Theme.of(context).primaryColor : Color(0xFF5625BA);
return AndroidNotificationDetails(
AppConfig.pushNotificationsChannelId,
AppConfig.pushNotificationsChannelName,
AppConfig.pushNotificationsChannelDescription,
styleInformation: styleInformation,
importance: Importance.max,
priority: Priority.high,
ticker: ticker,
color: color,
);
}
} }

@ -910,10 +910,12 @@ packages:
receive_sharing_intent: receive_sharing_intent:
dependency: "direct main" dependency: "direct main"
description: description:
name: receive_sharing_intent path: "."
url: "https://pub.dartlang.org" ref: "107ea4ae3c3da15be4e6d3337623b69cc2e04c68"
source: hosted resolved-ref: "107ea4ae3c3da15be4e6d3337623b69cc2e04c68"
version: "1.4.3" url: "https://github.com/radvansky-tomas/receive_sharing_intent.git"
source: git
version: "1.4.2"
rxdart: rxdart:
dependency: transitive dependency: transitive
description: description:
@ -1150,6 +1152,15 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0-nullsafety.3" version: "1.3.0-nullsafety.3"
unifiedpush:
dependency: "direct main"
description:
path: "."
ref: main
resolved-ref: "7092bcc846f6f919c7dfb58f2c30bb45ca7231c0"
url: "https://github.com/UnifiedPush/flutter-connector.git"
source: git
version: "0.0.1"
universal_html: universal_html:
dependency: "direct main" dependency: "direct main"
description: description:

@ -15,6 +15,12 @@ dependencies:
url: https://gitlab.com/famedly/famedlysdk.git url: https://gitlab.com/famedly/famedlysdk.git
ref: main ref: main
unifiedpush:
# path: /home/sorunome/repos/gotify/flutter_unified_push
git:
url: https://github.com/UnifiedPush/flutter-connector.git
ref: main
localstorage: ^3.0.6+9 localstorage: ^3.0.6+9
file_picker_cross: 4.2.2 file_picker_cross: 4.2.2
image_picker: ^0.6.7+21 image_picker: ^0.6.7+21
@ -35,7 +41,11 @@ dependencies:
flutter_secure_storage: ^3.3.5 flutter_secure_storage: ^3.3.5
http: ^0.12.2 http: ^0.12.2
universal_html: ^1.2.4 universal_html: ^1.2.4
receive_sharing_intent: ^1.4.3 receive_sharing_intent:
# see https://github.com/KasemJaffer/receive_sharing_intent/pull/115
git:
url: https://github.com/radvansky-tomas/receive_sharing_intent.git
ref: 107ea4ae3c3da15be4e6d3337623b69cc2e04c68
flutter_slidable: ^0.5.7 flutter_slidable: ^0.5.7
flutter_sound_lite: ^7.5.3+1 flutter_sound_lite: ^7.5.3+1
open_file: ^3.0.3 open_file: ^3.0.3

Loading…
Cancel
Save