feat: Rate limit streams so that large accounts have a smoother UI

This commit is contained in:
Sorunome 2021-07-31 12:31:31 +02:00
parent 8c20f7b00c
commit 493c88649d
No known key found for this signature in database
GPG Key ID: B19471D07FC9BE9C
4 changed files with 62 additions and 6 deletions

View File

@ -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<void>(
future: controller.waitForFirstSync(),

View File

@ -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(

View File

@ -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<bool> rateLimit(Duration t) {
final controller = StreamController<bool>();
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;
}
}

View File

@ -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