Add basic new message notifications

This commit is contained in:
Nutcake 2023-05-05 11:29:54 +02:00
parent ca198a7cc4
commit 3eaf6ea9c6
14 changed files with 123 additions and 11 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 895 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View file

@ -12,6 +12,13 @@ class UserApi {
final data = jsonDecode(response.body) as List;
return data.map((e) => User.fromMap(e));
}
static Future<User> getUser(ApiClient client, {required String userId}) async {
final response = await client.get("/users/$userId/");
ApiClient.checkResponse(response);
final data = jsonDecode(response.body);
return User.fromMap(data);
}
static Future<void> addUserAsFriend(ApiClient client, {required User user}) async {
final friend = Friend(

View file

@ -66,11 +66,13 @@ extension Unique<E, Id> on List<E> {
}
}
extension StripHTLM on String {
extension Strip on String {
String stripHtml() {
final document = htmlparser.parse(this);
return htmlparser.parse(document.body?.text).documentElement?.text ?? "";
}
String stripUid() => startsWith("U-") ? substring(2) : this;
}
extension Format on Duration {

View file

@ -150,6 +150,7 @@ class ClientHolder extends InheritedWidget {
final ApiClient apiClient;
final SettingsClient settingsClient;
late final MessagingClient messagingClient;
final NotificationClient notificationClient = NotificationClient();
ClientHolder({
super.key,
@ -157,7 +158,7 @@ class ClientHolder extends InheritedWidget {
required this.settingsClient,
required super.child
}) : apiClient = ApiClient(authenticationData: authenticationData) {
messagingClient = MessagingClient(apiClient: apiClient);
messagingClient = MessagingClient(apiClient: apiClient, notificationClient: notificationClient);
}
static ClientHolder? maybeOf(BuildContext context) {

View file

@ -1,9 +1,13 @@
import 'dart:convert';
import 'dart:io';
import 'package:contacts_plus_plus/apis/message_api.dart';
import 'package:contacts_plus_plus/auxiliary.dart';
import 'package:contacts_plus_plus/models/authentication_data.dart';
import 'package:contacts_plus_plus/models/friend.dart';
import 'package:contacts_plus_plus/models/session.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart' as fln;
import 'package:http/http.dart' as http;
import 'package:collection/collection.dart';
import 'package:contacts_plus_plus/clients/api_client.dart';
import 'package:contacts_plus_plus/config.dart';
@ -19,7 +23,7 @@ enum EventType {
enum EventTarget {
unknown,
messageSent,
messageReceived,
receiveMessage,
messagesRead;
factory EventTarget.parse(String? text) {
@ -41,11 +45,12 @@ class MessagingClient {
final Map<String, Function> _updateListeners = {};
final Logger _logger = Logger("NeosHub");
final Workmanager _workmanager = Workmanager();
final NotificationClient _notificationClient;
WebSocket? _wsChannel;
bool _isConnecting = false;
MessagingClient({required ApiClient apiClient})
: _apiClient = apiClient {
MessagingClient({required ApiClient apiClient, required NotificationClient notificationClient})
: _apiClient = apiClient, _notificationClient = notificationClient {
start();
}
@ -183,11 +188,14 @@ class MessagingClient {
cache.addMessage(message);
notifyListener(message.recipientId);
break;
case EventTarget.messageReceived:
case EventTarget.receiveMessage:
final msg = args[0];
final message = Message.fromMap(msg);
final cache = await getMessageCache(message.senderId);
cache.addMessage(message);
if (!_updateListeners.containsKey(message.senderId)) {
_notificationClient.showUnreadMessagesNotification([message]);
}
notifyListener(message.senderId);
break;
case EventTarget.messagesRead:
@ -229,3 +237,90 @@ class MessagingClient {
_sendData(data);
}
}
class NotificationChannel {
final String id;
final String name;
final String description;
const NotificationChannel({required this.name, required this.id, required this.description});
}
class NotificationClient {
static const NotificationChannel _messageChannel = NotificationChannel(
id: "messages",
name: "Messages",
description: "Messages received from your friends",
);
final fln.FlutterLocalNotificationsPlugin _notifier = fln.FlutterLocalNotificationsPlugin()
..initialize(
const fln.InitializationSettings(
android: fln.AndroidInitializationSettings("ic_notification"),
)
);
Future<void> showUnreadMessagesNotification(List<Message> messages) async {
if (messages.isEmpty) return;
final bySender = groupBy(messages, (p0) => p0.senderId);
for (final entry in bySender.entries) {
final uname = entry.key.stripUid();
await _notifier.show(
uname.hashCode,
null,
null,
fln.NotificationDetails(android: fln.AndroidNotificationDetails(
_messageChannel.id,
_messageChannel.name,
channelDescription: _messageChannel.description,
importance: fln.Importance.high,
priority: fln.Priority.max,
styleInformation: fln.MessagingStyleInformation(
fln.Person(
name: uname,
bot: false,
),
groupConversation: false,
messages: entry.value.map((message) {
String content;
switch (message.type) {
case MessageType.unknown:
content = "Unknown Message Type";
break;
case MessageType.text:
content = message.content;
break;
case MessageType.sound:
content = "Audio Message";
break;
case MessageType.sessionInvite:
try {
final session = Session.fromMap(jsonDecode(message.content));
content = "Session Invite to ${session.name}";
} catch (e) {
content = "Session Invite";
}
break;
case MessageType.object:
content = "Asset";
break;
}
return fln.Message(
content,
message.sendTime,
fln.Person(
name: uname,
bot: false,
),
);
}).toList(),
),
),
),
);
}
}
}

View file

@ -60,7 +60,6 @@ class _FriendsListState extends State<FriendsList> {
_friendsFuture = FriendApi.getFriendsList(_clientHolder!.apiClient).then((Iterable<Friend> value) async {
final unreadMessages = await MessageApi.getUserMessages(_clientHolder!.apiClient, unreadOnly: true);
_unreads.clear();
for (final msg in unreadMessages) {
if (msg.senderId != _clientHolder!.apiClient.userId) {
final value = _unreads[msg.senderId];
@ -128,7 +127,7 @@ class _FriendsListState extends State<FriendsList> {
} else {
_autoRefresh = Timer(_autoRefreshDuration, () => setState(() => _refreshFriendsList()));
}
})
}),
].map((item) =>
PopupMenuItem<MenuItemDefinition>(
value: item,

View file

@ -49,8 +49,15 @@ class _MessagesListState extends State<MessagesList> {
_messageCacheFutureComplete = false;
_messageCacheFuture = _clientHolder?.messagingClient.getMessageCache(widget.friend.id)
.whenComplete(() => _messageCacheFutureComplete = true);
_clientHolder?.messagingClient.registerListener(
widget.friend.id, () => setState(() {}));
final mClient = _clientHolder?.messagingClient;
final id = widget.friend.id;
mClient?.registerListener(id, () {
if (context.mounted) {
setState(() {});
} else {
mClient.unregisterListener(id);
}
});
}
@override

View file

@ -74,7 +74,7 @@ packages:
source: hosted
version: "1.1.1"
collection:
dependency: transitive
dependency: "direct main"
description:
name: collection
sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0

View file

@ -50,6 +50,7 @@ dependencies:
url_launcher: ^6.1.10
workmanager: ^0.5.1
flutter_local_notifications: ^14.0.0+1
collection: any
dev_dependencies:
flutter_test: