Make messages-list use new Provider implementation
This commit is contained in:
parent
33e46c9f22
commit
ab21717829
6 changed files with 147 additions and 180 deletions
|
@ -52,11 +52,11 @@ class MessagingClient extends ChangeNotifier {
|
||||||
final Map<String, Friend> _friendsCache = {};
|
final Map<String, Friend> _friendsCache = {};
|
||||||
final List<Friend> _sortedFriendsCache = []; // Keep a sorted copy so as to not have to sort during build()
|
final List<Friend> _sortedFriendsCache = []; // Keep a sorted copy so as to not have to sort during build()
|
||||||
final Map<String, MessageCache> _messageCache = {};
|
final Map<String, MessageCache> _messageCache = {};
|
||||||
final Map<String, Function> _messageUpdateListeners = {};
|
|
||||||
final Map<String, List<Message>> _unreads = {};
|
final Map<String, List<Message>> _unreads = {};
|
||||||
final Logger _logger = Logger("NeosHub");
|
final Logger _logger = Logger("NeosHub");
|
||||||
final Workmanager _workmanager = Workmanager();
|
final Workmanager _workmanager = Workmanager();
|
||||||
final NotificationClient _notificationClient;
|
final NotificationClient _notificationClient;
|
||||||
|
Friend? selectedFriend;
|
||||||
Timer? _autoRefresh;
|
Timer? _autoRefresh;
|
||||||
Timer? _refreshTimeout;
|
Timer? _refreshTimeout;
|
||||||
int _attempts = 0;
|
int _attempts = 0;
|
||||||
|
@ -172,15 +172,16 @@ class MessagingClient extends ChangeNotifier {
|
||||||
|
|
||||||
List<Friend> get cachedFriends => _sortedFriendsCache;
|
List<Friend> get cachedFriends => _sortedFriendsCache;
|
||||||
|
|
||||||
Future<MessageCache> getMessageCache(String userId) async {
|
MessageCache _createUserMessageCache(String userId) => MessageCache(apiClient: _apiClient, userId: userId);
|
||||||
var cache = _messageCache[userId];
|
|
||||||
if (cache == null){
|
Future<void> loadUserMessageCache(String userId) async {
|
||||||
cache = MessageCache(apiClient: _apiClient, userId: userId);
|
final cache = getUserMessageCache(userId) ?? _createUserMessageCache(userId);
|
||||||
await cache.loadInitialMessages();
|
await cache.loadMessages();
|
||||||
_messageCache[userId] = cache;
|
_messageCache[userId] = cache;
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
return cache;
|
|
||||||
}
|
MessageCache? getUserMessageCache(String userId) => _messageCache[userId];
|
||||||
|
|
||||||
static Future<void> backgroundCheckUnreads(Map<String, dynamic>? inputData) async {
|
static Future<void> backgroundCheckUnreads(Map<String, dynamic>? inputData) async {
|
||||||
if (inputData == null) return;
|
if (inputData == null) return;
|
||||||
|
@ -258,10 +259,6 @@ class MessagingClient extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerMessageListener(String userId, Function function) => _messageUpdateListeners[userId] = function;
|
|
||||||
void unregisterMessageListener(String userId) => _messageUpdateListeners.remove(userId);
|
|
||||||
void notifyMessageListener(String userId) => _messageUpdateListeners[userId]?.call();
|
|
||||||
|
|
||||||
void _handleEvent(event) {
|
void _handleEvent(event) {
|
||||||
final body = jsonDecode((event.toString().replaceAll(eofChar, "")));
|
final body = jsonDecode((event.toString().replaceAll(eofChar, "")));
|
||||||
final int rawType = body["type"] ?? 0;
|
final int rawType = body["type"] ?? 0;
|
||||||
|
@ -301,28 +298,30 @@ class MessagingClient extends ChangeNotifier {
|
||||||
case EventTarget.messageSent:
|
case EventTarget.messageSent:
|
||||||
final msg = args[0];
|
final msg = args[0];
|
||||||
final message = Message.fromMap(msg, withState: MessageState.sent);
|
final message = Message.fromMap(msg, withState: MessageState.sent);
|
||||||
final cache = await getMessageCache(message.recipientId);
|
final cache = getUserMessageCache(message.recipientId) ?? _createUserMessageCache(message.recipientId);
|
||||||
cache.addMessage(message);
|
cache.addMessage(message);
|
||||||
notifyMessageListener(message.recipientId);
|
notifyListeners();
|
||||||
break;
|
break;
|
||||||
case EventTarget.receiveMessage:
|
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 = getUserMessageCache(message.senderId) ?? _createUserMessageCache(message.senderId);
|
||||||
cache.addMessage(message);
|
cache.addMessage(message);
|
||||||
if (!_messageUpdateListeners.containsKey(message.senderId)) {
|
if (message.senderId != selectedFriend?.id) {
|
||||||
addUnread(message);
|
addUnread(message);
|
||||||
}
|
}
|
||||||
notifyMessageListener(message.senderId);
|
notifyListeners();
|
||||||
break;
|
break;
|
||||||
case EventTarget.messagesRead:
|
case EventTarget.messagesRead:
|
||||||
final messageIds = args[0]["ids"] as List;
|
final messageIds = args[0]["ids"] as List;
|
||||||
final recipientId = args[0]["recipientId"];
|
final recipientId = args[0]["recipientId"];
|
||||||
final cache = await getMessageCache(recipientId ?? "");
|
if (recipientId == null) break;
|
||||||
|
final cache = getUserMessageCache(recipientId);
|
||||||
|
if (cache == null) break;
|
||||||
for (var id in messageIds) {
|
for (var id in messageIds) {
|
||||||
cache.setMessageState(id, MessageState.read);
|
cache.setMessageState(id, MessageState.read);
|
||||||
}
|
}
|
||||||
notifyMessageListener(recipientId);
|
notifyListeners();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -337,9 +336,9 @@ class MessagingClient extends ChangeNotifier {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
_sendData(data);
|
_sendData(data);
|
||||||
final cache = await getMessageCache(message.recipientId);
|
final cache = getUserMessageCache(message.recipientId) ?? _createUserMessageCache(message.recipientId);
|
||||||
cache.messages.add(message);
|
cache.messages.add(message);
|
||||||
notifyMessageListener(message.recipientId);
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void markMessagesRead(MarkReadBatch batch) {
|
void markMessagesRead(MarkReadBatch batch) {
|
||||||
|
@ -354,3 +353,8 @@ class MessagingClient extends ChangeNotifier {
|
||||||
_sendData(data);
|
_sendData(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MessagesProvider extends ChangeNotifier {
|
||||||
|
|
||||||
|
}
|
|
@ -69,7 +69,7 @@ class _ContactsPlusPlusState extends State<ContactsPlusPlus> {
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple, brightness: Brightness.dark)
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple, brightness: Brightness.dark)
|
||||||
),
|
),
|
||||||
home: _authData.isAuthenticated ?
|
home: _authData.isAuthenticated ?
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider( // This doesn't need to be a proxy provider since the arguments should never change during it's lifetime.
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
MessagingClient(
|
MessagingClient(
|
||||||
apiClient: clientHolder.apiClient, notificationClient: clientHolder.notificationClient),
|
apiClient: clientHolder.apiClient, notificationClient: clientHolder.notificationClient),
|
||||||
|
|
|
@ -140,7 +140,7 @@ class MessageCache {
|
||||||
return this; //lmao
|
return this; //lmao
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadInitialMessages() async {
|
Future<void> loadMessages() async {
|
||||||
final messages = await MessageApi.getUserMessages(_apiClient, userId: _userId);
|
final messages = await MessageApi.getUserMessages(_apiClient, userId: _userId);
|
||||||
_messages.addAll(messages);
|
_messages.addAll(messages);
|
||||||
_ensureIntegrity();
|
_ensureIntegrity();
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import 'package:contacts_plus_plus/auxiliary.dart';
|
import 'package:contacts_plus_plus/auxiliary.dart';
|
||||||
|
import 'package:contacts_plus_plus/client_holder.dart';
|
||||||
|
import 'package:contacts_plus_plus/clients/messaging_client.dart';
|
||||||
import 'package:contacts_plus_plus/models/friend.dart';
|
import 'package:contacts_plus_plus/models/friend.dart';
|
||||||
|
import 'package:contacts_plus_plus/models/message.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/generic_avatar.dart';
|
import 'package:contacts_plus_plus/widgets/generic_avatar.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/messages_list.dart';
|
import 'package:contacts_plus_plus/widgets/messages_list.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class FriendListTile extends StatelessWidget {
|
class FriendListTile extends StatelessWidget {
|
||||||
const FriendListTile({required this.friend, this.unreads, this.onTap, super.key});
|
const FriendListTile({required this.friend, this.unreads, this.onTap, super.key});
|
||||||
|
@ -24,8 +28,32 @@ class FriendListTile extends StatelessWidget {
|
||||||
title: Text(friend.username),
|
title: Text(friend.username),
|
||||||
subtitle: Text(toBeginningOfSentenceCase(friend.userStatus.onlineStatus.name) ?? "Unknown"),
|
subtitle: Text(toBeginningOfSentenceCase(friend.userStatus.onlineStatus.name) ?? "Unknown"),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
Navigator.of(context).push(MaterialPageRoute(builder: (context) => MessagesList(friend: friend)));
|
onTap?.call();
|
||||||
await onTap?.call();
|
final mClient = Provider.of<MessagingClient>(context, listen: false);
|
||||||
|
mClient.loadUserMessageCache(friend.id);
|
||||||
|
final apiClient = ClientHolder
|
||||||
|
.of(context)
|
||||||
|
.apiClient;
|
||||||
|
final unreads = mClient.getUnreadsForFriend(friend);
|
||||||
|
if (unreads.isNotEmpty) {
|
||||||
|
final readBatch = MarkReadBatch(
|
||||||
|
senderId: apiClient.userId,
|
||||||
|
ids: unreads.map((e) => e.id).toList(),
|
||||||
|
readTime: DateTime.now(),
|
||||||
|
);
|
||||||
|
mClient.markMessagesRead(readBatch);
|
||||||
|
}
|
||||||
|
mClient.selectedFriend = friend;
|
||||||
|
await Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) =>
|
||||||
|
ChangeNotifierProvider<MessagingClient>.value(
|
||||||
|
value: mClient,
|
||||||
|
child: MessagesList(friend: friend),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
mClient.selectedFriend = null;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -289,19 +289,6 @@ class _FriendsListState extends State<FriendsList> {
|
||||||
return FriendListTile(
|
return FriendListTile(
|
||||||
friend: friend,
|
friend: friend,
|
||||||
unreads: unreads.length,
|
unreads: unreads.length,
|
||||||
onTap: () async {
|
|
||||||
if (unreads.isNotEmpty) {
|
|
||||||
final readBatch = MarkReadBatch(
|
|
||||||
senderId: _clientHolder!.apiClient.userId,
|
|
||||||
ids: unreads.map((e) => e.id).toList(),
|
|
||||||
readTime: DateTime.now(),
|
|
||||||
);
|
|
||||||
mClient.markMessagesRead(readBatch);
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
unreads.clear();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:contacts_plus_plus/client_holder.dart';
|
import 'package:contacts_plus_plus/client_holder.dart';
|
||||||
import 'package:contacts_plus_plus/auxiliary.dart';
|
import 'package:contacts_plus_plus/auxiliary.dart';
|
||||||
|
import 'package:contacts_plus_plus/clients/messaging_client.dart';
|
||||||
import 'package:contacts_plus_plus/models/friend.dart';
|
import 'package:contacts_plus_plus/models/friend.dart';
|
||||||
import 'package:contacts_plus_plus/models/message.dart';
|
import 'package:contacts_plus_plus/models/message.dart';
|
||||||
import 'package:contacts_plus_plus/models/session.dart';
|
import 'package:contacts_plus_plus/models/session.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/default_error_widget.dart';
|
|
||||||
import 'package:contacts_plus_plus/widgets/message_audio_player.dart';
|
import 'package:contacts_plus_plus/widgets/message_audio_player.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/generic_avatar.dart';
|
import 'package:contacts_plus_plus/widgets/generic_avatar.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/message_session_invite.dart';
|
import 'package:contacts_plus_plus/widgets/message_session_invite.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class MessagesList extends StatefulWidget {
|
class MessagesList extends StatefulWidget {
|
||||||
const MessagesList({required this.friend, super.key});
|
const MessagesList({required this.friend, super.key});
|
||||||
|
@ -23,49 +22,18 @@ class MessagesList extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MessagesListState extends State<MessagesList> {
|
class _MessagesListState extends State<MessagesList> {
|
||||||
Future<MessageCache>? _messageCacheFuture;
|
|
||||||
final TextEditingController _messageTextController = TextEditingController();
|
final TextEditingController _messageTextController = TextEditingController();
|
||||||
final ScrollController _sessionListScrollController = ScrollController();
|
final ScrollController _sessionListScrollController = ScrollController();
|
||||||
final ScrollController _messageScrollController = ScrollController();
|
final ScrollController _messageScrollController = ScrollController();
|
||||||
ClientHolder? _clientHolder;
|
|
||||||
|
|
||||||
bool _isSendable = false;
|
bool _isSendable = false;
|
||||||
bool _showSessionListChevron = false;
|
bool _showSessionListScrollChevron = false;
|
||||||
bool _messageCacheFutureComplete = false;
|
|
||||||
|
|
||||||
double get _shevronOpacity => _showSessionListChevron ? 1.0 : 0.0;
|
double get _shevronOpacity => _showSessionListScrollChevron ? 1.0 : 0.0;
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
final clientHolder = ClientHolder.of(context);
|
|
||||||
if (_clientHolder != clientHolder) {
|
|
||||||
_clientHolder = clientHolder;
|
|
||||||
}
|
|
||||||
_loadMessages();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _loadMessages() {
|
|
||||||
_messageCacheFutureComplete = false;
|
|
||||||
/* TODO: Use provider
|
|
||||||
_messageCacheFuture = _clientHolder?.messagingClient.getMessageCache(widget.friend.id)
|
|
||||||
.whenComplete(() => _messageCacheFutureComplete = true);
|
|
||||||
final mClient = _clientHolder?.messagingClient;
|
|
||||||
final id = widget.friend.id;
|
|
||||||
mClient?.registerMessageListener(id, () {
|
|
||||||
if (context.mounted) {
|
|
||||||
setState(() {});
|
|
||||||
} else {
|
|
||||||
mClient.unregisterMessageListener(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
// TODO user provider
|
|
||||||
//_clientHolder?.messagingClient.unregisterMessageListener(widget.friend.id);
|
|
||||||
_messageTextController.dispose();
|
_messageTextController.dispose();
|
||||||
_sessionListScrollController.dispose();
|
_sessionListScrollController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
@ -75,26 +43,15 @@ class _MessagesListState extends State<MessagesList> {
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_sessionListScrollController.addListener(() {
|
_sessionListScrollController.addListener(() {
|
||||||
if (_sessionListScrollController.position.maxScrollExtent > 0 && !_showSessionListChevron) {
|
if (_sessionListScrollController.position.maxScrollExtent > 0 && !_showSessionListScrollChevron) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_showSessionListChevron = true;
|
_showSessionListScrollChevron = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (_sessionListScrollController.position.atEdge && _sessionListScrollController.position.pixels > 0
|
if (_sessionListScrollController.position.atEdge && _sessionListScrollController.position.pixels > 0
|
||||||
&& _showSessionListChevron) {
|
&& _showSessionListScrollChevron) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_showSessionListChevron = false;
|
_showSessionListScrollChevron = false;
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_messageScrollController.addListener(() {
|
|
||||||
if (_messageScrollController.position.atEdge && _messageScrollController.position.pixels > 0 &&
|
|
||||||
_messageScrollController.position.maxScrollExtent > 0 && _messageCacheFutureComplete) {
|
|
||||||
setState(() {
|
|
||||||
_messageCacheFutureComplete = false;
|
|
||||||
// TODO: Use provider
|
|
||||||
//_messageCacheFuture = _clientHolder?.messagingClient.getMessageCache(widget.friend.id)
|
|
||||||
// .then((value) => value.loadOlderMessages()).whenComplete(() => _messageCacheFutureComplete = true);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -160,11 +117,17 @@ class _MessagesListState extends State<MessagesList> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: FutureBuilder(
|
child: Consumer<MessagingClient>(
|
||||||
future: _messageCacheFuture,
|
builder: (context, mClient, _) {
|
||||||
builder: (context, snapshot) {
|
final cache = mClient.getUserMessageCache(widget.friend.id);
|
||||||
if (snapshot.hasData) {
|
if (cache == null) {
|
||||||
final cache = snapshot.data as MessageCache;
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: const [
|
||||||
|
LinearProgressIndicator()
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
if (cache.messages.isEmpty) {
|
if (cache.messages.isEmpty) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
@ -204,24 +167,6 @@ class _MessagesListState extends State<MessagesList> {
|
||||||
return widget;
|
return widget;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else if (snapshot.hasError) {
|
|
||||||
return DefaultErrorWidget(
|
|
||||||
message: "${snapshot.error}",
|
|
||||||
onRetry: () {
|
|
||||||
setState(() {
|
|
||||||
_loadMessages();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: const [
|
|
||||||
LinearProgressIndicator(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -269,9 +214,11 @@ class _MessagesListState extends State<MessagesList> {
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 8, right: 4.0),
|
padding: const EdgeInsets.only(left: 8, right: 4.0),
|
||||||
child: IconButton(
|
child: Consumer<MessagingClient>(
|
||||||
|
builder: (context, mClient, _) {
|
||||||
|
return IconButton(
|
||||||
splashRadius: 24,
|
splashRadius: 24,
|
||||||
onPressed: _isSendable && _clientHolder != null ? () async {
|
onPressed: _isSendable ? () async {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isSendable = false;
|
_isSendable = false;
|
||||||
});
|
});
|
||||||
|
@ -284,8 +231,7 @@ class _MessagesListState extends State<MessagesList> {
|
||||||
sendTime: DateTime.now().toUtc(),
|
sendTime: DateTime.now().toUtc(),
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
// TODO use provider
|
mClient.sendMessage(message);
|
||||||
//_clientHolder!.messagingClient.sendMessage(message);
|
|
||||||
_messageTextController.clear();
|
_messageTextController.clear();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -303,6 +249,8 @@ class _MessagesListState extends State<MessagesList> {
|
||||||
} : null,
|
} : null,
|
||||||
iconSize: 28,
|
iconSize: 28,
|
||||||
icon: const Icon(Icons.send),
|
icon: const Icon(Icons.send),
|
||||||
|
);
|
||||||
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in a new issue