Merge branch 'soru/rate-limit-rerender' into 'main'

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

See merge request famedly/fluffychat!464
This commit is contained in:
Krille Fear 2021-07-31 14:57:34 +00:00
commit 63ae83b852
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 'package:vrouter/vrouter.dart';
import '../../widgets/matrix.dart'; import '../../widgets/matrix.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import '../../utils/stream_extension.dart';
class ChatListView extends StatelessWidget { class ChatListView extends StatelessWidget {
final ChatListController controller; final ChatListController controller;
@ -161,7 +162,8 @@ class ChatListView extends StatelessWidget {
.client .client
.onSync .onSync
.stream .stream
.where((s) => s.hasRoomUpdate), .where((s) => s.hasRoomUpdate)
.rateLimit(Duration(seconds: 1)),
builder: (context, snapshot) { builder: (context, snapshot) {
return FutureBuilder<void>( return FutureBuilder<void>(
future: controller.waitForFirstSync(), 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:swipe_to_action/swipe_to_action.dart';
import 'package:vrouter/vrouter.dart'; import 'package:vrouter/vrouter.dart';
import '../../utils/stream_extension.dart';
class ChatView extends StatelessWidget { class ChatView extends StatelessWidget {
final ChatController controller; final ChatController controller;
@ -72,7 +74,8 @@ class ChatView extends StatelessWidget {
titleSpacing: 0, titleSpacing: 0,
title: controller.selectedEvents.isEmpty title: controller.selectedEvents.isEmpty
? StreamBuilder( ? StreamBuilder(
stream: controller.room.onUpdate.stream, stream: controller.room.onUpdate.stream
.rateLimit(Duration(milliseconds: 250)),
builder: (context, snapshot) => ListTile( builder: (context, snapshot) => ListTile(
leading: Avatar(controller.room.avatar, leading: Avatar(controller.room.avatar,
controller.room.displayname), controller.room.displayname),
@ -105,7 +108,8 @@ class ChatView extends StatelessWidget {
.stream .stream
.where((p) => .where((p) =>
p.senderId == p.senderId ==
controller.room.directChatMatrixID), controller.room.directChatMatrixID)
.rateLimit(Duration(seconds: 1)),
builder: (context, snapshot) => Text( builder: (context, snapshot) => Text(
controller.room controller.room
.getLocalizedStatus(context), .getLocalizedStatus(context),
@ -283,8 +287,10 @@ class ChatView extends StatelessWidget {
: Container() : Container()
: i == 0 : i == 0
? StreamBuilder( ? StreamBuilder(
stream: stream: controller
controller.room.onUpdate.stream, .room.onUpdate.stream
.rateLimit(Duration(
milliseconds: 250)),
builder: (_, __) { builder: (_, __) {
final seenByText = controller.room final seenByText = controller.room
.getLocalizedSeenByText( .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, curve: Curves.bounceInOut,
padding: EdgeInsets.symmetric(horizontal: 7), padding: EdgeInsets.symmetric(horizontal: 7),
height: unreadBubbleSize, height: unreadBubbleSize,
width: room.notificationCount == 0 && !room.isUnread ? 0 : null, width: room.notificationCount == 0 && !room.isUnread
? 0
: unreadBubbleSize,
decoration: BoxDecoration( decoration: BoxDecoration(
color: room.highlightCount > 0 color: room.highlightCount > 0
? Colors.red ? Colors.red