Add unread indicator functionality
This commit is contained in:
parent
3a4c7be758
commit
bc57b20219
5 changed files with 107 additions and 30 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Reference in a new issue