Merge branch 'soru/unified-push' into 'main'

feat: Add unified push as push provider

See merge request famedly/fluffychat!341
This commit is contained in:
Krille Fear 2021-01-28 14:32:07 +00:00
commit fd08a6a4db
11 changed files with 458 additions and 239 deletions

View File

@ -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.

View File

@ -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'
} }

View File

@ -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

View File

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

View File

@ -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);
} }
} }
}); });

View File

@ -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';
} }

View File

@ -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();

View File

@ -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,91 +28,22 @@ 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;
try {
token = await _firebaseMessaging.getToken();
if (token?.isEmpty ?? true) {
throw '_firebaseMessaging.getToken() has not thrown an exception but returned no token';
}
} catch (e, s) {
Logs().w('Unable to get firebase token', e, s);
final storeItem = await matrix.store.getItem(SettingKeys.showNoGoogle);
final configOptionMissing = storeItem == null || storeItem.isEmpty;
if (configOptionMissing || (!configOptionMissing && storeItem == '1')) {
await FlushbarHelper.createError(
message: L10n.of(context).noGoogleServicesWarning,
duration: Duration(seconds: 15),
).show(context);
if (configOptionMissing) {
await matrix.store.setItem(SettingKeys.showNoGoogle, '0');
}
}
return;
}
final pushers = await client.requestPushers().catchError((e) {
Logs().w('[Push] Unable to request pushers', e);
return <Pusher>[];
});
final currentPushers = pushers.where((pusher) => pusher.pushkey == token);
if (currentPushers.length == 1 &&
currentPushers.first.kind == 'http' &&
currentPushers.first.appId == AppConfig.pushNotificationsAppId &&
currentPushers.first.appDisplayName == clientName &&
currentPushers.first.deviceDisplayName == client.deviceName &&
currentPushers.first.lang == 'en' &&
currentPushers.first.data.url.toString() ==
AppConfig.pushNotificationsGatewayUrl &&
currentPushers.first.data.format ==
AppConfig.pushNotificationsPusherFormat) {
Logs().i('[Push] Pusher already set');
} else {
if (currentPushers.isNotEmpty) {
for (final currentPusher in currentPushers) {
currentPusher.pushkey = token;
currentPusher.kind = null;
await client.setPusher(
currentPusher,
append: true,
);
Logs().i('[Push] Remove legacy pusher for this device');
}
}
await client
.setPusher(
Pusher(
token,
AppConfig.pushNotificationsAppId,
clientName,
client.deviceName,
'en',
PusherData(
url: Uri.parse(AppConfig.pushNotificationsGatewayUrl),
format: AppConfig.pushNotificationsPusherFormat,
),
kind: 'http',
),
append: false,
)
.catchError((e, s) {
Logs().e('[Push] Unable to set pushers', e, s);
return [];
});
}
Function goToRoom = (dynamic message) async { Function goToRoom = (dynamic message) async {
try { try {
String roomId; String roomId;
if (message is String && message[0] == '{') {
message = json.decode(message);
}
if (message is String) { if (message is String) {
roomId = message; roomId = message;
} else if (message is Map) { } else if (message is Map) {
roomId = (message['data'] ?? message)['room_id']; roomId = (message['data'] ??
message['notification'] ??
message)['room_id'];
} }
if (roomId?.isEmpty ?? true) throw ('Bad roomId'); if (roomId?.isEmpty ?? true) throw ('Bad roomId');
await matrix.widget.apl.currentState await matrix.widget.apl.currentState
@ -136,30 +69,246 @@ abstract class FirebaseController {
await _flutterLocalNotificationsPlugin.initialize(initializationSettings, await _flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: goToRoom); 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';
}
} catch (e, s) {
Logs().w('Unable to get firebase token', e, s);
fcmToken = null;
}
if (fcmToken?.isEmpty ?? true) {
// no google services warning
if (await matrix.store.getItemBool(SettingKeys.showNoGoogle, true)) {
await FlushbarHelper.createError(
message: L10n.of(context).noGoogleServicesWarning,
duration: Duration(seconds: 15),
).show(context);
if (null == await matrix.store.getItemBool(SettingKeys.showNoGoogle)) {
await matrix.store.setItemBool(SettingKeys.showNoGoogle, false);
}
}
return;
}
await setupPusher(
clientName: clientName,
gatewayUrl: AppConfig.pushNotificationsGatewayUrl,
token: fcmToken,
);
_firebaseMessaging.configure( _firebaseMessaging.configure(
onMessage: _onMessage, onMessage: _onFcmMessage,
onBackgroundMessage: _onMessage, onBackgroundMessage: _onFcmMessage,
onResume: goToRoom, onResume: goToRoom,
onLaunch: goToRoom, onLaunch: goToRoom,
); );
Logs().i('[Push] Firebase initialized'); Logs().i('[Push] Firebase initialized');
return;
} }
static Future<dynamic> _onMessage(Map<String, dynamic> message) async { 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) {
Logs().w('[Push] Unable to request pushers', e);
return <Pusher>[];
});
var setNewPusher = false;
if (gatewayUrl != null && token != null && clientName != null) {
final currentPushers = pushers.where((pusher) => pusher.pushkey == token);
if (currentPushers.length == 1 &&
currentPushers.first.kind == 'http' &&
currentPushers.first.appId == AppConfig.pushNotificationsAppId &&
currentPushers.first.appDisplayName == clientName &&
currentPushers.first.deviceDisplayName == client.deviceName &&
currentPushers.first.lang == 'en' &&
currentPushers.first.data.url.toString() == gatewayUrl &&
currentPushers.first.data.format ==
AppConfig.pushNotificationsPusherFormat) {
Logs().i('[Push] Pusher already set');
} else {
oldTokens.add(token);
setNewPusher = true;
}
}
for (final pusher in pushers) {
if (oldTokens.contains(pusher.pushkey)) {
pusher.kind = null;
try { try {
final data = message['data'] ?? message; await client.setPusher(
pusher,
append: true,
);
Logs().i('[Push] Removed legacy pusher for this device');
} catch (err) {
Logs().w('[Push] Failed to remove old pusher', err);
}
}
}
if (setNewPusher) {
try {
await client.setPusher(
Pusher(
token,
AppConfig.pushNotificationsAppId,
clientName,
client.deviceName,
'en',
PusherData(
url: Uri.parse(gatewayUrl),
format: AppConfig.pushNotificationsPusherFormat,
),
kind: 'http',
),
append: false,
);
} catch (e, s) {
Logs().e('[Push] Unable to set pushers', e, s);
}
}
}
static Future<void> onUnifiedPushMessage(String payload) async {
Map<String, dynamic> data;
try {
data = Map<String, dynamic>.from(json.decode(payload)['notification']);
await _onMessage(data);
} catch (e, s) {
Logs().e('[Push] Failed to display message', e, s);
await _showDefaultNotification(data);
}
}
static Future<void> onRemoveEndpoint() async {
Logs().i('[Push] Removing UnifiedPush endpoint...');
// we need our own store object as it is likely that we don't have a context here
final store = Store();
final oldEndpoint = await store.getItem(SettingKeys.unifiedPushEndpoint);
await store.setItemBool(SettingKeys.unifiedPushRegistered, false);
await store.deleteItem(SettingKeys.unifiedPushEndpoint);
if (matrix != null && (oldEndpoint?.isNotEmpty ?? false)) {
// remove the old pusher
await setupPusher(
oldTokens: {oldEndpoint},
);
}
}
static Future<void> onBackgroundNewEndpoint(String endpoint) async {
// just remove the old endpoint. we'll deal with this when the app next starts up
await onRemoveEndpoint();
}
static Future<void> setupUnifiedPush(String clientName) async {
final onNewEndpoint = (String newEndpoint) async {
if (newEndpoint?.isEmpty ?? true) {
await onRemoveEndpoint();
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> _onFcmMessage(Map<String, dynamic> message) async {
Map<String, dynamic> data;
try {
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,
);
}
} }

View File

@ -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:

View File

@ -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