refactor: Remove unnecessary setState in ChatPage for better performance

This commit is contained in:
krille-chan 2023-12-23 07:38:45 +01:00
parent a615de889e
commit 63d7bef515
No known key found for this signature in database
3 changed files with 222 additions and 215 deletions

View file

@ -187,8 +187,6 @@ class ChatController extends State<ChatPageWithRoom> {
final int _loadHistoryCount = 100; final int _loadHistoryCount = 100;
String inputText = '';
String pendingText = ''; String pendingText = '';
bool showEmojiPicker = false; bool showEmojiPicker = false;
@ -260,7 +258,6 @@ class ChatController extends State<ChatPageWithRoom> {
if (!mounted) { if (!mounted) {
return; return;
} }
setReadMarker();
if (!scrollController.hasClients) return; if (!scrollController.hasClients) return;
if (timeline?.allowNewEvent == false || if (timeline?.allowNewEvent == false ||
scrollController.position.pixels > 0 && _scrolledUp == false) { scrollController.position.pixels > 0 && _scrolledUp == false) {
@ -285,7 +282,6 @@ class ChatController extends State<ChatPageWithRoom> {
final draft = prefs.getString('draft_$roomId'); final draft = prefs.getString('draft_$roomId');
if (draft != null && draft.isNotEmpty) { if (draft != null && draft.isNotEmpty) {
sendController.text = draft; sendController.text = draft;
setState(() => inputText = draft);
} }
} }
@ -472,7 +468,7 @@ class ChatController extends State<ChatPageWithRoom> {
); );
setState(() { setState(() {
inputText = pendingText; sendController.text = pendingText;
replyEvent = null; replyEvent = null;
editEvent = null; editEvent = null;
pendingText = ''; pendingText = '';
@ -938,7 +934,7 @@ class ChatController extends State<ChatPageWithRoom> {
); );
}); });
await loadTimelineFuture; await loadTimelineFuture;
setReadMarker(eventId: timeline!.events.first.eventId); setReadMarker();
} }
scrollController.jumpTo(0); scrollController.jumpTo(0);
} }
@ -1040,7 +1036,7 @@ class ChatController extends State<ChatPageWithRoom> {
setState(() { setState(() {
pendingText = sendController.text; pendingText = sendController.text;
editEvent = selectedEvents.first; editEvent = selectedEvents.first;
inputText = sendController.text = sendController.text =
editEvent!.getDisplayEvent(timeline!).calcLocalizedBodyFallback( editEvent!.getDisplayEvent(timeline!).calcLocalizedBodyFallback(
MatrixLocals(L10n.of(context)!), MatrixLocals(L10n.of(context)!),
withSenderNamePrefix: false, withSenderNamePrefix: false,
@ -1187,7 +1183,6 @@ class ChatController extends State<ChatPageWithRoom> {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
await prefs.setString('draft_$roomId', text); await prefs.setString('draft_$roomId', text);
}); });
setReadMarker();
if (text.endsWith(' ') && Matrix.of(context).hasComplexBundles) { if (text.endsWith(' ') && Matrix.of(context).hasComplexBundles) {
final clients = currentRoomBundle; final clients = currentRoomBundle;
for (final client in clients) { for (final client in clients) {
@ -1196,7 +1191,6 @@ class ChatController extends State<ChatPageWithRoom> {
text.toLowerCase() == '${prefix.toLowerCase()} ') { text.toLowerCase() == '${prefix.toLowerCase()} ') {
setSendingClient(client); setSendingClient(client);
setState(() { setState(() {
inputText = '';
sendController.text = ''; sendController.text = '';
}); });
return; return;
@ -1222,9 +1216,15 @@ class ChatController extends State<ChatPageWithRoom> {
); );
} }
} }
setState(() => inputText = text); if (_inputTextIsEmpty != text.isEmpty) {
setState(() {
_inputTextIsEmpty = text.isEmpty;
});
}
} }
bool _inputTextIsEmpty = true;
bool get isArchived => bool get isArchived =>
{Membership.leave, Membership.ban}.contains(room.membership); {Membership.leave, Membership.ban}.contains(room.membership);
@ -1291,7 +1291,7 @@ class ChatController extends State<ChatPageWithRoom> {
void cancelReplyEventAction() => setState(() { void cancelReplyEventAction() => setState(() {
if (editEvent != null) { if (editEvent != null) {
inputText = sendController.text = pendingText; sendController.text = pendingText;
pendingText = ''; pendingText = '';
} }
replyEvent = null; replyEvent = null;

View file

@ -105,7 +105,7 @@ class ChatInputRow extends StatelessWidget {
duration: FluffyThemes.animationDuration, duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve, curve: FluffyThemes.animationCurve,
height: 56, height: 56,
width: controller.inputText.isEmpty ? 56 : 0, width: controller.sendController.text.isEmpty ? 56 : 0,
alignment: Alignment.center, alignment: Alignment.center,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(), decoration: const BoxDecoration(),
@ -268,7 +268,7 @@ class ChatInputRow extends StatelessWidget {
), ),
), ),
if (PlatformInfos.platformCanRecord && if (PlatformInfos.platformCanRecord &&
controller.inputText.isEmpty) controller.sendController.text.isEmpty)
Container( Container(
height: 56, height: 56,
alignment: Alignment.center, alignment: Alignment.center,
@ -278,7 +278,8 @@ class ChatInputRow extends StatelessWidget {
onPressed: controller.voiceMessageAction, onPressed: controller.voiceMessageAction,
), ),
), ),
if (!PlatformInfos.isMobile || controller.inputText.isNotEmpty) if (!PlatformInfos.isMobile ||
controller.sendController.text.isNotEmpty)
Container( Container(
height: 56, height: 56,
alignment: Alignment.center, alignment: Alignment.center,

View file

@ -149,222 +149,228 @@ class ChatView extends StatelessWidget {
child: GestureDetector( child: GestureDetector(
onTapDown: (_) => controller.setReadMarker(), onTapDown: (_) => controller.setReadMarker(),
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
child: StreamBuilder( child: MouseRegion(
stream: controller.room.onUpdate.stream onEnter: (_) => controller.setReadMarker(),
.rateLimit(const Duration(seconds: 1)), child: StreamBuilder(
builder: (context, snapshot) => FutureBuilder( stream: controller.room.onUpdate.stream
future: controller.loadTimelineFuture, .rateLimit(const Duration(seconds: 1)),
builder: (BuildContext context, snapshot) { builder: (context, snapshot) => FutureBuilder(
return Scaffold( future: controller.loadTimelineFuture,
appBar: AppBar( builder: (BuildContext context, snapshot) {
actionsIconTheme: IconThemeData( return Scaffold(
color: controller.selectedEvents.isEmpty appBar: AppBar(
? null actionsIconTheme: IconThemeData(
: Theme.of(context).colorScheme.primary, color: controller.selectedEvents.isEmpty
? null
: Theme.of(context).colorScheme.primary,
),
leading: controller.selectMode
? IconButton(
icon: const Icon(Icons.close),
onPressed: controller.clearSelectedEvents,
tooltip: L10n.of(context)!.close,
color: Theme.of(context).colorScheme.primary,
)
: UnreadRoomsBadge(
filter: (r) => r.id != controller.roomId,
badgePosition: BadgePosition.topEnd(end: 8, top: 4),
child: const Center(child: BackButton()),
),
titleSpacing: 0,
title: ChatAppBarTitle(controller),
actions: _appBarActions(context),
), ),
leading: controller.selectMode floatingActionButton: controller.showScrollDownButton &&
? IconButton( controller.selectedEvents.isEmpty
icon: const Icon(Icons.close), ? Padding(
onPressed: controller.clearSelectedEvents, padding: const EdgeInsets.only(bottom: 56.0),
tooltip: L10n.of(context)!.close, child: FloatingActionButton(
color: Theme.of(context).colorScheme.primary, onPressed: controller.scrollDown,
heroTag: null,
mini: true,
child: const Icon(Icons.arrow_downward_outlined),
),
) )
: UnreadRoomsBadge( : null,
filter: (r) => r.id != controller.roomId, body: DropTarget(
badgePosition: BadgePosition.topEnd(end: 8, top: 4), onDragDone: controller.onDragDone,
child: const Center(child: BackButton()), onDragEntered: controller.onDragEntered,
), onDragExited: controller.onDragExited,
titleSpacing: 0, child: Stack(
title: ChatAppBarTitle(controller), children: <Widget>[
actions: _appBarActions(context), if (Matrix.of(context).wallpaper != null)
), Image.file(
floatingActionButton: controller.showScrollDownButton && Matrix.of(context).wallpaper!,
controller.selectedEvents.isEmpty width: double.infinity,
? Padding( height: double.infinity,
padding: const EdgeInsets.only(bottom: 56.0), fit: BoxFit.cover,
child: FloatingActionButton( filterQuality: FilterQuality.medium,
onPressed: controller.scrollDown, ),
heroTag: null, SafeArea(
mini: true, child: Column(
child: const Icon(Icons.arrow_downward_outlined), children: <Widget>[
), TombstoneDisplay(controller),
) if (scrollUpBannerEventId != null)
: null, Material(
body: DropTarget( color: Theme.of(context)
onDragDone: controller.onDragDone, .colorScheme
onDragEntered: controller.onDragEntered, .surfaceVariant,
onDragExited: controller.onDragExited, shape: Border(
child: Stack( bottom: BorderSide(
children: <Widget>[ width: 1,
if (Matrix.of(context).wallpaper != null) color: Theme.of(context).dividerColor,
Image.file( ),
Matrix.of(context).wallpaper!, ),
width: double.infinity, child: ListTile(
height: double.infinity, leading: IconButton(
fit: BoxFit.cover, color: Theme.of(context)
filterQuality: FilterQuality.medium, .colorScheme
), .onSurfaceVariant,
SafeArea( icon: const Icon(Icons.close),
child: Column( tooltip: L10n.of(context)!.close,
children: <Widget>[ onPressed: () {
TombstoneDisplay(controller), controller
if (scrollUpBannerEventId != null) .discardScrollUpBannerEventId();
Material( controller.setReadMarker();
color: Theme.of(context) },
.colorScheme ),
.surfaceVariant, title: Text(
shape: Border( L10n.of(context)!.jumpToLastReadMessage,
bottom: BorderSide( ),
width: 1, contentPadding:
color: Theme.of(context).dividerColor, const EdgeInsets.only(left: 8),
trailing: TextButton(
onPressed: () {
controller.scrollToEventId(
scrollUpBannerEventId,
);
controller
.discardScrollUpBannerEventId();
},
child: Text(L10n.of(context)!.jump),
),
), ),
), ),
child: ListTile( PinnedEvents(controller),
leading: IconButton( Expanded(
color: Theme.of(context) child: GestureDetector(
.colorScheme onTap: controller.clearSingleSelectedEvent,
.onSurfaceVariant, child: Builder(
icon: const Icon(Icons.close), builder: (context) {
tooltip: L10n.of(context)!.close, if (controller.timeline == null) {
onPressed: () { return const Center(
controller.discardScrollUpBannerEventId(); child: CircularProgressIndicator
controller.setReadMarker(); .adaptive(
strokeWidth: 2,
),
);
}
return ChatEventList(
controller: controller,
);
}, },
), ),
title: Text(
L10n.of(context)!.jumpToLastReadMessage,
),
contentPadding:
const EdgeInsets.only(left: 8),
trailing: TextButton(
onPressed: () {
controller.scrollToEventId(
scrollUpBannerEventId,
);
controller.discardScrollUpBannerEventId();
},
child: Text(L10n.of(context)!.jump),
),
), ),
), ),
PinnedEvents(controller), if (controller.room.canSendDefaultMessages &&
Expanded( controller.room.membership == Membership.join)
child: GestureDetector( Container(
onTap: controller.clearSingleSelectedEvent, margin: EdgeInsets.only(
child: Builder( bottom: bottomSheetPadding,
builder: (context) { left: bottomSheetPadding,
if (controller.timeline == null) { right: bottomSheetPadding,
return const Center(
child:
CircularProgressIndicator.adaptive(
strokeWidth: 2,
),
);
}
return ChatEventList(
controller: controller,
);
},
),
),
),
if (controller.room.canSendDefaultMessages &&
controller.room.membership == Membership.join)
Container(
margin: EdgeInsets.only(
bottom: bottomSheetPadding,
left: bottomSheetPadding,
right: bottomSheetPadding,
),
constraints: const BoxConstraints(
maxWidth: FluffyThemes.columnWidth * 2.5,
),
alignment: Alignment.center,
child: Material(
borderRadius: const BorderRadius.only(
bottomLeft:
Radius.circular(AppConfig.borderRadius),
bottomRight:
Radius.circular(AppConfig.borderRadius),
), ),
elevation: 4, constraints: const BoxConstraints(
shadowColor: Colors.black.withAlpha(64), maxWidth: FluffyThemes.columnWidth * 2.5,
clipBehavior: Clip.hardEdge, ),
color: Theme.of(context).brightness == alignment: Alignment.center,
Brightness.light child: Material(
? Colors.white borderRadius: const BorderRadius.only(
: Colors.black, bottomLeft: Radius.circular(
child: controller.room.isAbandonedDMRoom == AppConfig.borderRadius,
true ),
? Row( bottomRight: Radius.circular(
mainAxisAlignment: AppConfig.borderRadius,
MainAxisAlignment.spaceEvenly, ),
children: [ ),
TextButton.icon( elevation: 4,
style: TextButton.styleFrom( shadowColor: Colors.black.withAlpha(64),
padding: clipBehavior: Clip.hardEdge,
const EdgeInsets.all(16), color: Theme.of(context).brightness ==
foregroundColor: Brightness.light
Theme.of(context) ? Colors.white
.colorScheme : Colors.black,
.error, child: controller.room.isAbandonedDMRoom ==
true
? Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: [
TextButton.icon(
style: TextButton.styleFrom(
padding:
const EdgeInsets.all(16),
foregroundColor:
Theme.of(context)
.colorScheme
.error,
),
icon: const Icon(
Icons.archive_outlined,
),
onPressed: controller.leaveChat,
label: Text(
L10n.of(context)!.leave,
),
), ),
icon: const Icon( TextButton.icon(
Icons.archive_outlined, style: TextButton.styleFrom(
padding:
const EdgeInsets.all(16),
),
icon: const Icon(
Icons.forum_outlined,
),
onPressed:
controller.recreateChat,
label: Text(
L10n.of(context)!.reopenChat,
),
), ),
onPressed: controller.leaveChat, ],
label: Text( )
L10n.of(context)!.leave, : Column(
), mainAxisSize: MainAxisSize.min,
), children: [
TextButton.icon( const ConnectionStatusHeader(),
style: TextButton.styleFrom( ReactionsPicker(controller),
padding: ReplyDisplay(controller),
const EdgeInsets.all(16), ChatInputRow(controller),
), ChatEmojiPicker(controller),
icon: const Icon( ],
Icons.forum_outlined, ),
), ),
onPressed:
controller.recreateChat,
label: Text(
L10n.of(context)!.reopenChat,
),
),
],
)
: Column(
mainAxisSize: MainAxisSize.min,
children: [
const ConnectionStatusHeader(),
ReactionsPicker(controller),
ReplyDisplay(controller),
ChatInputRow(controller),
ChatEmojiPicker(controller),
],
),
), ),
), ],
],
),
),
if (controller.dragging)
Container(
color: Theme.of(context)
.scaffoldBackgroundColor
.withOpacity(0.9),
alignment: Alignment.center,
child: const Icon(
Icons.upload_outlined,
size: 100,
), ),
), ),
], if (controller.dragging)
Container(
color: Theme.of(context)
.scaffoldBackgroundColor
.withOpacity(0.9),
alignment: Alignment.center,
child: const Icon(
Icons.upload_outlined,
size: 100,
),
),
],
),
), ),
), );
); },
}, ),
), ),
), ),
), ),