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));
|
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 {
|
static Future<void> addUserAsFriend(ApiClient client, {required User user}) async {
|
||||||
final friend = Friend(
|
final friend = Friend(
|
||||||
id: user.id,
|
id: user.id,
|
||||||
|
|
|
@ -66,11 +66,13 @@ extension Unique<E, Id> on List<E> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StripHTLM on String {
|
extension Strip on String {
|
||||||
String stripHtml() {
|
String stripHtml() {
|
||||||
final document = htmlparser.parse(this);
|
final document = htmlparser.parse(this);
|
||||||
return htmlparser.parse(document.body?.text).documentElement?.text ?? "";
|
return htmlparser.parse(document.body?.text).documentElement?.text ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String stripUid() => startsWith("U-") ? substring(2) : this;
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Format on Duration {
|
extension Format on Duration {
|
||||||
|
|
|
@ -150,6 +150,7 @@ class ClientHolder extends InheritedWidget {
|
||||||
final ApiClient apiClient;
|
final ApiClient apiClient;
|
||||||
final SettingsClient settingsClient;
|
final SettingsClient settingsClient;
|
||||||
late final MessagingClient messagingClient;
|
late final MessagingClient messagingClient;
|
||||||
|
final NotificationClient notificationClient = NotificationClient();
|
||||||
|
|
||||||
ClientHolder({
|
ClientHolder({
|
||||||
super.key,
|
super.key,
|
||||||
|
@ -157,7 +158,7 @@ class ClientHolder extends InheritedWidget {
|
||||||
required this.settingsClient,
|
required this.settingsClient,
|
||||||
required super.child
|
required super.child
|
||||||
}) : apiClient = ApiClient(authenticationData: authenticationData) {
|
}) : apiClient = ApiClient(authenticationData: authenticationData) {
|
||||||
messagingClient = MessagingClient(apiClient: apiClient);
|
messagingClient = MessagingClient(apiClient: apiClient, notificationClient: notificationClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ClientHolder? maybeOf(BuildContext context) {
|
static ClientHolder? maybeOf(BuildContext context) {
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:contacts_plus_plus/apis/message_api.dart';
|
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/authentication_data.dart';
|
||||||
import 'package:contacts_plus_plus/models/friend.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:http/http.dart' as http;
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
import 'package:contacts_plus_plus/clients/api_client.dart';
|
import 'package:contacts_plus_plus/clients/api_client.dart';
|
||||||
import 'package:contacts_plus_plus/config.dart';
|
import 'package:contacts_plus_plus/config.dart';
|
||||||
|
@ -19,7 +23,7 @@ enum EventType {
|
||||||
enum EventTarget {
|
enum EventTarget {
|
||||||
unknown,
|
unknown,
|
||||||
messageSent,
|
messageSent,
|
||||||
messageReceived,
|
receiveMessage,
|
||||||
messagesRead;
|
messagesRead;
|
||||||
|
|
||||||
factory EventTarget.parse(String? text) {
|
factory EventTarget.parse(String? text) {
|
||||||
|
@ -41,11 +45,12 @@ class MessagingClient {
|
||||||
final Map<String, Function> _updateListeners = {};
|
final Map<String, Function> _updateListeners = {};
|
||||||
final Logger _logger = Logger("NeosHub");
|
final Logger _logger = Logger("NeosHub");
|
||||||
final Workmanager _workmanager = Workmanager();
|
final Workmanager _workmanager = Workmanager();
|
||||||
|
final NotificationClient _notificationClient;
|
||||||
WebSocket? _wsChannel;
|
WebSocket? _wsChannel;
|
||||||
bool _isConnecting = false;
|
bool _isConnecting = false;
|
||||||
|
|
||||||
MessagingClient({required ApiClient apiClient})
|
MessagingClient({required ApiClient apiClient, required NotificationClient notificationClient})
|
||||||
: _apiClient = apiClient {
|
: _apiClient = apiClient, _notificationClient = notificationClient {
|
||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,11 +188,14 @@ class MessagingClient {
|
||||||
cache.addMessage(message);
|
cache.addMessage(message);
|
||||||
notifyListener(message.recipientId);
|
notifyListener(message.recipientId);
|
||||||
break;
|
break;
|
||||||
case EventTarget.messageReceived:
|
case EventTarget.receiveMessage:
|
||||||
final msg = args[0];
|
final msg = args[0];
|
||||||
final message = Message.fromMap(msg);
|
final message = Message.fromMap(msg);
|
||||||
final cache = await getMessageCache(message.senderId);
|
final cache = await getMessageCache(message.senderId);
|
||||||
cache.addMessage(message);
|
cache.addMessage(message);
|
||||||
|
if (!_updateListeners.containsKey(message.senderId)) {
|
||||||
|
_notificationClient.showUnreadMessagesNotification([message]);
|
||||||
|
}
|
||||||
notifyListener(message.senderId);
|
notifyListener(message.senderId);
|
||||||
break;
|
break;
|
||||||
case EventTarget.messagesRead:
|
case EventTarget.messagesRead:
|
||||||
|
@ -229,3 +237,90 @@ class MessagingClient {
|
||||||
_sendData(data);
|
_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 {
|
_friendsFuture = FriendApi.getFriendsList(_clientHolder!.apiClient).then((Iterable<Friend> value) async {
|
||||||
final unreadMessages = await MessageApi.getUserMessages(_clientHolder!.apiClient, unreadOnly: true);
|
final unreadMessages = await MessageApi.getUserMessages(_clientHolder!.apiClient, unreadOnly: true);
|
||||||
_unreads.clear();
|
_unreads.clear();
|
||||||
|
|
||||||
for (final msg in unreadMessages) {
|
for (final msg in unreadMessages) {
|
||||||
if (msg.senderId != _clientHolder!.apiClient.userId) {
|
if (msg.senderId != _clientHolder!.apiClient.userId) {
|
||||||
final value = _unreads[msg.senderId];
|
final value = _unreads[msg.senderId];
|
||||||
|
@ -128,7 +127,7 @@ class _FriendsListState extends State<FriendsList> {
|
||||||
} else {
|
} else {
|
||||||
_autoRefresh = Timer(_autoRefreshDuration, () => setState(() => _refreshFriendsList()));
|
_autoRefresh = Timer(_autoRefreshDuration, () => setState(() => _refreshFriendsList()));
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
].map((item) =>
|
].map((item) =>
|
||||||
PopupMenuItem<MenuItemDefinition>(
|
PopupMenuItem<MenuItemDefinition>(
|
||||||
value: item,
|
value: item,
|
||||||
|
|
|
@ -49,8 +49,15 @@ class _MessagesListState extends State<MessagesList> {
|
||||||
_messageCacheFutureComplete = false;
|
_messageCacheFutureComplete = false;
|
||||||
_messageCacheFuture = _clientHolder?.messagingClient.getMessageCache(widget.friend.id)
|
_messageCacheFuture = _clientHolder?.messagingClient.getMessageCache(widget.friend.id)
|
||||||
.whenComplete(() => _messageCacheFutureComplete = true);
|
.whenComplete(() => _messageCacheFutureComplete = true);
|
||||||
_clientHolder?.messagingClient.registerListener(
|
final mClient = _clientHolder?.messagingClient;
|
||||||
widget.friend.id, () => setState(() {}));
|
final id = widget.friend.id;
|
||||||
|
mClient?.registerListener(id, () {
|
||||||
|
if (context.mounted) {
|
||||||
|
setState(() {});
|
||||||
|
} else {
|
||||||
|
mClient.unregisterListener(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -74,7 +74,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
|
sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
|
||||||
|
|
|
@ -50,6 +50,7 @@ dependencies:
|
||||||
url_launcher: ^6.1.10
|
url_launcher: ^6.1.10
|
||||||
workmanager: ^0.5.1
|
workmanager: ^0.5.1
|
||||||
flutter_local_notifications: ^14.0.0+1
|
flutter_local_notifications: ^14.0.0+1
|
||||||
|
collection: any
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in a new issue