Add basic new message notifications
This commit is contained in:
parent
ca198a7cc4
commit
3eaf6ea9c6
14 changed files with 123 additions and 11 deletions
BIN
android/app/src/main/res/drawable-hdpi/ic_notification.png
Normal file
BIN
android/app/src/main/res/drawable-hdpi/ic_notification.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 677 B |
BIN
android/app/src/main/res/drawable-mdpi/ic_notification.png
Normal file
BIN
android/app/src/main/res/drawable-mdpi/ic_notification.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 461 B |
BIN
android/app/src/main/res/drawable-xhdpi/ic_notification.png
Normal file
BIN
android/app/src/main/res/drawable-xhdpi/ic_notification.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 895 B |
BIN
android/app/src/main/res/drawable-xxhdpi/ic_notification.png
Normal file
BIN
android/app/src/main/res/drawable-xxhdpi/ic_notification.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/ic_notification.png
Normal file
BIN
android/app/src/main/res/drawable-xxxhdpi/ic_notification.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
assets/images/logo-white.png
Normal file
BIN
assets/images/logo-white.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
|
@ -13,6 +13,13 @@ class UserApi {
|
|||
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(
|
||||
id: user.id,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -74,7 +74,7 @@ packages:
|
|||
source: hosted
|
||||
version: "1.1.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: collection
|
||||
sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue