refactor: Enable avoid-returning-widgets lint

This commit is contained in:
Christian Kußowski 2026-02-24 09:09:49 +01:00
commit ab976db8e7
No known key found for this signature in database
GPG key ID: E067ECD60F1A0652
6 changed files with 497 additions and 473 deletions

View file

@ -34,120 +34,6 @@ class ChatView extends StatelessWidget {
const ChatView(this.controller, {super.key});
List<Widget> _appBarActions(BuildContext context) {
if (controller.selectMode) {
return [
if (controller.canEditSelectedEvents)
IconButton(
icon: const Icon(Icons.edit_outlined),
tooltip: L10n.of(context).edit,
onPressed: controller.editSelectedEventAction,
),
if (controller.selectedEvents.length == 1 &&
controller.activeThreadId == null &&
controller.room.canSendDefaultMessages)
IconButton(
icon: const Icon(Icons.message_outlined),
tooltip: L10n.of(context).replyInThread,
onPressed: () => controller.enterThread(
controller.selectedEvents.single.eventId,
),
),
IconButton(
icon: const Icon(Icons.copy_outlined),
tooltip: L10n.of(context).copyToClipboard,
onPressed: controller.copyEventsAction,
),
if (controller.canRedactSelectedEvents)
IconButton(
icon: const Icon(Icons.delete_outlined),
tooltip: L10n.of(context).redactMessage,
onPressed: controller.redactEventsAction,
),
if (controller.selectedEvents.length == 1)
PopupMenuButton<_EventContextAction>(
useRootNavigator: true,
onSelected: (action) {
switch (action) {
case _EventContextAction.info:
controller.showEventInfo();
controller.clearSelectedEvents();
break;
case _EventContextAction.report:
controller.reportEventAction();
break;
}
},
itemBuilder: (context) => [
if (controller.canPinSelectedEvents)
PopupMenuItem(
onTap: controller.pinEvent,
value: null,
child: Row(
mainAxisSize: .min,
children: [
const Icon(Icons.push_pin_outlined),
const SizedBox(width: 12),
Text(L10n.of(context).pinMessage),
],
),
),
if (controller.canSaveSelectedEvent)
PopupMenuItem(
onTap: () => controller.saveSelectedEvent(context),
value: null,
child: Row(
mainAxisSize: .min,
children: [
const Icon(Icons.download_outlined),
const SizedBox(width: 12),
Text(L10n.of(context).downloadFile),
],
),
),
PopupMenuItem(
value: _EventContextAction.info,
child: Row(
mainAxisSize: .min,
children: [
const Icon(Icons.info_outlined),
const SizedBox(width: 12),
Text(L10n.of(context).messageInfo),
],
),
),
if (controller.selectedEvents.single.status.isSent)
PopupMenuItem(
value: _EventContextAction.report,
child: Row(
mainAxisSize: .min,
children: [
const Icon(Icons.shield_outlined, color: Colors.red),
const SizedBox(width: 12),
Text(L10n.of(context).reportMessage),
],
),
),
],
),
];
} else if (!controller.room.isArchived) {
return [
if (AppSettings.experimentalVoip.value &&
Matrix.of(context).voipPlugin != null &&
controller.room.isDirectChat)
IconButton(
onPressed: controller.onPhoneButtonTap,
icon: const Icon(Icons.call_outlined),
tooltip: L10n.of(context).placeCall,
),
EncryptionButton(controller.room),
ChatSettingsPopupMenu(controller.room, true),
];
}
return [];
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
@ -238,7 +124,118 @@ class ChatView extends StatelessWidget {
),
titleSpacing: FluffyThemes.isColumnMode(context) ? 24 : 0,
title: ChatAppBarTitle(controller),
actions: _appBarActions(context),
actions: [
if (controller.selectMode) ...[
if (controller.canEditSelectedEvents)
IconButton(
icon: const Icon(Icons.edit_outlined),
tooltip: L10n.of(context).edit,
onPressed: controller.editSelectedEventAction,
),
if (controller.selectedEvents.length == 1 &&
controller.activeThreadId == null &&
controller.room.canSendDefaultMessages)
IconButton(
icon: const Icon(Icons.message_outlined),
tooltip: L10n.of(context).replyInThread,
onPressed: () => controller.enterThread(
controller.selectedEvents.single.eventId,
),
),
IconButton(
icon: const Icon(Icons.copy_outlined),
tooltip: L10n.of(context).copyToClipboard,
onPressed: controller.copyEventsAction,
),
if (controller.canRedactSelectedEvents)
IconButton(
icon: const Icon(Icons.delete_outlined),
tooltip: L10n.of(context).redactMessage,
onPressed: controller.redactEventsAction,
),
if (controller.selectedEvents.length == 1)
PopupMenuButton<_EventContextAction>(
useRootNavigator: true,
onSelected: (action) {
switch (action) {
case _EventContextAction.info:
controller.showEventInfo();
controller.clearSelectedEvents();
break;
case _EventContextAction.report:
controller.reportEventAction();
break;
}
},
itemBuilder: (context) => [
if (controller.canPinSelectedEvents)
PopupMenuItem(
onTap: controller.pinEvent,
value: null,
child: Row(
mainAxisSize: .min,
children: [
const Icon(Icons.push_pin_outlined),
const SizedBox(width: 12),
Text(L10n.of(context).pinMessage),
],
),
),
if (controller.canSaveSelectedEvent)
PopupMenuItem(
onTap: () =>
controller.saveSelectedEvent(context),
value: null,
child: Row(
mainAxisSize: .min,
children: [
const Icon(Icons.download_outlined),
const SizedBox(width: 12),
Text(L10n.of(context).downloadFile),
],
),
),
PopupMenuItem(
value: _EventContextAction.info,
child: Row(
mainAxisSize: .min,
children: [
const Icon(Icons.info_outlined),
const SizedBox(width: 12),
Text(L10n.of(context).messageInfo),
],
),
),
if (controller.selectedEvents.single.status.isSent)
PopupMenuItem(
value: _EventContextAction.report,
child: Row(
mainAxisSize: .min,
children: [
const Icon(
Icons.shield_outlined,
color: Colors.red,
),
const SizedBox(width: 12),
Text(L10n.of(context).reportMessage),
],
),
),
],
),
] else if (!controller.room.isArchived) ...[
if (AppSettings.experimentalVoip.value &&
Matrix.of(context).voipPlugin != null &&
controller.room.isDirectChat)
IconButton(
onPressed: controller.onPhoneButtonTap,
icon: const Icon(Icons.call_outlined),
tooltip: L10n.of(context).placeCall,
),
EncryptionButton(controller.room),
ChatSettingsPopupMenu(controller.room, true),
],
],
bottom: PreferredSize(
preferredSize: Size.fromHeight(appbarBottomHeight),
child: Column(

View file

@ -392,7 +392,10 @@ class InputBar extends StatelessWidget {
controller: controller,
focusNode: focusNode,
readOnly: readOnly,
contextMenuBuilder: (c, e) => markdownContextBuilder(c, e, controller),
contextMenuBuilder: (c, e) => MarkdownContextBuilder(
editableTextState: e,
controller: controller,
),
contentInsertionConfiguration: ContentInsertionConfiguration(
onContentInserted: (KeyboardInsertedContent content) {
final data = content.data;

View file

@ -339,235 +339,103 @@ class MyCallingPage extends State<Calling> {
}
*/
List<Widget> _buildActionButtons(bool isFloating) {
if (isFloating) {
return [];
}
final switchCameraButton = FloatingActionButton(
heroTag: 'switchCamera',
onPressed: _switchCamera,
backgroundColor: Colors.black45,
child: const Icon(Icons.switch_camera),
);
/*
var switchSpeakerButton = FloatingActionButton(
heroTag: 'switchSpeaker',
child: Icon(_speakerOn ? Icons.volume_up : Icons.volume_off),
onPressed: _switchSpeaker,
foregroundColor: Colors.black54,
backgroundColor: Theme.of(widget.context).backgroundColor,
);
*/
final hangupButton = FloatingActionButton(
heroTag: 'hangup',
onPressed: _hangUp,
tooltip: 'Hangup',
backgroundColor: _state == CallState.kEnded ? Colors.black45 : Colors.red,
child: const Icon(Icons.call_end),
);
final answerButton = FloatingActionButton(
heroTag: 'answer',
onPressed: _answerCall,
tooltip: 'Answer',
backgroundColor: Colors.green,
child: const Icon(Icons.phone),
);
final muteMicButton = FloatingActionButton(
heroTag: 'muteMic',
onPressed: _muteMic,
foregroundColor: isMicrophoneMuted ? Colors.black26 : Colors.white,
backgroundColor: isMicrophoneMuted ? Colors.white : Colors.black45,
child: Icon(isMicrophoneMuted ? Icons.mic_off : Icons.mic),
);
final screenSharingButton = FloatingActionButton(
heroTag: 'screenSharing',
onPressed: _screenSharing,
foregroundColor: isScreensharingEnabled ? Colors.black26 : Colors.white,
backgroundColor: isScreensharingEnabled ? Colors.white : Colors.black45,
child: const Icon(Icons.desktop_mac),
);
final holdButton = FloatingActionButton(
heroTag: 'hold',
onPressed: _remoteOnHold,
foregroundColor: isRemoteOnHold ? Colors.black26 : Colors.white,
backgroundColor: isRemoteOnHold ? Colors.white : Colors.black45,
child: const Icon(Icons.pause),
);
final muteCameraButton = FloatingActionButton(
heroTag: 'muteCam',
onPressed: _muteCamera,
foregroundColor: isLocalVideoMuted ? Colors.black26 : Colors.white,
backgroundColor: isLocalVideoMuted ? Colors.white : Colors.black45,
child: Icon(isLocalVideoMuted ? Icons.videocam_off : Icons.videocam),
);
switch (_state) {
case CallState.kRinging:
case CallState.kInviteSent:
case CallState.kCreateAnswer:
case CallState.kConnecting:
return call.isOutgoing
? <Widget>[hangupButton]
: <Widget>[answerButton, hangupButton];
case CallState.kConnected:
return <Widget>[
muteMicButton,
//switchSpeakerButton,
if (!voiceonly && !kIsWeb) switchCameraButton,
if (!voiceonly) muteCameraButton,
if (PlatformInfos.isMobile || PlatformInfos.isWeb)
screenSharingButton,
holdButton,
hangupButton,
];
case CallState.kEnded:
return <Widget>[hangupButton];
case CallState.kFledgling:
case CallState.kWaitLocalMedia:
case CallState.kCreateOffer:
case CallState.kEnding:
case null:
break;
}
return <Widget>[];
}
List<Widget> _buildContent(Orientation orientation, bool isFloating) {
final stackWidgets = <Widget>[];
final call = this.call;
if (call.callHasEnded) {
return stackWidgets;
}
if (call.localHold || call.remoteOnHold) {
var title = '';
if (call.localHold) {
title =
'${call.room.getLocalizedDisplayname(MatrixLocals(L10n.of(widget.context)))} held the call.';
} else if (call.remoteOnHold) {
title = 'You held the call.';
}
stackWidgets.add(
Center(
child: Column(
mainAxisAlignment: .center,
children: [
const Icon(Icons.pause, size: 48.0, color: Colors.white),
Text(
title,
style: const TextStyle(color: Colors.white, fontSize: 24.0),
),
],
),
),
);
return stackWidgets;
}
var primaryStream =
call.remoteScreenSharingStream ??
call.localScreenSharingStream ??
call.remoteUserMediaStream ??
call.localUserMediaStream;
if (!connected) {
primaryStream = call.localUserMediaStream;
}
if (primaryStream != null) {
stackWidgets.add(
Center(
child: _StreamView(
primaryStream,
mainView: true,
matrixClient: widget.client,
),
),
);
}
if (isFloating || !connected) {
return stackWidgets;
}
_resizeLocalVideo(orientation);
if (call.getRemoteStreams.isEmpty) {
return stackWidgets;
}
final secondaryStreamViews = <Widget>[];
if (call.remoteScreenSharingStream != null) {
final remoteUserMediaStream = call.remoteUserMediaStream;
secondaryStreamViews.add(
SizedBox(
width: _localVideoWidth,
height: _localVideoHeight,
child: _StreamView(
remoteUserMediaStream!,
matrixClient: widget.client,
),
),
);
secondaryStreamViews.add(const SizedBox(height: 10));
}
final localStream =
call.localUserMediaStream ?? call.localScreenSharingStream;
if (localStream != null && !isFloating) {
secondaryStreamViews.add(
SizedBox(
width: _localVideoWidth,
height: _localVideoHeight,
child: _StreamView(localStream, matrixClient: widget.client),
),
);
secondaryStreamViews.add(const SizedBox(height: 10));
}
if (call.localScreenSharingStream != null && !isFloating) {
secondaryStreamViews.add(
SizedBox(
width: _localVideoWidth,
height: _localVideoHeight,
child: _StreamView(
call.remoteUserMediaStream!,
matrixClient: widget.client,
),
),
);
secondaryStreamViews.add(const SizedBox(height: 10));
}
if (secondaryStreamViews.isNotEmpty) {
stackWidgets.add(
Container(
padding: const EdgeInsets.only(top: 20, bottom: 120),
alignment: Alignment.bottomRight,
child: Container(
width: _localVideoWidth,
margin: _localVideoMargin,
child: Column(children: secondaryStreamViews),
),
),
);
}
return stackWidgets;
}
@override
Widget build(BuildContext context) {
return PIPView(
builder: (context, isFloating) {
// Build action buttons
final switchCameraButton = FloatingActionButton(
heroTag: 'switchCamera',
onPressed: _switchCamera,
backgroundColor: Colors.black45,
child: const Icon(Icons.switch_camera),
);
final hangupButton = FloatingActionButton(
heroTag: 'hangup',
onPressed: _hangUp,
tooltip: 'Hangup',
backgroundColor: _state == CallState.kEnded
? Colors.black45
: Colors.red,
child: const Icon(Icons.call_end),
);
final answerButton = FloatingActionButton(
heroTag: 'answer',
onPressed: _answerCall,
tooltip: 'Answer',
backgroundColor: Colors.green,
child: const Icon(Icons.phone),
);
final muteMicButton = FloatingActionButton(
heroTag: 'muteMic',
onPressed: _muteMic,
foregroundColor: isMicrophoneMuted ? Colors.black26 : Colors.white,
backgroundColor: isMicrophoneMuted ? Colors.white : Colors.black45,
child: Icon(isMicrophoneMuted ? Icons.mic_off : Icons.mic),
);
final screenSharingButton = FloatingActionButton(
heroTag: 'screenSharing',
onPressed: _screenSharing,
foregroundColor: isScreensharingEnabled
? Colors.black26
: Colors.white,
backgroundColor: isScreensharingEnabled
? Colors.white
: Colors.black45,
child: const Icon(Icons.desktop_mac),
);
final holdButton = FloatingActionButton(
heroTag: 'hold',
onPressed: _remoteOnHold,
foregroundColor: isRemoteOnHold ? Colors.black26 : Colors.white,
backgroundColor: isRemoteOnHold ? Colors.white : Colors.black45,
child: const Icon(Icons.pause),
);
final muteCameraButton = FloatingActionButton(
heroTag: 'muteCam',
onPressed: _muteCamera,
foregroundColor: isLocalVideoMuted ? Colors.black26 : Colors.white,
backgroundColor: isLocalVideoMuted ? Colors.white : Colors.black45,
child: Icon(isLocalVideoMuted ? Icons.videocam_off : Icons.videocam),
);
late final List<Widget> actionButtons;
if (!isFloating) {
switch (_state) {
case CallState.kRinging:
case CallState.kInviteSent:
case CallState.kCreateAnswer:
case CallState.kConnecting:
actionButtons = call.isOutgoing
? <Widget>[hangupButton]
: <Widget>[answerButton, hangupButton];
break;
case CallState.kConnected:
actionButtons = <Widget>[
muteMicButton,
if (!voiceonly && !kIsWeb) switchCameraButton,
if (!voiceonly) muteCameraButton,
if (PlatformInfos.isMobile || PlatformInfos.isWeb)
screenSharingButton,
holdButton,
hangupButton,
];
break;
case CallState.kEnded:
actionButtons = <Widget>[hangupButton];
break;
case CallState.kFledgling:
case CallState.kWaitLocalMedia:
case CallState.kCreateOffer:
case CallState.kEnding:
case null:
actionButtons = <Widget>[];
break;
}
} else {
actionButtons = <Widget>[];
}
return Scaffold(
resizeToAvoidBottomInset: !isFloating,
floatingActionButtonLocation:
@ -577,16 +445,147 @@ class MyCallingPage extends State<Calling> {
height: 150.0,
child: Row(
mainAxisAlignment: .spaceAround,
children: _buildActionButtons(isFloating),
children: actionButtons,
),
),
body: OrientationBuilder(
builder: (BuildContext context, Orientation orientation) {
final stackWidgets = <Widget>[];
final callHasEnded = call.callHasEnded;
if (!callHasEnded) {
if (call.localHold || call.remoteOnHold) {
var title = '';
if (call.localHold) {
title =
'${call.room.getLocalizedDisplayname(MatrixLocals(L10n.of(widget.context)))} held the call.';
} else if (call.remoteOnHold) {
title = 'You held the call.';
}
stackWidgets.add(
Center(
child: Column(
mainAxisAlignment: .center,
children: [
const Icon(
Icons.pause,
size: 48.0,
color: Colors.white,
),
Text(
title,
style: const TextStyle(
color: Colors.white,
fontSize: 24.0,
),
),
],
),
),
);
} else {
var primaryStream =
call.remoteScreenSharingStream ??
call.localScreenSharingStream ??
call.remoteUserMediaStream ??
call.localUserMediaStream;
if (!connected) {
primaryStream = call.localUserMediaStream;
}
if (primaryStream != null) {
stackWidgets.add(
Center(
child: _StreamView(
primaryStream,
mainView: true,
matrixClient: widget.client,
),
),
);
}
if (!isFloating && connected) {
_resizeLocalVideo(orientation);
if (call.getRemoteStreams.isNotEmpty) {
final secondaryStreamViews = <Widget>[];
if (call.remoteScreenSharingStream != null) {
final remoteUserMediaStream =
call.remoteUserMediaStream;
secondaryStreamViews.add(
SizedBox(
width: _localVideoWidth,
height: _localVideoHeight,
child: _StreamView(
remoteUserMediaStream!,
matrixClient: widget.client,
),
),
);
secondaryStreamViews.add(const SizedBox(height: 10));
}
final localStream =
call.localUserMediaStream ??
call.localScreenSharingStream;
if (localStream != null && !isFloating) {
secondaryStreamViews.add(
SizedBox(
width: _localVideoWidth,
height: _localVideoHeight,
child: _StreamView(
localStream,
matrixClient: widget.client,
),
),
);
secondaryStreamViews.add(const SizedBox(height: 10));
}
if (call.localScreenSharingStream != null &&
!isFloating) {
secondaryStreamViews.add(
SizedBox(
width: _localVideoWidth,
height: _localVideoHeight,
child: _StreamView(
call.remoteUserMediaStream!,
matrixClient: widget.client,
),
),
);
secondaryStreamViews.add(const SizedBox(height: 10));
}
if (secondaryStreamViews.isNotEmpty) {
stackWidgets.add(
Container(
padding: const EdgeInsets.only(
top: 20,
bottom: 120,
),
alignment: Alignment.bottomRight,
child: Container(
width: _localVideoWidth,
margin: _localVideoMargin,
child: Column(children: secondaryStreamViews),
),
),
);
}
}
}
}
}
return Container(
decoration: const BoxDecoration(color: Colors.black87),
child: Stack(
children: [
..._buildContent(orientation, isFloating),
...stackWidgets,
if (!isFloating)
Positioned(
top: 24.0,