mirror of
https://gitlab.com/famedly/fluffychat.git
synced 2024-12-25 15:02:33 +01:00
refactor: New push
This commit is contained in:
parent
cafd9ccaa6
commit
6fe94d5910
@ -2779,5 +2779,50 @@
|
|||||||
"widgetName": "Name",
|
"widgetName": "Name",
|
||||||
"widgetUrlError": "This is not a valid URL.",
|
"widgetUrlError": "This is not a valid URL.",
|
||||||
"widgetNameError": "Please provide a display name.",
|
"widgetNameError": "Please provide a display name.",
|
||||||
"errorAddingWidget": "Error adding the widget."
|
"errorAddingWidget": "Error adding the widget.",
|
||||||
|
"youRejectedTheInvitation": "You rejected the invitation",
|
||||||
|
"youJoinedTheChat": "You joined the chat",
|
||||||
|
"youAcceptedTheInvitation": "You accepted the invitation",
|
||||||
|
"youBannedUser": "You banned {user}",
|
||||||
|
"@youBannedUser": {
|
||||||
|
"placeholders": {
|
||||||
|
"user": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"youHaveWithdrawnTheInvitationFor": "You have withdrawn the invitation for {user}",
|
||||||
|
"@youHaveWithdrawnTheInvitationFor": {
|
||||||
|
"placeholders": {
|
||||||
|
"user": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"youInvitedBy": "You have been invited by {user}",
|
||||||
|
"@youInvitedBy": {
|
||||||
|
"placeholders": {
|
||||||
|
"user": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"youInvitedUser": "You invited {user}",
|
||||||
|
"@youInvitedUser": {
|
||||||
|
"placeholders": {
|
||||||
|
"user": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"youKicked": "You kicked {user}",
|
||||||
|
"@youKicked": {
|
||||||
|
"placeholders": {
|
||||||
|
"user": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"youKickedAndBanned": "You kicked and banned {user}",
|
||||||
|
"@youKickedAndBanned": {
|
||||||
|
"placeholders": {
|
||||||
|
"user": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"youUnbannedUser": "You unbanned {user}",
|
||||||
|
"@youUnbannedUser": {
|
||||||
|
"placeholders": {
|
||||||
|
"user": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ import 'dart:ui';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
@ -34,10 +33,10 @@ import 'package:unifiedpush/unifiedpush.dart';
|
|||||||
import 'package:vrouter/vrouter.dart';
|
import 'package:vrouter/vrouter.dart';
|
||||||
|
|
||||||
import 'package:fluffychat/utils/matrix_sdk_extensions.dart/client_stories_extension.dart';
|
import 'package:fluffychat/utils/matrix_sdk_extensions.dart/client_stories_extension.dart';
|
||||||
|
import 'package:fluffychat/utils/push_helper.dart';
|
||||||
import '../config/app_config.dart';
|
import '../config/app_config.dart';
|
||||||
import '../config/setting_keys.dart';
|
import '../config/setting_keys.dart';
|
||||||
import 'famedlysdk_store.dart';
|
import 'famedlysdk_store.dart';
|
||||||
import 'matrix_sdk_extensions.dart/matrix_locals.dart';
|
|
||||||
import 'platform_infos.dart';
|
import 'platform_infos.dart';
|
||||||
|
|
||||||
//import 'package:fcm_shared_isolate/fcm_shared_isolate.dart';
|
//import 'package:fcm_shared_isolate/fcm_shared_isolate.dart';
|
||||||
@ -66,6 +65,8 @@ class BackgroundPush {
|
|||||||
|
|
||||||
final pendingTests = <String, Completer<void>>{};
|
final pendingTests = <String, Completer<void>>{};
|
||||||
|
|
||||||
|
final dynamic firebase = null; //FcmSharedIsolate();
|
||||||
|
|
||||||
DateTime? lastReceivedPush;
|
DateTime? lastReceivedPush;
|
||||||
|
|
||||||
bool upAction = false;
|
bool upAction = false;
|
||||||
@ -76,8 +77,15 @@ class BackgroundPush {
|
|||||||
onRoomSync ??= client.onSync.stream
|
onRoomSync ??= client.onSync.stream
|
||||||
.where((s) => s.hasRoomUpdate)
|
.where((s) => s.hasRoomUpdate)
|
||||||
.listen((s) => _onClearingPush(getFromServer: false));
|
.listen((s) => _onClearingPush(getFromServer: false));
|
||||||
_fcmSharedIsolate?.setListeners(
|
firebase?.setListeners(
|
||||||
onMessage: _onFcmMessage,
|
onMessage: (message) => pushHelper(
|
||||||
|
PushNotification.fromJson(
|
||||||
|
Map<String, dynamic>.from(message['data'] ?? message)),
|
||||||
|
client: client,
|
||||||
|
l10n: l10n,
|
||||||
|
activeRoomId: router?.currentState?.pathParameters['roomid'],
|
||||||
|
onSelectNotification: goToRoom,
|
||||||
|
),
|
||||||
onNewToken: _newFcmToken,
|
onNewToken: _newFcmToken,
|
||||||
);
|
);
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
@ -112,16 +120,14 @@ class BackgroundPush {
|
|||||||
|
|
||||||
void handleLoginStateChanged(_) => setupPush();
|
void handleLoginStateChanged(_) => setupPush();
|
||||||
|
|
||||||
|
StreamSubscription<LoginState>? onLogin;
|
||||||
|
StreamSubscription<SyncUpdate>? onRoomSync;
|
||||||
|
|
||||||
void _newFcmToken(String token) {
|
void _newFcmToken(String token) {
|
||||||
_fcmToken = token;
|
_fcmToken = token;
|
||||||
setupPush();
|
setupPush();
|
||||||
}
|
}
|
||||||
|
|
||||||
final dynamic _fcmSharedIsolate = null; //FcmSharedIsolate();
|
|
||||||
|
|
||||||
StreamSubscription<LoginState>? onLogin;
|
|
||||||
StreamSubscription<SyncUpdate>? onRoomSync;
|
|
||||||
|
|
||||||
Future<void> setupPusher({
|
Future<void> setupPusher({
|
||||||
String? gatewayUrl,
|
String? gatewayUrl,
|
||||||
String? token,
|
String? token,
|
||||||
@ -129,7 +135,7 @@ class BackgroundPush {
|
|||||||
bool useDeviceSpecificAppId = false,
|
bool useDeviceSpecificAppId = false,
|
||||||
}) async {
|
}) async {
|
||||||
if (PlatformInfos.isIOS) {
|
if (PlatformInfos.isIOS) {
|
||||||
await _fcmSharedIsolate?.requestPermission();
|
await firebase?.requestPermission();
|
||||||
}
|
}
|
||||||
final clientName = PlatformInfos.clientName;
|
final clientName = PlatformInfos.clientName;
|
||||||
oldTokens ??= <String>{};
|
oldTokens ??= <String>{};
|
||||||
@ -213,7 +219,6 @@ class BackgroundPush {
|
|||||||
|
|
||||||
Future<void> setupPush() async {
|
Future<void> setupPush() async {
|
||||||
Logs().d("SetupPush");
|
Logs().d("SetupPush");
|
||||||
await setupLocalNotificationsPlugin();
|
|
||||||
if (client.loginState != LoginState.loggedIn ||
|
if (client.loginState != LoginState.loggedIn ||
|
||||||
!PlatformInfos.isMobile ||
|
!PlatformInfos.isMobile ||
|
||||||
context == null) {
|
context == null) {
|
||||||
@ -272,7 +277,7 @@ class BackgroundPush {
|
|||||||
Logs().v('Setup firebase');
|
Logs().v('Setup firebase');
|
||||||
if (_fcmToken?.isEmpty ?? true) {
|
if (_fcmToken?.isEmpty ?? true) {
|
||||||
try {
|
try {
|
||||||
_fcmToken = await _fcmSharedIsolate?.getToken();
|
_fcmToken = await firebase?.getToken();
|
||||||
if (_fcmToken == null) throw ('PushToken is null');
|
if (_fcmToken == null) throw ('PushToken is null');
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs().w('[Push] cannot get token', e, e is String ? null : s);
|
Logs().w('[Push] cannot get token', e, e is String ? null : s);
|
||||||
@ -306,43 +311,10 @@ class BackgroundPush {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _notificationsPluginSetUp = false;
|
|
||||||
Future<void> setupLocalNotificationsPlugin() async {
|
|
||||||
if (_notificationsPluginSetUp) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project
|
|
||||||
const initializationSettingsAndroid =
|
|
||||||
AndroidInitializationSettings('notifications_icon');
|
|
||||||
final initializationSettingsIOS = IOSInitializationSettings(
|
|
||||||
onDidReceiveLocalNotification: (i, a, b, c) async => null,
|
|
||||||
);
|
|
||||||
final initializationSettings = InitializationSettings(
|
|
||||||
android: initializationSettingsAndroid,
|
|
||||||
iOS: initializationSettingsIOS,
|
|
||||||
);
|
|
||||||
await _flutterLocalNotificationsPlugin.initialize(initializationSettings,
|
|
||||||
onSelectNotification: goToRoom);
|
|
||||||
|
|
||||||
_notificationsPluginSetUp = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setupUp() async {
|
Future<void> setupUp() async {
|
||||||
await UnifiedPush.registerAppWithDialog(context!);
|
await UnifiedPush.registerAppWithDialog(context!);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onFcmMessage(Map<dynamic, dynamic> message) async {
|
|
||||||
Logs().v('[Push] Foreground message received');
|
|
||||||
final data = Map<String, dynamic>.from(message['data'] ?? message);
|
|
||||||
try {
|
|
||||||
await _onMessage(data);
|
|
||||||
} catch (e, s) {
|
|
||||||
Logs().e('[Push] Error while processing notification', e, s);
|
|
||||||
await _showDefaultNotification(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _newUpEndpoint(String newEndpoint, String i) async {
|
Future<void> _newUpEndpoint(String newEndpoint, String i) async {
|
||||||
upAction = true;
|
upAction = true;
|
||||||
if (newEndpoint.isEmpty) {
|
if (newEndpoint.isEmpty) {
|
||||||
@ -374,7 +346,7 @@ class BackgroundPush {
|
|||||||
Logs().i('[Push] UnifiedPush using endpoint ' + endpoint);
|
Logs().i('[Push] UnifiedPush using endpoint ' + endpoint);
|
||||||
final oldTokens = <String?>{};
|
final oldTokens = <String?>{};
|
||||||
try {
|
try {
|
||||||
final fcmToken = await _fcmSharedIsolate?.getToken();
|
final fcmToken = await firebase?.getToken();
|
||||||
oldTokens.add(fcmToken);
|
oldTokens.add(fcmToken);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
await setupPusher(
|
await setupPusher(
|
||||||
@ -405,80 +377,12 @@ class BackgroundPush {
|
|||||||
upAction = true;
|
upAction = true;
|
||||||
final data = Map<String, dynamic>.from(
|
final data = Map<String, dynamic>.from(
|
||||||
json.decode(utf8.decode(message))['notification']);
|
json.decode(utf8.decode(message))['notification']);
|
||||||
try {
|
await pushHelper(
|
||||||
await _onMessage(data);
|
PushNotification.fromJson(data),
|
||||||
} catch (e, s) {
|
client: client,
|
||||||
Logs().e('[Push] Error while processing notification', e, s);
|
l10n: l10n,
|
||||||
await _showDefaultNotification(data);
|
activeRoomId: router?.currentState?.pathParameters['roomid'],
|
||||||
}
|
);
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onMessage(Map<String, dynamic> data) async {
|
|
||||||
Logs().v('[Push] _onMessage');
|
|
||||||
lastReceivedPush = DateTime.now();
|
|
||||||
final roomId = data['room_id'];
|
|
||||||
final eventId = data['event_id'];
|
|
||||||
if (roomId == 'test') {
|
|
||||||
Logs().v('[Push] Test $eventId was successful!');
|
|
||||||
pendingTests.remove(eventId)?.complete();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For legacy reasons the counts map could be a String encoded JSON:
|
|
||||||
final countsMap =
|
|
||||||
data.tryGetMap<String, dynamic>('counts', TryGet.silent) ??
|
|
||||||
(jsonDecode(data.tryGet<String>('counts') ?? '{}')
|
|
||||||
as Map<String, dynamic>);
|
|
||||||
final unread = countsMap.tryGet<int>('unread');
|
|
||||||
|
|
||||||
if ((roomId?.isEmpty ?? true) ||
|
|
||||||
(eventId?.isEmpty ?? true) ||
|
|
||||||
unread == 0) {
|
|
||||||
await _onClearingPush();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var giveUp = false;
|
|
||||||
var loaded = false;
|
|
||||||
final stopwatch = Stopwatch();
|
|
||||||
stopwatch.start();
|
|
||||||
final syncSubscription = client.onSync.stream.listen((r) {
|
|
||||||
if (stopwatch.elapsed.inSeconds >= 30) {
|
|
||||||
giveUp = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
final eventSubscription = client.onEvent.stream.listen((e) {
|
|
||||||
if (e.content['event_id'] == eventId) {
|
|
||||||
loaded = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
if (!(await eventExists(roomId, eventId)) && !loaded) {
|
|
||||||
do {
|
|
||||||
Logs().v('[Push] getting ' + roomId + ', event ' + eventId);
|
|
||||||
await client
|
|
||||||
.oneShotSync()
|
|
||||||
.catchError((e) => Logs().v('[Push] Error one-shot syncing', e));
|
|
||||||
if (stopwatch.elapsed.inSeconds >= 60) {
|
|
||||||
giveUp = true;
|
|
||||||
}
|
|
||||||
} while (!loaded && !giveUp);
|
|
||||||
}
|
|
||||||
Logs().v('[Push] ' +
|
|
||||||
(giveUp ? 'gave up on ' : 'got ') +
|
|
||||||
roomId +
|
|
||||||
', event ' +
|
|
||||||
eventId);
|
|
||||||
} finally {
|
|
||||||
await syncSubscription.cancel();
|
|
||||||
await eventSubscription.cancel();
|
|
||||||
}
|
|
||||||
await _showNotification(roomId, eventId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> eventExists(String roomId, String? eventId) async {
|
|
||||||
final room = client.getRoomById(roomId);
|
|
||||||
if (room == null) return false;
|
|
||||||
return (await client.database!.getEventById(eventId!, room)) != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Workaround for the problem that local notification IDs must be int but we
|
/// Workaround for the problem that local notification IDs must be int but we
|
||||||
@ -584,139 +488,4 @@ class BackgroundPush {
|
|||||||
_clearingPushLock = false;
|
_clearingPushLock = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showNotification(String roomId, String eventId) async {
|
|
||||||
await setupLocalNotificationsPlugin();
|
|
||||||
final room = client.getRoomById(roomId);
|
|
||||||
if (room == null) {
|
|
||||||
throw 'Room not found';
|
|
||||||
}
|
|
||||||
await room.postLoad();
|
|
||||||
final event = await client.database!.getEventById(eventId, room);
|
|
||||||
|
|
||||||
final activeRoomId = router!.currentState!.pathParameters['roomid'];
|
|
||||||
|
|
||||||
if (((activeRoomId?.isNotEmpty ?? false) &&
|
|
||||||
activeRoomId == room.id &&
|
|
||||||
client.syncPresence == null) ||
|
|
||||||
(event != null && (room.notificationCount == 0))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// load the locale
|
|
||||||
await loadLocale();
|
|
||||||
|
|
||||||
// Calculate title
|
|
||||||
final title = l10n!.unreadMessages(room.notificationCount);
|
|
||||||
|
|
||||||
// Calculate the body
|
|
||||||
final body = event!.getLocalizedBody(
|
|
||||||
MatrixLocals(L10n.of(context!)!),
|
|
||||||
withSenderNamePrefix: !room.isDirectChat,
|
|
||||||
plaintextBody: true,
|
|
||||||
hideReply: true,
|
|
||||||
hideEdit: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// The person object for the android message style notification
|
|
||||||
final avatar = room.avatar == null
|
|
||||||
? null
|
|
||||||
: await DefaultCacheManager().getSingleFile(
|
|
||||||
event.room.avatar!
|
|
||||||
.getThumbnail(
|
|
||||||
client,
|
|
||||||
width: 126,
|
|
||||||
height: 126,
|
|
||||||
)
|
|
||||||
.toString(),
|
|
||||||
);
|
|
||||||
final person = Person(
|
|
||||||
name: room.getLocalizedDisplayname(MatrixLocals(l10n!)),
|
|
||||||
icon: avatar == null ? null : BitmapFilePathAndroidIcon(avatar.path),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Show notification
|
|
||||||
final androidPlatformChannelSpecifics = _getAndroidNotificationDetails(
|
|
||||||
styleInformation: MessagingStyleInformation(
|
|
||||||
person,
|
|
||||||
conversationTitle: title,
|
|
||||||
messages: [
|
|
||||||
Message(
|
|
||||||
body,
|
|
||||||
event.originServerTs,
|
|
||||||
person,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
ticker: l10n!.newMessageInFluffyChat,
|
|
||||||
);
|
|
||||||
const iOSPlatformChannelSpecifics = IOSNotificationDetails();
|
|
||||||
final platformChannelSpecifics = NotificationDetails(
|
|
||||||
android: androidPlatformChannelSpecifics,
|
|
||||||
iOS: iOSPlatformChannelSpecifics,
|
|
||||||
);
|
|
||||||
await _flutterLocalNotificationsPlugin.show(
|
|
||||||
await mapRoomIdToInt(room.id),
|
|
||||||
room.getLocalizedDisplayname(MatrixLocals(l10n!)),
|
|
||||||
body,
|
|
||||||
platformChannelSpecifics,
|
|
||||||
payload: roomId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> _showDefaultNotification(Map<String, dynamic> data) async {
|
|
||||||
try {
|
|
||||||
await setupLocalNotificationsPlugin();
|
|
||||||
|
|
||||||
await loadLocale();
|
|
||||||
final String? eventId = data['event_id'];
|
|
||||||
final String? roomId = data['room_id'];
|
|
||||||
|
|
||||||
// For legacy reasons the counts map could be a String encoded JSON:
|
|
||||||
final countsMap = data.tryGetMap<String, dynamic>('counts') ??
|
|
||||||
(jsonDecode(data.tryGet<String>('counts') ?? '{}')
|
|
||||||
as Map<String, dynamic>);
|
|
||||||
final unread = countsMap.tryGet<int>('unread') ?? 1;
|
|
||||||
|
|
||||||
if (unread == 0 || roomId == null || eventId == null) {
|
|
||||||
await _onClearingPush();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display notification
|
|
||||||
final androidPlatformChannelSpecifics = _getAndroidNotificationDetails();
|
|
||||||
const iOSPlatformChannelSpecifics = IOSNotificationDetails();
|
|
||||||
final platformChannelSpecifics = NotificationDetails(
|
|
||||||
android: androidPlatformChannelSpecifics,
|
|
||||||
iOS: iOSPlatformChannelSpecifics,
|
|
||||||
);
|
|
||||||
final title = l10n!.unreadChats(unread);
|
|
||||||
await _flutterLocalNotificationsPlugin.show(
|
|
||||||
await mapRoomIdToInt(roomId),
|
|
||||||
title,
|
|
||||||
l10n!.openAppToReadMessages,
|
|
||||||
platformChannelSpecifics,
|
|
||||||
payload: roomId,
|
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
Logs().e('[Push] Error while processing background notification', e, s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AndroidNotificationDetails _getAndroidNotificationDetails(
|
|
||||||
{MessagingStyleInformation? styleInformation, String? ticker}) {
|
|
||||||
final color = (context != null ? Theme.of(context!).primaryColor : null) ??
|
|
||||||
const Color(0xFF5625BA);
|
|
||||||
|
|
||||||
return AndroidNotificationDetails(
|
|
||||||
AppConfig.pushNotificationsChannelId,
|
|
||||||
AppConfig.pushNotificationsChannelName,
|
|
||||||
AppConfig.pushNotificationsChannelDescription,
|
|
||||||
styleInformation: styleInformation,
|
|
||||||
importance: Importance.max,
|
|
||||||
priority: Priority.high,
|
|
||||||
ticker: ticker,
|
|
||||||
color: color,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ import 'matrix_sdk_extensions.dart/flutter_matrix_hive_database.dart';
|
|||||||
|
|
||||||
abstract class ClientManager {
|
abstract class ClientManager {
|
||||||
static const String clientNamespace = 'im.fluffychat.store.clients';
|
static const String clientNamespace = 'im.fluffychat.store.clients';
|
||||||
static Future<List<Client>> getClients() async {
|
static Future<List<Client>> getClients({bool initialize = true}) async {
|
||||||
if (PlatformInfos.isLinux) {
|
if (PlatformInfos.isLinux) {
|
||||||
Hive.init((await getApplicationSupportDirectory()).path);
|
Hive.init((await getApplicationSupportDirectory()).path);
|
||||||
} else {
|
} else {
|
||||||
@ -37,12 +37,15 @@ abstract class ClientManager {
|
|||||||
await Store().setItem(clientNamespace, jsonEncode(clientNames.toList()));
|
await Store().setItem(clientNamespace, jsonEncode(clientNames.toList()));
|
||||||
}
|
}
|
||||||
final clients = clientNames.map(createClient).toList();
|
final clients = clientNames.map(createClient).toList();
|
||||||
await Future.wait(clients.map((client) => client
|
if (initialize) {
|
||||||
.init(
|
await Future.wait(clients.map((client) => client
|
||||||
waitForFirstSync: false,
|
.init(
|
||||||
waitUntilLoadCompletedLoaded: false,
|
waitForFirstSync: false,
|
||||||
)
|
waitUntilLoadCompletedLoaded: false,
|
||||||
.catchError((e, s) => Logs().e('Unable to initialize client', e, s))));
|
)
|
||||||
|
.catchError(
|
||||||
|
(e, s) => Logs().e('Unable to initialize client', e, s))));
|
||||||
|
}
|
||||||
if (clients.length > 1 && clients.any((c) => !c.isLogged())) {
|
if (clients.length > 1 && clients.any((c) => !c.isLogged())) {
|
||||||
final loggedOutClients = clients.where((c) => !c.isLogged()).toList();
|
final loggedOutClients = clients.where((c) => !c.isLogged()).toList();
|
||||||
for (final client in loggedOutClients) {
|
for (final client in loggedOutClients) {
|
||||||
|
@ -265,4 +265,39 @@ class MatrixLocals extends MatrixLocalizations {
|
|||||||
@override
|
@override
|
||||||
String sentReaction(String senderName, String reactionKey) =>
|
String sentReaction(String senderName, String reactionKey) =>
|
||||||
l10n.reactedWith(senderName, reactionKey);
|
l10n.reactedWith(senderName, reactionKey);
|
||||||
|
|
||||||
|
@override
|
||||||
|
// TODO: implement youAcceptedTheInvitation
|
||||||
|
String get youAcceptedTheInvitation => l10n.youAcceptedTheInvitation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String youBannedUser(String targetName) => l10n.youBannedUser(targetName);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String youHaveWithdrawnTheInvitationFor(String targetName) =>
|
||||||
|
l10n.youHaveWithdrawnTheInvitationFor(targetName);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String youInvitedBy(String senderName) => l10n.youInvitedBy(senderName);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String youInvitedUser(String targetName) => l10n.youInvitedUser(targetName);
|
||||||
|
|
||||||
|
@override
|
||||||
|
// TODO: implement youJoinedTheChat
|
||||||
|
String get youJoinedTheChat => l10n.youJoinedTheChat;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String youKicked(String targetName) => l10n.youKicked(targetName);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String youKickedAndBanned(String targetName) =>
|
||||||
|
l10n.youKickedAndBanned(targetName);
|
||||||
|
|
||||||
|
@override
|
||||||
|
// TODO: implement youRejectedTheInvitation
|
||||||
|
String get youRejectedTheInvitation => l10n.youRejectedTheInvitation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String youUnbannedUser(String targetName) => l10n.youUnbannedUser(targetName);
|
||||||
}
|
}
|
||||||
|
146
lib/utils/push_helper.dart
Normal file
146
lib/utils/push_helper.dart
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
|
import 'package:matrix/matrix.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import 'package:fluffychat/config/app_config.dart';
|
||||||
|
import 'package:fluffychat/config/setting_keys.dart';
|
||||||
|
import 'package:fluffychat/utils/client_manager.dart';
|
||||||
|
import 'package:fluffychat/utils/matrix_sdk_extensions.dart/matrix_locals.dart';
|
||||||
|
|
||||||
|
Future<void> pushHelper(
|
||||||
|
PushNotification notification, {
|
||||||
|
Client? client,
|
||||||
|
L10n? l10n,
|
||||||
|
String? activeRoomId,
|
||||||
|
Future<dynamic> Function(String?)? onSelectNotification,
|
||||||
|
}) async {
|
||||||
|
final isBackgroundMessage = client == null;
|
||||||
|
Logs().v(
|
||||||
|
'Push helper has been started (background=$isBackgroundMessage).',
|
||||||
|
notification.toJson(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isBackgroundMessage &&
|
||||||
|
activeRoomId == notification.roomId &&
|
||||||
|
activeRoomId != null &&
|
||||||
|
client?.syncPresence == null) {
|
||||||
|
Logs().v('Room is in foreground. Stop push helper here.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project
|
||||||
|
final _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||||
|
await _flutterLocalNotificationsPlugin.initialize(
|
||||||
|
InitializationSettings(
|
||||||
|
android: const AndroidInitializationSettings('notifications_icon'),
|
||||||
|
iOS: IOSInitializationSettings(
|
||||||
|
onDidReceiveLocalNotification: (i, a, b, c) async => null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onSelectNotification: onSelectNotification,
|
||||||
|
);
|
||||||
|
|
||||||
|
client ??= (await ClientManager.getClients(initialize: false)).first;
|
||||||
|
final event = await client.getEventByPushNotification(
|
||||||
|
notification,
|
||||||
|
storeInDatabase: isBackgroundMessage,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (event == null) {
|
||||||
|
Logs().v('Notification is a clearing indicator.');
|
||||||
|
await _flutterLocalNotificationsPlugin.cancelAll();
|
||||||
|
final store = await SharedPreferences.getInstance();
|
||||||
|
await store.setString(SettingKeys.notificationCurrentIds, json.encode({}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Logs().v('Push helper got notification event.');
|
||||||
|
|
||||||
|
l10n ??= await L10n.delegate.load(window.locale);
|
||||||
|
final matrixLocals = MatrixLocals(l10n);
|
||||||
|
|
||||||
|
// Display notification// Calculate title
|
||||||
|
final title = l10n.unreadMessages(notification.counts?.unread ?? 1);
|
||||||
|
|
||||||
|
// Calculate the body
|
||||||
|
final body = event.getLocalizedBody(
|
||||||
|
matrixLocals,
|
||||||
|
plaintextBody: true,
|
||||||
|
hideReply: true,
|
||||||
|
hideEdit: true,
|
||||||
|
removeMarkdown: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// The person object for the android message style notification
|
||||||
|
if (isBackgroundMessage) WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final avatar = event.room.avatar?.toString();
|
||||||
|
final person = Person(
|
||||||
|
name: event.room.getLocalizedDisplayname(matrixLocals),
|
||||||
|
icon: avatar == null ? null : ContentUriAndroidIcon(avatar),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show notification
|
||||||
|
final androidPlatformChannelSpecifics = AndroidNotificationDetails(
|
||||||
|
AppConfig.pushNotificationsChannelId,
|
||||||
|
AppConfig.pushNotificationsChannelName,
|
||||||
|
AppConfig.pushNotificationsChannelDescription,
|
||||||
|
styleInformation: MessagingStyleInformation(
|
||||||
|
person,
|
||||||
|
conversationTitle: title,
|
||||||
|
groupConversation: !event.room.isDirectChat,
|
||||||
|
messages: [
|
||||||
|
Message(
|
||||||
|
body,
|
||||||
|
event.originServerTs,
|
||||||
|
person,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
ticker: l10n.newMessageInFluffyChat,
|
||||||
|
importance: Importance.max,
|
||||||
|
priority: Priority.high,
|
||||||
|
);
|
||||||
|
const iOSPlatformChannelSpecifics = IOSNotificationDetails();
|
||||||
|
final platformChannelSpecifics = NotificationDetails(
|
||||||
|
android: androidPlatformChannelSpecifics,
|
||||||
|
iOS: iOSPlatformChannelSpecifics,
|
||||||
|
);
|
||||||
|
await _flutterLocalNotificationsPlugin.show(
|
||||||
|
await mapRoomIdToInt(event.room.id),
|
||||||
|
event.room.displayname,
|
||||||
|
body,
|
||||||
|
platformChannelSpecifics,
|
||||||
|
payload: event.roomId,
|
||||||
|
);
|
||||||
|
Logs().v('Push helper has been completed!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Workaround for the problem that local notification IDs must be int but we
|
||||||
|
/// sort by [roomId] which is a String. To make sure that we don't have duplicated
|
||||||
|
/// IDs we map the [roomId] to a number and store this number.
|
||||||
|
Future<int> mapRoomIdToInt(String roomId) async {
|
||||||
|
final store = await SharedPreferences.getInstance();
|
||||||
|
final idMap = Map<String, int>.from(jsonDecode(
|
||||||
|
(store.getString(SettingKeys.notificationCurrentIds)) ?? '{}'));
|
||||||
|
int? currentInt;
|
||||||
|
try {
|
||||||
|
currentInt = idMap[roomId];
|
||||||
|
} catch (_) {
|
||||||
|
currentInt = null;
|
||||||
|
}
|
||||||
|
if (currentInt != null) {
|
||||||
|
return currentInt;
|
||||||
|
}
|
||||||
|
var nCurrentInt = 0;
|
||||||
|
while (idMap.values.contains(currentInt)) {
|
||||||
|
nCurrentInt++;
|
||||||
|
}
|
||||||
|
idMap[roomId] = nCurrentInt;
|
||||||
|
await store.setString(SettingKeys.notificationCurrentIds, json.encode(idMap));
|
||||||
|
return nCurrentInt;
|
||||||
|
}
|
@ -983,7 +983,7 @@ packages:
|
|||||||
name: matrix
|
name: matrix
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.18"
|
version: "0.8.19"
|
||||||
matrix_api_lite:
|
matrix_api_lite:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -24,8 +24,7 @@ dependencies:
|
|||||||
email_validator: ^2.0.1
|
email_validator: ^2.0.1
|
||||||
emoji_picker_flutter: ^1.1.2
|
emoji_picker_flutter: ^1.1.2
|
||||||
encrypt: ^5.0.1
|
encrypt: ^5.0.1
|
||||||
#fcm_shared_isolate:
|
#fcm_shared_isolate: ^0.1.0
|
||||||
# git: https://gitlab.com/famedly/libraries/fcm_shared_isolate.git
|
|
||||||
file_picker_cross: ^4.5.0
|
file_picker_cross: ^4.5.0
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
@ -58,7 +57,7 @@ dependencies:
|
|||||||
keyboard_shortcuts: ^0.1.4
|
keyboard_shortcuts: ^0.1.4
|
||||||
localstorage: ^4.0.0+1
|
localstorage: ^4.0.0+1
|
||||||
lottie: ^1.2.2
|
lottie: ^1.2.2
|
||||||
matrix: ^0.8.18
|
matrix: ^0.8.19
|
||||||
matrix_link_text: ^1.0.2
|
matrix_link_text: ^1.0.2
|
||||||
open_noti_settings: ^0.4.0
|
open_noti_settings: ^0.4.0
|
||||||
package_info_plus: ^1.3.0
|
package_info_plus: ^1.3.0
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
diff --git a/android/app/build.gradle b/android/app/build.gradle
|
diff --git a/android/app/build.gradle b/android/app/build.gradle
|
||||||
index 4e018b38..eebf7582 100644
|
index ad9ffb87..37baafb1 100644
|
||||||
--- a/android/app/build.gradle
|
--- a/android/app/build.gradle
|
||||||
+++ b/android/app/build.gradle
|
+++ b/android/app/build.gradle
|
||||||
@@ -44,7 +44,7 @@ android {
|
@@ -44,7 +44,7 @@ android {
|
||||||
@ -11,7 +11,7 @@ index 4e018b38..eebf7582 100644
|
|||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
@@ -81,7 +81,7 @@ flutter {
|
@@ -82,11 +82,11 @@ flutter {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
@ -20,7 +20,6 @@ index 4e018b38..eebf7582 100644
|
|||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||||
@@ -89,4 +89,4 @@ dependencies {
|
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,11 +55,11 @@ index 85aa8647..3b7e09e7 100644
|
|||||||
}
|
}
|
||||||
|
|
||||||
diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart
|
diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart
|
||||||
index 3347f176..e304abb4 100644
|
index 00ca2aaa..8bb8a156 100644
|
||||||
--- a/lib/utils/background_push.dart
|
--- a/lib/utils/background_push.dart
|
||||||
+++ b/lib/utils/background_push.dart
|
+++ b/lib/utils/background_push.dart
|
||||||
@@ -39,7 +39,7 @@ import 'famedlysdk_store.dart';
|
@@ -39,7 +39,7 @@ import '../config/setting_keys.dart';
|
||||||
import 'matrix_sdk_extensions.dart/matrix_locals.dart';
|
import 'famedlysdk_store.dart';
|
||||||
import 'platform_infos.dart';
|
import 'platform_infos.dart';
|
||||||
|
|
||||||
-//import 'package:fcm_shared_isolate/fcm_shared_isolate.dart';
|
-//import 'package:fcm_shared_isolate/fcm_shared_isolate.dart';
|
||||||
@ -68,28 +67,25 @@ index 3347f176..e304abb4 100644
|
|||||||
|
|
||||||
class NoTokenException implements Exception {
|
class NoTokenException implements Exception {
|
||||||
String get cause => 'Cannot get firebase token';
|
String get cause => 'Cannot get firebase token';
|
||||||
@@ -117,7 +117,7 @@ class BackgroundPush {
|
@@ -65,7 +65,7 @@ class BackgroundPush {
|
||||||
setupPush();
|
|
||||||
}
|
|
||||||
|
|
||||||
- final dynamic _fcmSharedIsolate = null; //FcmSharedIsolate();
|
final pendingTests = <String, Completer<void>>{};
|
||||||
+ final dynamic _fcmSharedIsolate = FcmSharedIsolate();
|
|
||||||
|
- final dynamic firebase = null; //FcmSharedIsolate();
|
||||||
|
+ final dynamic firebase = FcmSharedIsolate();
|
||||||
|
|
||||||
|
DateTime? lastReceivedPush;
|
||||||
|
|
||||||
StreamSubscription<LoginState>? onLogin;
|
|
||||||
StreamSubscription<SyncUpdate>? onRoomSync;
|
|
||||||
diff --git a/pubspec.yaml b/pubspec.yaml
|
diff --git a/pubspec.yaml b/pubspec.yaml
|
||||||
index 73b9eca2..5e6f9f16 100644
|
index c6295788..8dd17ce4 100644
|
||||||
--- a/pubspec.yaml
|
--- a/pubspec.yaml
|
||||||
+++ b/pubspec.yaml
|
+++ b/pubspec.yaml
|
||||||
@@ -25,8 +25,8 @@ dependencies:
|
@@ -24,7 +24,7 @@ dependencies:
|
||||||
email_validator: ^2.0.1
|
email_validator: ^2.0.1
|
||||||
emoji_picker_flutter: ^1.1.2
|
emoji_picker_flutter: ^1.1.2
|
||||||
encrypt: ^5.0.1
|
encrypt: ^5.0.1
|
||||||
- #fcm_shared_isolate:
|
- #fcm_shared_isolate: ^0.1.0
|
||||||
- # git: https://gitlab.com/famedly/libraries/fcm_shared_isolate.git
|
+ fcm_shared_isolate: ^0.1.0
|
||||||
+ fcm_shared_isolate:
|
|
||||||
+ git: https://gitlab.com/famedly/libraries/fcm_shared_isolate.git
|
|
||||||
file_picker_cross: ^4.5.0
|
file_picker_cross: ^4.5.0
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
Loading…
Reference in New Issue
Block a user