Add unread indicator functionality

This commit is contained in:
Nutcake 2023-05-03 20:03:46 +02:00
parent 3a4c7be758
commit bc57b20219
5 changed files with 107 additions and 30 deletions

View file

@ -25,16 +25,8 @@ class Friend extends Comparable {
}
@override
int compareTo(other) {
if (userStatus.onlineStatus == other.userStatus.onlineStatus) {
return userStatus.lastStatusChange.compareTo(other.userStatus.lastStatusChange);
} else {
if (userStatus.onlineStatus == OnlineStatus.online) {
return -1;
} else {
return 1;
}
}
int compareTo(covariant Friend other) {
return username.compareTo(other.username);
}
}

View file

@ -165,3 +165,19 @@ class AudioClipContent {
);
}
}
class MarkReadBatch {
final String senderId;
final List<String> ids;
final DateTime readTime;
MarkReadBatch({required this.senderId, required this.ids, required this.readTime});
Map toMap() {
return {
"senderId": senderId,
"ids": ids,
"readTime": readTime.toUtc().toIso8601String(),
};
}
}

View file

@ -1,5 +1,6 @@
import 'dart:convert';
import 'dart:io';
import 'package:contacts_plus_plus/apis/message_api.dart';
import 'package:http/http.dart' as http;
import 'package:contacts_plus_plus/api_client.dart';
@ -42,6 +43,11 @@ class NeosHub {
start();
}
void _sendData(data) {
if (_wsChannel == null) throw "Neos Hub is not connected";
_wsChannel!.add(jsonEncode(data)+eofChar);
}
Future<MessageCache> getCache(String userId) async {
var cache = _messageCache[userId];
if (cache == null){
@ -52,6 +58,13 @@ class NeosHub {
return cache;
}
Future<void> checkUnreads() async {
final unreads = await MessageApi.getUserMessages(_apiClient, unreadOnly: true);
for (var message in unreads) {
throw UnimplementedError();
}
}
void _onDisconnected(error) {
_logger.warning("Neos Hub connection died with error '$error', reconnecting...");
start();
@ -161,7 +174,6 @@ class NeosHub {
}
void sendMessage(Message message) async {
if (_wsChannel == null) throw "Neos Hub is not connected";
final msgBody = message.toMap();
final data = {
"type": EventType.message.index,
@ -170,9 +182,21 @@ class NeosHub {
msgBody
],
};
_sendData(data);
final cache = await getCache(message.recipientId);
cache.messages.add(message);
_wsChannel!.add(jsonEncode(data)+eofChar);
notifyListener(message.recipientId);
}
void markMessagesRead(MarkReadBatch batch) {
final msgBody = batch.toMap();
final data = {
"type": EventType.message.index,
"target": "MarkMessagesRead",
"arguments": [
msgBody
],
};
_sendData(data);
}
}

View file

@ -5,19 +5,26 @@ import 'package:contacts_plus_plus/widgets/messages.dart';
import 'package:flutter/material.dart';
class FriendListTile extends StatelessWidget {
const FriendListTile({required this.friend, super.key});
const FriendListTile({required this.friend, this.unreads, this.onTap, super.key});
final Friend friend;
final int? unreads;
final Function? onTap;
@override
Widget build(BuildContext context) {
final imageUri = Aux.neosDbToHttp(friend.userProfile.iconUrl);
final theme = Theme.of(context);
return ListTile(
leading: GenericAvatar(imageUri: imageUri,),
trailing: unreads != null && unreads != 0
? Text("+$unreads", style: theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.primary),)
: null,
title: Text(friend.username),
subtitle: Text(friend.userStatus.onlineStatus.name),
onTap: () {
onTap: () async {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => Messages(friend: friend)));
await onTap?.call();
},
);
}

View file

@ -2,7 +2,9 @@ import 'dart:async';
import 'package:contacts_plus_plus/api_client.dart';
import 'package:contacts_plus_plus/apis/friend_api.dart';
import 'package:contacts_plus_plus/apis/message_api.dart';
import 'package:contacts_plus_plus/models/friend.dart';
import 'package:contacts_plus_plus/models/message.dart';
import 'package:contacts_plus_plus/widgets/expanding_input_fab.dart';
import 'package:contacts_plus_plus/widgets/friend_list_tile.dart';
import 'package:contacts_plus_plus/widgets/settings_page.dart';
@ -20,6 +22,7 @@ class _FriendsListState extends State<FriendsList> {
ClientHolder? _clientHolder;
Timer? _debouncer;
String _searchFilter = "";
final _unreads = <String, List<Message>>{};
@override
void dispose() {
@ -38,17 +41,32 @@ class _FriendsListState extends State<FriendsList> {
}
void _refreshFriendsList() {
_friendsFuture = FriendApi.getFriendsList(_clientHolder!.client).then((Iterable<Friend> value) =>
value.toList()
..sort((a, b) {
if (a.userStatus.onlineStatus == b.userStatus.onlineStatus) {
return a.userStatus.lastStatusChange.compareTo(b.userStatus.lastStatusChange);
_friendsFuture = FriendApi.getFriendsList(_clientHolder!.client).then((Iterable<Friend> value) async {
final unreadMessages = await MessageApi.getUserMessages(_clientHolder!.client, unreadOnly: true);
_unreads.clear();
for (final msg in unreadMessages) {
if (msg.senderId != _clientHolder!.client.userId) {
final value = _unreads[msg.senderId];
if (value == null) {
_unreads[msg.senderId] = [msg];
} else {
return a.userStatus.onlineStatus.compareTo(b.userStatus.onlineStatus);
value.add(msg);
}
},
),
);
}
}
final friends = value.toList()
..sort((a, b) {
var aVal = _unreads.containsKey(a.id) ? -3 : 0;
var bVal = _unreads.containsKey(b.id) ? -3 : 0;
aVal -= a.userStatus.lastStatusChange.compareTo(b.userStatus.lastStatusChange);
aVal += a.userStatus.onlineStatus.compareTo(b.userStatus.onlineStatus) * 2;
return aVal.compareTo(bVal);
});
return friends;
});
}
@override
@ -76,14 +94,34 @@ class _FriendsListState extends State<FriendsList> {
future: _friendsFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
var data = (snapshot.data as List<Friend>);
var friends = (snapshot.data as List<Friend>);
if (_searchFilter.isNotEmpty) {
data = data.where((element) => element.username.contains(_searchFilter)).toList();
data.sort((a, b) => a.username.length.compareTo(b.username.length));
friends = friends.where((element) => element.username.contains(_searchFilter)).toList();
friends.sort((a, b) => a.username.length.compareTo(b.username.length));
}
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) => FriendListTile(friend: data[index]),
itemCount: friends.length,
itemBuilder: (context, index) {
final friend = friends[index];
final unread = _unreads[friend.id] ?? [];
return FriendListTile(
friend: friend,
unreads: unread.length,
onTap: () async {
if (unread.isNotEmpty) {
final readBatch = MarkReadBatch(
senderId: _clientHolder!.client.userId,
ids: unread.map((e) => e.id).toList(),
readTime: DateTime.now(),
);
_clientHolder!.hub.markMessagesRead(readBatch);
}
setState(() {
unread.clear();
});
},
);
},
);
} else if (snapshot.hasError) {
FlutterError.reportError(FlutterErrorDetails(exception: snapshot.error!, stack: snapshot.stackTrace));