diff --git a/lib/pages/views/chat_list_view.dart b/lib/pages/views/chat_list_view.dart index 79db5c38..8d35cb75 100644 --- a/lib/pages/views/chat_list_view.dart +++ b/lib/pages/views/chat_list_view.dart @@ -9,6 +9,7 @@ import 'package:flutter/material.dart'; import 'package:vrouter/vrouter.dart'; import '../../widgets/matrix.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import '../../utils/stream_extension.dart'; class ChatListView extends StatelessWidget { final ChatListController controller; @@ -161,7 +162,8 @@ class ChatListView extends StatelessWidget { .client .onSync .stream - .where((s) => s.hasRoomUpdate), + .where((s) => s.hasRoomUpdate) + .rateLimit(Duration(seconds: 1)), builder: (context, snapshot) { return FutureBuilder( future: controller.waitForFirstSync(), diff --git a/lib/pages/views/chat_view.dart b/lib/pages/views/chat_view.dart index fc2260f7..e60f8e2d 100644 --- a/lib/pages/views/chat_view.dart +++ b/lib/pages/views/chat_view.dart @@ -27,6 +27,8 @@ import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:swipe_to_action/swipe_to_action.dart'; import 'package:vrouter/vrouter.dart'; +import '../../utils/stream_extension.dart'; + class ChatView extends StatelessWidget { final ChatController controller; @@ -72,7 +74,8 @@ class ChatView extends StatelessWidget { titleSpacing: 0, title: controller.selectedEvents.isEmpty ? StreamBuilder( - stream: controller.room.onUpdate.stream, + stream: controller.room.onUpdate.stream + .rateLimit(Duration(milliseconds: 250)), builder: (context, snapshot) => ListTile( leading: Avatar(controller.room.avatar, controller.room.displayname), @@ -105,7 +108,8 @@ class ChatView extends StatelessWidget { .stream .where((p) => p.senderId == - controller.room.directChatMatrixID), + controller.room.directChatMatrixID) + .rateLimit(Duration(seconds: 1)), builder: (context, snapshot) => Text( controller.room .getLocalizedStatus(context), @@ -283,8 +287,10 @@ class ChatView extends StatelessWidget { : Container() : i == 0 ? StreamBuilder( - stream: - controller.room.onUpdate.stream, + stream: controller + .room.onUpdate.stream + .rateLimit(Duration( + milliseconds: 250)), builder: (_, __) { final seenByText = controller.room .getLocalizedSeenByText( diff --git a/lib/utils/stream_extension.dart b/lib/utils/stream_extension.dart new file mode 100644 index 00000000..e7b739da --- /dev/null +++ b/lib/utils/stream_extension.dart @@ -0,0 +1,46 @@ +import 'dart:async'; + +extension StreamExtension on Stream { + /// Returns a new Stream which outputs only `true` for every update of the original + /// stream, ratelimited by the Duration t + Stream rateLimit(Duration t) { + final controller = StreamController(); + Timer timer; + var gotMessage = false; + // as we call our inline-defined function recursively we need to make sure that the + // variable exists prior of creating the function. Silly dart. + Function _onMessage; + // callback to determine if we should send out an update + _onMessage = () { + // do nothing if it is already closed + if (controller.isClosed) { + return; + } + if (timer == null) { + // if we don't have a timer yet, send out the update and start a timer + gotMessage = false; + controller.add(true); + timer = Timer(t, () { + // the timer has ended...delete it and, if we got a message, re-run the + // method to send out an update! + timer = null; + if (gotMessage) { + _onMessage(); + } + }); + } else { + // set that we got a message + gotMessage = true; + } + }; + final subscription = listen((_) => _onMessage(), + onDone: () => controller.close(), + onError: (e, s) => controller.addError(e, s)); + // add proper cleanup to the subscription and the controller, to not memory leak + controller.onCancel = () { + subscription.cancel(); + controller.close(); + }; + return controller.stream; + } +} diff --git a/lib/widgets/list_items/chat_list_item.dart b/lib/widgets/list_items/chat_list_item.dart index 93bb6387..c0a48889 100644 --- a/lib/widgets/list_items/chat_list_item.dart +++ b/lib/widgets/list_items/chat_list_item.dart @@ -289,7 +289,9 @@ class ChatListItem extends StatelessWidget { curve: Curves.bounceInOut, padding: EdgeInsets.symmetric(horizontal: 7), height: unreadBubbleSize, - width: room.notificationCount == 0 && !room.isUnread ? 0 : null, + width: room.notificationCount == 0 && !room.isUnread + ? 0 + : unreadBubbleSize, decoration: BoxDecoration( color: room.highlightCount > 0 ? Colors.red