refactor: Update to Dart 3.10 with . shorthands

This commit is contained in:
Christian Kußowski 2025-11-30 12:54:06 +01:00
commit 1ea649f01e
No known key found for this signature in database
GPG key ID: E067ECD60F1A0652
167 changed files with 3351 additions and 3912 deletions

View file

@ -19,20 +19,21 @@ class AddWidgetTileView extends StatelessWidget {
CupertinoSegmentedControl(
groupValue: controller.widgetType,
padding: const EdgeInsets.all(8),
children: {
'm.etherpad': Text(L10n.of(context).widgetEtherpad),
'm.jitsi': Text(L10n.of(context).widgetJitsi),
'm.video': Text(L10n.of(context).widgetVideo),
'm.custom': Text(L10n.of(context).widgetCustom),
}.map(
(key, value) => MapEntry(
key,
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: value,
children:
{
'm.etherpad': Text(L10n.of(context).widgetEtherpad),
'm.jitsi': Text(L10n.of(context).widgetJitsi),
'm.video': Text(L10n.of(context).widgetVideo),
'm.custom': Text(L10n.of(context).widgetCustom),
}.map(
(key, value) => MapEntry(
key,
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: value,
),
),
),
),
),
onValueChanged: controller.setWidgetType,
),
Padding(

View file

@ -182,14 +182,14 @@ class ChatController extends State<ChatPageWithRoom>
}
void enterThread(String eventId) => setState(() {
activeThreadId = eventId;
selectedEvents.clear();
});
activeThreadId = eventId;
selectedEvents.clear();
});
void closeThread() => setState(() {
activeThreadId = null;
selectedEvents.clear();
});
activeThreadId = null;
selectedEvents.clear();
});
void recreateChat() async {
final room = this.room;
@ -266,9 +266,7 @@ class ChatController extends State<ChatPageWithRoom>
closeIconColor: theme.colorScheme.onErrorContainer,
content: Text(
L10n.of(context).otherPartyNotLoggedIn,
style: TextStyle(
color: theme.colorScheme.onErrorContainer,
),
style: TextStyle(color: theme.colorScheme.onErrorContainer),
),
showCloseIcon: true,
),
@ -306,11 +304,9 @@ class ChatController extends State<ChatPageWithRoom>
}
return KeyEventResult.handled;
} else if (evt.logicalKey.keyLabel == 'Enter' && evt is KeyDownEvent) {
final currentLineNum = sendController.text
.substring(
0,
sendController.selection.baseOffset,
)
final currentLineNum =
sendController.text
.substring(0, sendController.selection.baseOffset)
.split('\n')
.length -
1;
@ -357,10 +353,11 @@ class ChatController extends State<ChatPageWithRoom>
sendingClient = Matrix.of(context).client;
final lastEventThreadId =
room.lastEvent?.relationshipType == RelationshipTypes.thread
? room.lastEvent?.relationshipEventId
: null;
readMarkerEventId =
room.hasNewMessages ? lastEventThreadId ?? room.fullyRead : '';
? room.lastEvent?.relationshipEventId
: null;
readMarkerEventId = room.hasNewMessages
? lastEventThreadId ?? room.fullyRead
: '';
WidgetsBinding.instance.addObserver(this);
_tryLoadTimeline();
if (kIsWeb) {
@ -402,11 +399,11 @@ class ChatController extends State<ChatPageWithRoom>
var readMarkerEventIndex = readMarkerEventId.isEmpty
? -1
: timeline!.events
.filterByVisibleInGui(
exceptionEventId: readMarkerEventId,
threadId: activeThreadId,
)
.indexWhere((e) => e.eventId == readMarkerEventId);
.filterByVisibleInGui(
exceptionEventId: readMarkerEventId,
threadId: activeThreadId,
)
.indexWhere((e) => e.eventId == readMarkerEventId);
// Read marker is existing but not found in first events. Try a single
// requestHistory call before opening timeline on event context:
@ -441,12 +438,12 @@ class ChatController extends State<ChatPageWithRoom>
String? scrollUpBannerEventId;
void discardScrollUpBannerEventId() => setState(() {
scrollUpBannerEventId = null;
});
scrollUpBannerEventId = null;
});
void _showScrollUpMaterialBanner(String eventId) => setState(() {
scrollUpBannerEventId = eventId;
});
scrollUpBannerEventId = eventId;
});
void updateView() {
if (!mounted) return;
@ -463,9 +460,7 @@ class ChatController extends State<ChatPageWithRoom>
animateInEventIndex = i;
}
Future<void> _getTimeline({
String? eventContextId,
}) async {
Future<void> _getTimeline({String? eventContextId}) async {
await Matrix.of(context).client.roomsLoading;
await Matrix.of(context).client.accountDataLoading;
if (eventContextId != null &&
@ -534,12 +529,12 @@ class ChatController extends State<ChatPageWithRoom>
// ignore: unawaited_futures
_setReadMarkerFuture = timeline
.setReadMarker(
eventId: eventId,
public: AppSettings.sendPublicReadReceipts.value,
)
eventId: eventId,
public: AppSettings.sendPublicReadReceipts.value,
)
.then((_) {
_setReadMarkerFuture = null;
});
_setReadMarkerFuture = null;
});
if (eventId == null || eventId == timeline.room.lastEvent?.eventId) {
Matrix.of(context).backgroundPush?.cancelNotification(roomId);
}
@ -579,8 +574,8 @@ class ChatController extends State<ChatPageWithRoom>
}
void setActiveClient(Client c) => setState(() {
Matrix.of(context).setActiveClient(c);
});
Matrix.of(context).setActiveClient(c);
});
Future<void> send() async {
if (sendController.text.trim().isEmpty) return;
@ -627,11 +622,7 @@ class ChatController extends State<ChatPageWithRoom>
}
void sendFileAction({FileSelectorType type = FileSelectorType.any}) async {
final files = await selectFiles(
context,
allowMultiple: true,
type: type,
);
final files = await selectFiles(context, allowMultiple: true, type: type);
if (files.isEmpty) return;
await showAdaptiveDialog(
context: context,
@ -722,31 +713,26 @@ class ChatController extends State<ChatPageWithRoom>
setState(() {
replyEvent = null;
});
room.sendFileEvent(
file,
inReplyTo: replyEvent,
threadRootEventId: activeThreadId,
extraContent: {
'info': {
...file.info,
'duration': duration,
},
'org.matrix.msc3245.voice': {},
'org.matrix.msc1767.audio': {
'duration': duration,
'waveform': waveform,
},
},
).catchError((e) {
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text(
(e as Object).toLocalizedString(context),
),
),
);
return null;
});
room
.sendFileEvent(
file,
inReplyTo: replyEvent,
threadRootEventId: activeThreadId,
extraContent: {
'info': {...file.info, 'duration': duration},
'org.matrix.msc3245.voice': {},
'org.matrix.msc1767.audio': {
'duration': duration,
'waveform': waveform,
},
},
)
.catchError((e) {
scaffoldMessenger.showSnackBar(
SnackBar(content: Text((e as Object).toLocalizedString(context))),
);
return null;
});
return;
}
@ -785,7 +771,9 @@ class ChatController extends State<ChatPageWithRoom>
}
for (final event in selectedEvents) {
if (copyString.isNotEmpty) copyString += '\n\n';
copyString += event.getDisplayEvent(timeline!).calcLocalizedBodyFallback(
copyString += event
.getDisplayEvent(timeline!)
.calcLocalizedBodyFallback(
MatrixLocals(L10n.of(context)),
withSenderNamePrefix: true,
);
@ -813,14 +801,8 @@ class ChatController extends State<ChatPageWithRoom>
value: -100,
label: L10n.of(context).extremeOffensive,
),
AdaptiveModalAction(
value: -50,
label: L10n.of(context).offensive,
),
AdaptiveModalAction(
value: 0,
label: L10n.of(context).inoffensive,
),
AdaptiveModalAction(value: -50, label: L10n.of(context).offensive),
AdaptiveModalAction(value: 0, label: L10n.of(context).inoffensive),
],
);
if (score == null) return;
@ -835,11 +817,11 @@ class ChatController extends State<ChatPageWithRoom>
final result = await showFutureLoadingDialog(
context: context,
future: () => Matrix.of(context).client.reportEvent(
event.roomId!,
event.eventId,
reason: reason,
score: score,
),
event.roomId!,
event.eventId,
reason: reason,
score: score,
),
);
if (result.error != null) return;
setState(() {
@ -905,9 +887,10 @@ class ChatController extends State<ChatPageWithRoom>
return;
}
final room = client.getRoomById(roomId)!;
await Event.fromJson(event.toJson(), room).redactEvent(
reason: reason,
);
await Event.fromJson(
event.toJson(),
room,
).redactEvent(reason: reason);
}
} else {
await event.cancelSend();
@ -957,8 +940,9 @@ class ChatController extends State<ChatPageWithRoom>
!selectedEvents.first.status.isSent) {
return false;
}
return currentRoomBundle
.any((cl) => selectedEvents.first.senderId == cl!.userID);
return currentRoomBundle.any(
(cl) => selectedEvents.first.senderId == cl!.userID,
);
}
void forwardEventsAction() async {
@ -966,9 +950,9 @@ class ChatController extends State<ChatPageWithRoom>
final timeline = this.timeline;
if (timeline == null) return;
final forwardEvents = List<Event>.from(selectedEvents)
.map((event) => event.getDisplayEvent(timeline))
.toList();
final forwardEvents = List<Event>.from(
selectedEvents,
).map((event) => event.getDisplayEvent(timeline)).toList();
await showScaffoldDialog(
context: context,
@ -1004,29 +988,29 @@ class ChatController extends State<ChatPageWithRoom>
inputFocus.requestFocus();
}
void scrollToEventId(
String eventId, {
bool highlightEvent = true,
}) async {
final foundEvent =
timeline!.events.firstWhereOrNull((event) => event.eventId == eventId);
void scrollToEventId(String eventId, {bool highlightEvent = true}) async {
final foundEvent = timeline!.events.firstWhereOrNull(
(event) => event.eventId == eventId,
);
final eventIndex = foundEvent == null
? -1
: timeline!.events
.filterByVisibleInGui(
exceptionEventId: eventId,
threadId: activeThreadId,
)
.indexOf(foundEvent);
.filterByVisibleInGui(
exceptionEventId: eventId,
threadId: activeThreadId,
)
.indexOf(foundEvent);
if (eventIndex == -1) {
setState(() {
timeline = null;
_scrolledUp = false;
loadTimelineFuture = _getTimeline(eventContextId: eventId).onError(
ErrorReporter(context, 'Unable to load timeline after scroll to ID')
.onErrorCallback,
ErrorReporter(
context,
'Unable to load timeline after scroll to ID',
).onErrorCallback,
);
});
await loadTimelineFuture;
@ -1054,8 +1038,10 @@ class ChatController extends State<ChatPageWithRoom>
timeline = null;
_scrolledUp = false;
loadTimelineFuture = _getTimeline().onError(
ErrorReporter(context, 'Unable to load timeline after scroll down')
.onErrorCallback,
ErrorReporter(
context,
'Unable to load timeline after scroll down',
).onErrorCallback,
);
});
await loadTimelineFuture;
@ -1093,9 +1079,9 @@ class ChatController extends State<ChatPageWithRoom>
}
void clearSelectedEvents() => setState(() {
selectedEvents.clear();
showEmojiPicker = false;
});
selectedEvents.clear();
showEmojiPicker = false;
});
void clearSingleSelectedEvent() {
if (selectedEvents.length <= 1) {
@ -1115,12 +1101,13 @@ class ChatController extends State<ChatPageWithRoom>
setState(() {
pendingText = sendController.text;
editEvent = selectedEvents.first;
sendController.text =
editEvent!.getDisplayEvent(timeline!).calcLocalizedBodyFallback(
MatrixLocals(L10n.of(context)),
withSenderNamePrefix: false,
hideReply: true,
);
sendController.text = editEvent!
.getDisplayEvent(timeline!)
.calcLocalizedBodyFallback(
MatrixLocals(L10n.of(context)),
withSenderNamePrefix: false,
hideReply: true,
);
selectedEvents.clear();
});
inputFocus.requestFocus();
@ -1145,22 +1132,15 @@ class ChatController extends State<ChatPageWithRoom>
if (!mounted) return;
context.go('/rooms/${result.result!}');
await showFutureLoadingDialog(
context: context,
future: room.leave,
);
await showFutureLoadingDialog(context: context, future: room.leave);
}
void onSelectMessage(Event event) {
if (!event.redacted) {
if (selectedEvents.contains(event)) {
setState(
() => selectedEvents.remove(event),
);
setState(() => selectedEvents.remove(event));
} else {
setState(
() => selectedEvents.add(event),
);
setState(() => selectedEvents.add(event));
}
selectedEvents.sort(
(a, b) => a.originServerTs.compareTo(b.originServerTs),
@ -1243,7 +1223,8 @@ class ChatController extends State<ChatPageWithRoom>
void pinEvent() {
final pinnedEventIds = room.pinnedEventIds;
final selectedEventIds = selectedEvents.map((e) => e.eventId).toSet();
final unpin = selectedEventIds.length == 1 &&
final unpin =
selectedEventIds.length == 1 &&
pinnedEventIds.contains(selectedEventIds.single);
if (unpin) {
pinnedEventIds.removeWhere(selectedEventIds.contains);
@ -1353,20 +1334,20 @@ class ChatController extends State<ChatPageWithRoom>
try {
await voipPlugin!.voip.inviteToCall(room, callType);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.toLocalizedString(context))),
);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
}
}
void cancelReplyEventAction() => setState(() {
if (editEvent != null) {
sendController.text = pendingText;
pendingText = '';
}
replyEvent = null;
editEvent = null;
});
if (editEvent != null) {
sendController.text = pendingText;
pendingText = '';
}
replyEvent = null;
editEvent = null;
});
late final ValueNotifier<bool> _displayChatDetailsColumn;
@ -1382,38 +1363,30 @@ class ChatController extends State<ChatPageWithRoom>
final theme = Theme.of(context);
return Row(
children: [
Expanded(
child: ChatView(this),
),
Expanded(child: ChatView(this)),
ValueListenableBuilder(
valueListenable: _displayChatDetailsColumn,
builder: (context, displayChatDetailsColumn, _) =>
!FluffyThemes.isThreeColumnMode(context) ||
room.membership != Membership.join ||
!displayChatDetailsColumn
? const SizedBox(
height: double.infinity,
width: 0,
)
: Container(
width: FluffyThemes.columnWidth,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
border: Border(
left: BorderSide(
width: 1,
color: theme.dividerColor,
),
),
),
child: ChatDetails(
roomId: roomId,
embeddedCloseButton: IconButton(
icon: const Icon(Icons.close),
onPressed: toggleDisplayChatDetailsColumn,
),
),
room.membership != Membership.join ||
!displayChatDetailsColumn
? const SizedBox(height: double.infinity, width: 0)
: Container(
width: FluffyThemes.columnWidth,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
border: Border(
left: BorderSide(width: 1, color: theme.dividerColor),
),
),
child: ChatDetails(
roomId: roomId,
embeddedCloseButton: IconButton(
icon: const Icon(Icons.close),
onPressed: toggleDisplayChatDetailsColumn,
),
),
),
),
],
);

View file

@ -34,8 +34,8 @@ class ChatAppBarTitle extends StatelessWidget {
onTap: controller.isArchived
? null
: () => FluffyThemes.isThreeColumnMode(context)
? controller.toggleDisplayChatDetailsColumn()
: context.go('/rooms/${room.id}/details'),
? controller.toggleDisplayChatDetailsColumn()
: context.go('/rooms/${room.id}/details'),
child: Row(
children: [
Hero(
@ -51,22 +51,22 @@ class ChatAppBarTitle extends StatelessWidget {
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: .start,
children: [
Text(
room.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
),
style: const TextStyle(fontSize: 16),
),
StreamBuilder(
stream: room.client.onSyncStatus.stream,
builder: (context, snapshot) {
final status = room.client.onSyncStatus.value ??
final status =
room.client.onSyncStatus.value ??
const SyncStatusUpdate(SyncStatus.waitingForResponse);
final hide = FluffyThemes.isColumnMode(context) ||
final hide =
FluffyThemes.isColumnMode(context) ||
(room.client.onSync.value != null &&
status.status != SyncStatus.error &&
room.client.prevBatch != null);
@ -91,8 +91,9 @@ class ChatAppBarTitle extends StatelessWidget {
if (lastActiveTimestamp != null) {
return Text(
L10n.of(context).lastActiveAgo(
lastActiveTimestamp
.localizedTimeShort(context),
lastActiveTimestamp.localizedTimeShort(
context,
),
),
style: style,
);

View file

@ -52,8 +52,9 @@ class ChatEmojiPicker extends StatelessWidget {
),
categoryViewConfig: CategoryViewConfig(
backspaceColor: theme.colorScheme.primary,
iconColor:
theme.colorScheme.primary.withAlpha(128),
iconColor: theme.colorScheme.primary.withAlpha(
128,
),
iconColorSelected: theme.colorScheme.primary,
indicatorColor: theme.colorScheme.primary,
backgroundColor: theme.colorScheme.surface,

View file

@ -16,10 +16,7 @@ import 'package:fluffychat/utils/platform_infos.dart';
class ChatEventList extends StatelessWidget {
final ChatController controller;
const ChatEventList({
super.key,
required this.controller,
});
const ChatEventList({super.key, required this.controller});
@override
Widget build(BuildContext context) {
@ -30,10 +27,7 @@ class ChatEventList extends StatelessWidget {
}
final theme = Theme.of(context);
final colors = [
theme.secondaryBubbleColor,
theme.bubbleColor,
];
final colors = [theme.secondaryBubbleColor, theme.bubbleColor];
final horizontalPadding = FluffyThemes.isColumnMode(context) ? 8.0 : 0.0;
@ -83,11 +77,8 @@ class ChatEventList extends StatelessWidget {
);
}
return Column(
mainAxisSize: MainAxisSize.min,
children: [
SeenByRow(controller),
TypingIndicators(controller),
],
mainAxisSize: .min,
children: [SeenByRow(controller), TypingIndicators(controller)],
);
}
@ -101,8 +92,9 @@ class ChatEventList extends StatelessWidget {
!event.isCollapsedState && event.isVisibleInGui,
);
if (visibleIndex > timeline.events.length - 50) {
WidgetsBinding.instance
.addPostFrameCallback(controller.requestHistory);
WidgetsBinding.instance.addPostFrameCallback(
controller.requestHistory,
);
}
return Center(
child: AnimatedSwitcher(
@ -126,7 +118,8 @@ class ChatEventList extends StatelessWidget {
// The message at this index:
final event = events[i];
final animateIn = animateInEventIndex != null &&
final animateIn =
animateInEventIndex != null &&
timeline.events.length > animateInEventIndex &&
event == timeline.events[animateInEventIndex];
@ -134,10 +127,12 @@ class ChatEventList extends StatelessWidget {
final previousEvent = i > 0 ? events[i - 1] : null;
// Collapsed state event
final canExpand = event.isCollapsedState &&
final canExpand =
event.isCollapsedState &&
nextEvent?.isCollapsedState == true &&
previousEvent?.isCollapsedState != true;
final isCollapsed = event.isCollapsedState &&
final isCollapsed =
event.isCollapsedState &&
previousEvent?.isCollapsedState == true &&
!controller.expandedEventIds.contains(event.eventId);
@ -161,11 +156,12 @@ class ChatEventList extends StatelessWidget {
scrollToEventId: (String eventId) =>
controller.scrollToEventId(eventId),
longPressSelect: controller.selectedEvents.isNotEmpty,
selected: controller.selectedEvents
.any((e) => e.eventId == event.eventId),
selected: controller.selectedEvents.any(
(e) => e.eventId == event.eventId,
),
singleSelected:
controller.selectedEvents.singleOrNull?.eventId ==
event.eventId,
event.eventId,
onEdit: () => controller.editSelectedEventAction(),
timeline: timeline,
displayReadMarker:
@ -181,9 +177,9 @@ class ChatEventList extends StatelessWidget {
: null,
onExpand: canExpand
? () => controller.expandEventsFrom(
event,
!controller.expandedEventIds.contains(event.eventId),
)
event,
!controller.expandedEventIds.contains(event.eventId),
)
: null,
),
);

View file

@ -53,12 +53,13 @@ class ChatInputRow extends StatelessWidget {
);
}
return Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: .end,
mainAxisAlignment: .spaceBetween,
children: controller.selectMode
? <Widget>[
if (controller.selectedEvents
.every((event) => event.status == EventStatus.error))
if (controller.selectedEvents.every(
(event) => event.status == EventStatus.error,
))
SizedBox(
height: height,
child: TextButton(
@ -90,36 +91,36 @@ class ChatInputRow extends StatelessWidget {
),
controller.selectedEvents.length == 1
? controller.selectedEvents.first
.getDisplayEvent(controller.timeline!)
.status
.isSent
? SizedBox(
height: height,
child: TextButton(
style: selectedTextButtonStyle,
onPressed: controller.replyAction,
child: Row(
children: <Widget>[
Text(L10n.of(context).reply),
const Icon(Icons.keyboard_arrow_right),
],
.getDisplayEvent(controller.timeline!)
.status
.isSent
? SizedBox(
height: height,
child: TextButton(
style: selectedTextButtonStyle,
onPressed: controller.replyAction,
child: Row(
children: <Widget>[
Text(L10n.of(context).reply),
const Icon(Icons.keyboard_arrow_right),
],
),
),
),
)
: SizedBox(
height: height,
child: TextButton(
style: selectedTextButtonStyle,
onPressed: controller.sendAgainAction,
child: Row(
children: <Widget>[
Text(L10n.of(context).tryToSendAgain),
const SizedBox(width: 4),
const Icon(Icons.send_outlined, size: 16),
],
)
: SizedBox(
height: height,
child: TextButton(
style: selectedTextButtonStyle,
onPressed: controller.sendAgainAction,
child: Row(
children: <Widget>[
Text(L10n.of(context).tryToSendAgain),
const SizedBox(width: 4),
const Icon(Icons.send_outlined, size: 16),
],
),
),
),
)
)
: const SizedBox.shrink(),
]
: <Widget>[
@ -127,8 +128,9 @@ class ChatInputRow extends StatelessWidget {
AnimatedContainer(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
width:
controller.sendController.text.isNotEmpty ? 0 : height,
width: controller.sendController.text.isNotEmpty
? 0
: height,
height: height,
alignment: Alignment.center,
decoration: const BoxDecoration(),
@ -190,8 +192,9 @@ class ChatInputRow extends StatelessWidget {
theme.colorScheme.onPrimaryContainer,
foregroundColor:
theme.colorScheme.primaryContainer,
child:
const Icon(Icons.video_camera_back_outlined),
child: const Icon(
Icons.video_camera_back_outlined,
),
),
title: Text(L10n.of(context).sendVideo),
contentPadding: const EdgeInsets.all(0),
@ -270,19 +273,20 @@ class ChatInputRow extends StatelessWidget {
tooltip: L10n.of(context).emojis,
color: theme.colorScheme.onPrimaryContainer,
icon: PageTransitionSwitcher(
transitionBuilder: (
Widget child,
Animation<double> primaryAnimation,
Animation<double> secondaryAnimation,
) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.scaled,
fillColor: Colors.transparent,
child: child,
);
},
transitionBuilder:
(
Widget child,
Animation<double> primaryAnimation,
Animation<double> secondaryAnimation,
) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.scaled,
fillColor: Colors.transparent,
child: child,
);
},
child: Icon(
controller.showEmojiPicker
? Icons.keyboard
@ -313,9 +317,9 @@ class ChatInputRow extends StatelessWidget {
keyboardType: TextInputType.multiline,
textInputAction:
AppSettings.sendOnEnter.value == true &&
PlatformInfos.isMobile
? TextInputAction.send
: null,
PlatformInfos.isMobile
? TextInputAction.send
: null,
onSubmitted: controller.onInputBarSubmitted,
onSubmitImage: controller.sendImageFromClipBoard,
focusNode: controller.inputFocus,
@ -335,14 +339,18 @@ class ChatInputRow extends StatelessWidget {
filled: false,
),
onChanged: controller.onInputBarChanged,
suggestionEmojis: getDefaultEmojiLocale(
AppSettings.emojiSuggestionLocale.value.isNotEmpty
? Locale(AppSettings.emojiSuggestionLocale.value)
: Localizations.localeOf(context),
).fold(
[],
(emojis, category) => emojis..addAll(category.emoji),
),
suggestionEmojis:
getDefaultEmojiLocale(
AppSettings.emojiSuggestionLocale.value.isNotEmpty
? Locale(
AppSettings.emojiSuggestionLocale.value,
)
: Localizations.localeOf(context),
).fold(
[],
(emojis, category) =>
emojis..addAll(category.emoji),
),
),
),
),
@ -350,19 +358,21 @@ class ChatInputRow extends StatelessWidget {
height: height,
width: height,
alignment: Alignment.center,
child: PlatformInfos.platformCanRecord &&
child:
PlatformInfos.platformCanRecord &&
controller.sendController.text.isEmpty
? IconButton(
tooltip: L10n.of(context).voiceMessage,
onPressed: () =>
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
L10n.of(context)
.longPressToRecordVoiceMessage,
SnackBar(
content: Text(
L10n.of(
context,
).longPressToRecordVoiceMessage,
),
),
),
),
),
onLongPress: () => recordingViewModel
.startRecording(controller.room),
style: IconButton.styleFrom(
@ -394,9 +404,9 @@ class _ChatAccountPicker extends StatelessWidget {
const _ChatAccountPicker(this.controller);
void _popupMenuButtonSelected(String mxid, BuildContext context) {
final client = Matrix.of(context)
.currentBundle!
.firstWhere((cl) => cl!.userID == mxid, orElse: () => null);
final client = Matrix.of(
context,
).currentBundle!.firstWhere((cl) => cl!.userID == mxid, orElse: () => null);
if (client == null) {
Logs().w('Attempted to switch to a non-existing client $mxid');
return;
@ -423,7 +433,8 @@ class _ChatAccountPicker extends StatelessWidget {
builder: (context, snapshot) => ListTile(
leading: Avatar(
mxContent: snapshot.data?.avatarUrl,
name: snapshot.data?.displayName ??
name:
snapshot.data?.displayName ??
client.userID!.localpart,
size: 20,
),
@ -436,7 +447,8 @@ class _ChatAccountPicker extends StatelessWidget {
.toList(),
child: Avatar(
mxContent: snapshot.data?.avatarUrl,
name: snapshot.data?.displayName ??
name:
snapshot.data?.displayName ??
Matrix.of(context).client.userID!.localpart,
size: 20,
),

View file

@ -49,8 +49,9 @@ class ChatView extends StatelessWidget {
IconButton(
icon: const Icon(Icons.message_outlined),
tooltip: L10n.of(context).replyInThread,
onPressed: () => controller
.enterThread(controller.selectedEvents.single.eventId),
onPressed: () => controller.enterThread(
controller.selectedEvents.single.eventId,
),
),
IconButton(
icon: const Icon(Icons.copy_outlined),
@ -83,7 +84,7 @@ class ChatView extends StatelessWidget {
onTap: controller.pinEvent,
value: null,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
const Icon(Icons.push_pin_outlined),
const SizedBox(width: 12),
@ -96,7 +97,7 @@ class ChatView extends StatelessWidget {
onTap: () => controller.saveSelectedEvent(context),
value: null,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
const Icon(Icons.download_outlined),
const SizedBox(width: 12),
@ -107,7 +108,7 @@ class ChatView extends StatelessWidget {
PopupMenuItem(
value: _EventContextAction.info,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
const Icon(Icons.info_outlined),
const SizedBox(width: 12),
@ -119,12 +120,9 @@ class ChatView extends StatelessWidget {
PopupMenuItem(
value: _EventContextAction.report,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
const Icon(
Icons.shield_outlined,
color: Colors.red,
),
const Icon(Icons.shield_outlined, color: Colors.red),
const SizedBox(width: 12),
Text(L10n.of(context).reportMessage),
],
@ -166,7 +164,8 @@ class ChatView extends StatelessWidget {
final accountConfig = Matrix.of(context).client.applicationAccountConfig;
return PopScope(
canPop: controller.selectedEvents.isEmpty &&
canPop:
controller.selectedEvents.isEmpty &&
!controller.showEmojiPicker &&
controller.activeThreadId == null,
onPopInvokedWithResult: (pop, _) async {
@ -207,8 +206,8 @@ class ChatView extends StatelessWidget {
),
backgroundColor: controller.selectedEvents.isEmpty
? controller.activeThreadId != null
? theme.colorScheme.secondaryContainer
: null
? theme.colorScheme.secondaryContainer
: null
: theme.colorScheme.tertiaryContainer,
automaticallyImplyLeading: false,
leading: controller.selectMode
@ -219,36 +218,31 @@ class ChatView extends StatelessWidget {
color: theme.colorScheme.onTertiaryContainer,
)
: activeThreadId != null
? IconButton(
icon: const Icon(Icons.close),
onPressed: controller.closeThread,
tooltip: L10n.of(context).backToMainChat,
color: theme.colorScheme.onSecondaryContainer,
)
: FluffyThemes.isColumnMode(context)
? null
: StreamBuilder<Object>(
stream: Matrix.of(context)
.client
.onSync
.stream
.where(
(syncUpdate) => syncUpdate.hasRoomUpdate,
),
builder: (context, _) => UnreadRoomsBadge(
filter: (r) => r.id != controller.roomId,
badgePosition:
BadgePosition.topEnd(end: 8, top: 4),
child: const Center(child: BackButton()),
),
),
? IconButton(
icon: const Icon(Icons.close),
onPressed: controller.closeThread,
tooltip: L10n.of(context).backToMainChat,
color: theme.colorScheme.onSecondaryContainer,
)
: FluffyThemes.isColumnMode(context)
? null
: StreamBuilder<Object>(
stream: Matrix.of(context).client.onSync.stream.where(
(syncUpdate) => syncUpdate.hasRoomUpdate,
),
builder: (context, _) => UnreadRoomsBadge(
filter: (r) => r.id != controller.roomId,
badgePosition: BadgePosition.topEnd(end: 8, top: 4),
child: const Center(child: BackButton()),
),
),
titleSpacing: FluffyThemes.isColumnMode(context) ? 24 : 0,
title: ChatAppBarTitle(controller),
actions: _appBarActions(context),
bottom: PreferredSize(
preferredSize: Size.fromHeight(appbarBottomHeight),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
PinnedEvents(controller),
if (activeThreadId != null)
@ -285,9 +279,7 @@ class ChatView extends StatelessWidget {
title: L10n.of(context).jumpToLastReadMessage,
trailing: TextButton(
onPressed: () {
controller.scrollToEventId(
scrollUpBannerEventId,
);
controller.scrollToEventId(scrollUpBannerEventId);
controller.discardScrollUpBannerEventId();
},
child: Text(L10n.of(context).jump),
@ -299,7 +291,8 @@ class ChatView extends StatelessWidget {
),
floatingActionButtonLocation:
FloatingActionButtonLocation.miniCenterFloat,
floatingActionButton: controller.showScrollDownButton &&
floatingActionButton:
controller.showScrollDownButton &&
controller.selectedEvents.isEmpty
? Padding(
padding: const EdgeInsets.only(bottom: 56.0),
@ -348,10 +341,7 @@ class ChatView extends StatelessWidget {
),
),
if (controller.showScrollDownButton)
Divider(
height: 1,
color: theme.dividerColor,
),
Divider(height: 1, color: theme.dividerColor),
if (controller.room.isExtinct)
Container(
margin: EdgeInsets.all(bottomSheetPadding),
@ -380,14 +370,11 @@ class ChatView extends StatelessWidget {
),
child: controller.room.isAbandonedDMRoom == true
? Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
mainAxisAlignment: .spaceEvenly,
children: [
TextButton.icon(
style: TextButton.styleFrom(
padding: const EdgeInsets.all(
16,
),
padding: const EdgeInsets.all(16),
foregroundColor:
theme.colorScheme.error,
),
@ -395,15 +382,11 @@ class ChatView extends StatelessWidget {
Icons.archive_outlined,
),
onPressed: controller.leaveChat,
label: Text(
L10n.of(context).leave,
),
label: Text(L10n.of(context).leave),
),
TextButton.icon(
style: TextButton.styleFrom(
padding: const EdgeInsets.all(
16,
),
padding: const EdgeInsets.all(16),
),
icon: const Icon(
Icons.forum_outlined,
@ -416,7 +399,7 @@ class ChatView extends StatelessWidget {
],
)
: Column(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
ReplyDisplay(controller),
ChatInputRow(controller),
@ -432,10 +415,7 @@ class ChatView extends StatelessWidget {
Container(
color: theme.scaffoldBackgroundColor.withAlpha(230),
alignment: Alignment.center,
child: const Icon(
Icons.upload_outlined,
size: 100,
),
child: const Icon(Icons.upload_outlined, size: 100),
),
],
),

View file

@ -15,11 +15,9 @@ class EncryptionButton extends StatelessWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
return StreamBuilder<SyncUpdate>(
stream: Matrix.of(context)
.client
.onSync
.stream
.where((s) => s.deviceLists != null),
stream: Matrix.of(
context,
).client.onSync.stream.where((s) => s.deviceLists != null),
builder: (context, snapshot) {
final shouldBeEncrypted = room.joinRules != JoinRules.public;
return FutureBuilder<EncryptionHealthState>(

View file

@ -12,21 +12,16 @@ import 'package:fluffychat/widgets/avatar.dart';
extension EventInfoDialogExtension on Event {
void showInfoDialog(BuildContext context) => showAdaptiveBottomSheet(
context: context,
builder: (context) =>
EventInfoDialog(l10n: L10n.of(context), event: this),
);
context: context,
builder: (context) => EventInfoDialog(l10n: L10n.of(context), event: this),
);
}
class EventInfoDialog extends StatelessWidget {
final Event event;
final L10n l10n;
const EventInfoDialog({
required this.event,
required this.l10n,
super.key,
});
const EventInfoDialog({required this.event, required this.l10n, super.key});
String prettyJson(MatrixEvent event) {
const decoder = JsonDecoder();
@ -79,9 +74,7 @@ class EventInfoDialog extends StatelessWidget {
scrollDirection: Axis.horizontal,
child: SelectableText(
prettyJson(MatrixEvent.fromJson(event.toJson())),
style: TextStyle(
color: theme.colorScheme.onSurface,
),
style: TextStyle(color: theme.colorScheme.onSurface),
),
),
),
@ -98,9 +91,7 @@ class EventInfoDialog extends StatelessWidget {
scrollDirection: Axis.horizontal,
child: SelectableText(
prettyJson(originalSource),
style: TextStyle(
color: theme.colorScheme.onSurface,
),
style: TextStyle(color: theme.colorScheme.onSurface),
),
),
),

View file

@ -100,12 +100,13 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
onPressed: () {
audioPlayer.pause();
audioPlayer.dispose();
matrix.voiceMessageEventId.value =
matrix.audioPlayer = null;
matrix.voiceMessageEventId.value = matrix.audioPlayer =
null;
WidgetsBinding.instance.addPostFrameCallback((_) {
ScaffoldMessenger.of(matrix.context)
.clearMaterialBanners();
ScaffoldMessenger.of(
matrix.context,
).clearMaterialBanners();
});
},
icon: const Icon(Icons.close_outlined),
@ -128,8 +129,8 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
});
final currentPlayer =
matrix.voiceMessageEventId.value != widget.event.eventId
? null
: matrix.audioPlayer;
? null
: matrix.audioPlayer;
if (currentPlayer != null) {
if (currentPlayer.isAtEndPosition) {
currentPlayer.seek(Duration.zero);
@ -158,8 +159,9 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
? (progress) {
final progressPercentage = progress / fileSize;
setState(() {
_downloadProgress =
progressPercentage < 1 ? progressPercentage : null;
_downloadProgress = progressPercentage < 1
? progressPercentage
: null;
});
}
: null,
@ -190,11 +192,9 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
});
} catch (e, s) {
Logs().v('Could not download audio file', e, s);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toLocalizedString(context)),
),
);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
rethrow;
}
if (!context.mounted) return;
@ -209,9 +209,8 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
}
audioPlayer.play().onError(
ErrorReporter(context, 'Unable to play audio message')
.onErrorCallback,
);
ErrorReporter(context, 'Unable to play audio message').onErrorCallback,
);
}
void _toggleSpeed() async {
@ -289,8 +288,9 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
return ValueListenableBuilder(
valueListenable: matrix.voiceMessageEventId,
builder: (context, eventId, _) {
final audioPlayer =
eventId != widget.event.eventId ? null : matrix.audioPlayer;
final audioPlayer = eventId != widget.event.eventId
? null
: matrix.audioPlayer;
final fileDescription = widget.event.fileDescription;
@ -317,15 +317,15 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
return Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: .min,
crossAxisAlignment: .start,
children: [
ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: FluffyThemes.columnWidth,
),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: <Widget>[
SizedBox(
width: buttonSize,
@ -366,9 +366,11 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
),
child: Row(
children: [
for (var i = 0;
i < AudioPlayerWidget.wavesCount;
i++)
for (
var i = 0;
i < AudioPlayerWidget.wavesCount;
i++
)
Expanded(
child: Container(
height: 32,
@ -376,13 +378,14 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
child: Container(
margin:
const EdgeInsets.symmetric(
horizontal: 1,
),
horizontal: 1,
),
decoration: BoxDecoration(
color: i < wavePosition
? widget.color
: widget.color
.withAlpha(128),
: widget.color.withAlpha(
128,
),
borderRadius:
BorderRadius.circular(64),
),
@ -396,7 +399,8 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
SizedBox(
height: 32,
child: Slider(
thumbColor: widget.event.senderId ==
thumbColor:
widget.event.senderId ==
widget.event.room.client.userID
? theme.colorScheme.onPrimary
: theme.colorScheme.primary,
@ -425,10 +429,7 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
width: 36,
child: Text(
statusText,
style: TextStyle(
color: widget.color,
fontSize: 12,
),
style: TextStyle(color: widget.color, fontSize: 12),
),
),
const SizedBox(width: 8),
@ -442,11 +443,13 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
),
secondChild: Material(
color: widget.color.withAlpha(64),
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
child: InkWell(
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
onTap: _toggleSpeed,
child: SizedBox(
width: 32,
@ -481,8 +484,9 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
),
child: Linkify(
text: fileDescription,
textScaleFactor:
MediaQuery.textScalerOf(context).scale(1),
textScaleFactor: MediaQuery.textScalerOf(
context,
).scale(1),
style: TextStyle(
color: widget.color,
fontSize: widget.fontSize,

View file

@ -37,13 +37,10 @@ class _CuteContentState extends State<CuteContent> {
return GestureDetector(
onTap: addOverlay,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: .min,
mainAxisAlignment: .center,
children: [
Text(
widget.event.text,
style: const TextStyle(fontSize: 150),
),
Text(widget.event.text, style: const TextStyle(fontSize: 150)),
if (label != null) Text(label),
],
),
@ -112,10 +109,7 @@ class _CuteEventOverlayState extends State<CuteEventOverlay>
with TickerProviderStateMixin {
final List<Size> items = List.generate(
50,
(index) => Size(
Random().nextDouble(),
4 + (Random().nextDouble() * 4),
),
(index) => Size(Random().nextDouble(), 4 + (Random().nextDouble() * 4)),
);
AnimationController? controller;
@ -150,14 +144,13 @@ class _CuteEventOverlayState extends State<CuteEventOverlay>
.map(
(position) => Positioned(
left: position.width * width,
bottom: (height *
bottom:
(height *
.25 *
position.height *
(controller?.value ?? 0)) -
_CuteOverlayContent.size,
child: _CuteOverlayContent(
emoji: widget.emoji,
),
child: _CuteOverlayContent(emoji: widget.emoji),
),
)
.toList(),
@ -186,10 +179,7 @@ class _CuteOverlayContent extends StatelessWidget {
Widget build(BuildContext context) {
return SizedBox.square(
dimension: size,
child: Text(
emoji,
style: const TextStyle(fontSize: 48),
),
child: Text(emoji, style: const TextStyle(fontSize: 48)),
);
}
}

View file

@ -141,7 +141,8 @@ class HtmlMessage extends StatelessWidget {
if (node is! dom.Element) {
return TextSpan(text: node.text);
}
final style = atomOneDarkTheme[node.className.split('-').last] ??
final style =
atomOneDarkTheme[node.className.split('-').last] ??
atomOneDarkTheme['root'];
return TextSpan(
@ -151,11 +152,7 @@ class HtmlMessage extends StatelessWidget {
}
/// Transforms a Node to an InlineSpan.
InlineSpan _renderHtml(
dom.Node node,
BuildContext context, {
int depth = 1,
}) {
InlineSpan _renderHtml(dom.Node node, BuildContext context, {int depth = 1}) {
// We must not render elements nested more than 100 elements deep:
if (depth >= 100) return const TextSpan();
@ -245,9 +242,9 @@ class HtmlMessage extends StatelessWidget {
final isCheckbox = node.className == 'task-list-item';
final checkboxIndex = isCheckbox
? node.rootElement
.getElementsByClassName('task-list-item')
.indexOf(node) +
1
.getElementsByClassName('task-list-item')
.indexOf(node) +
1
: null;
final checkedByReaction = !isCheckbox
? null
@ -283,7 +280,8 @@ class HtmlMessage extends StatelessWidget {
activeColor: textColor.withAlpha(64),
value:
staticallyChecked || checkedByReaction != null,
onChanged: eventId == null ||
onChanged:
eventId == null ||
checkboxIndex == null ||
staticallyChecked ||
!room.canSendDefaultMessages ||
@ -292,25 +290,21 @@ class HtmlMessage extends StatelessWidget {
room.client.userID)
? null
: (_) => showFutureLoadingDialog(
context: context,
future: () => checkedByReaction != null
? room.redactEvent(
checkedByReaction.eventId,
)
: room.checkCheckbox(
eventId,
checkboxIndex,
),
),
context: context,
future: () => checkedByReaction != null
? room.redactEvent(
checkedByReaction.eventId,
)
: room.checkCheckbox(
eventId,
checkboxIndex,
),
),
),
),
),
),
..._renderWithLineBreaks(
node.nodes,
context,
depth: depth,
),
..._renderWithLineBreaks(node.nodes, context, depth: depth),
],
style: TextStyle(fontSize: fontSize, color: textColor),
),
@ -322,12 +316,7 @@ class HtmlMessage extends StatelessWidget {
child: Container(
padding: const EdgeInsets.only(left: 8.0),
decoration: BoxDecoration(
border: Border(
left: BorderSide(
color: textColor,
width: 5,
),
),
border: Border(left: BorderSide(color: textColor, width: 5)),
),
child: Text.rich(
TextSpan(
@ -347,7 +336,8 @@ class HtmlMessage extends StatelessWidget {
);
case 'code':
final isInline = node.parent?.localName != 'pre';
final lang = node.className
final lang =
node.className
.split(' ')
.singleWhereOrNull(
(className) => className.startsWith('language-'),
@ -355,8 +345,9 @@ class HtmlMessage extends StatelessWidget {
?.split('language-')
.last ??
'md';
final highlightedHtml =
highlight.parse(node.text, language: lang).toHtml();
final highlightedHtml = highlight
.parse(node.text, language: lang)
.toHtml();
final element = parser.parse(highlightedHtml).body;
if (element == null) {
return const TextSpan(text: 'Unable to render code block!');
@ -372,14 +363,9 @@ class HtmlMessage extends StatelessWidget {
child: Padding(
padding: isInline
? const EdgeInsets.symmetric(horizontal: 4.0)
: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 4.0,
),
: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
child: Text.rich(
TextSpan(
children: [_renderCodeBlockNode(element)],
),
TextSpan(children: [_renderCodeBlockNode(element)]),
selectionColor: hightlightTextColor.withAlpha(128),
),
),
@ -448,10 +434,7 @@ class HtmlMessage extends StatelessWidget {
),
],
),
style: TextStyle(
fontSize: fontSize,
color: textColor,
),
style: TextStyle(fontSize: fontSize, color: textColor),
),
),
),
@ -489,17 +472,13 @@ class HtmlMessage extends StatelessWidget {
default:
return TextSpan(
style: switch (node.localName) {
'body' => TextStyle(
fontSize: fontSize,
color: textColor,
),
'body' => TextStyle(fontSize: fontSize, color: textColor),
'a' => linkStyle,
'strong' => const TextStyle(fontWeight: FontWeight.bold),
'em' || 'i' => const TextStyle(fontStyle: FontStyle.italic),
'del' ||
's' ||
'strikethrough' =>
const TextStyle(decoration: TextDecoration.lineThrough),
'del' || 's' || 'strikethrough' => const TextStyle(
decoration: TextDecoration.lineThrough,
),
'u' => const TextStyle(decoration: TextDecoration.underline),
'h1' => TextStyle(fontSize: fontSize * 1.6, height: 2),
'h2' => TextStyle(fontSize: fontSize * 1.5, height: 2),
@ -508,22 +487,19 @@ class HtmlMessage extends StatelessWidget {
'h5' => TextStyle(fontSize: fontSize * 1.2, height: 1.75),
'h6' => TextStyle(fontSize: fontSize * 1.1, height: 1.5),
'span' => TextStyle(
color: node.attributes['color']?.hexToColor ??
node.attributes['data-mx-color']?.hexToColor ??
textColor,
backgroundColor:
node.attributes['data-mx-bg-color']?.hexToColor,
),
'sup' =>
const TextStyle(fontFeatures: [FontFeature.superscripts()]),
color:
node.attributes['color']?.hexToColor ??
node.attributes['data-mx-color']?.hexToColor ??
textColor,
backgroundColor: node.attributes['data-mx-bg-color']?.hexToColor,
),
'sup' => const TextStyle(
fontFeatures: [FontFeature.superscripts()],
),
'sub' => const TextStyle(fontFeatures: [FontFeature.subscripts()]),
_ => null,
},
children: _renderWithLineBreaks(
node.nodes,
context,
depth: depth,
),
children: _renderWithLineBreaks(node.nodes, context, depth: depth),
);
}
}
@ -533,10 +509,7 @@ class HtmlMessage extends StatelessWidget {
final element = parser.parse(html).body ?? dom.Element.html('');
return Text.rich(
_renderHtml(element, context),
style: TextStyle(
fontSize: fontSize,
color: textColor,
),
style: TextStyle(fontSize: fontSize, color: textColor),
maxLines: limitHeight ? 64 : null,
overflow: TextOverflow.fade,
selectionColor: textColor.withAlpha(128),
@ -568,13 +541,9 @@ class MatrixPill extends StatelessWidget {
splashColor: Colors.transparent,
onTap: UrlLauncher(outerContext, uri).launchUrl,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
Avatar(
mxContent: avatar,
name: name,
size: 16,
),
Avatar(mxContent: avatar, name: name, size: 16),
const SizedBox(width: 6),
Text(
name,

View file

@ -46,8 +46,8 @@ class ImageBubble extends StatelessWidget {
Widget _buildPlaceholder(BuildContext context) {
final String blurHashString =
event.infoMap['xyz.amorgan.blurhash'] is String
? event.infoMap['xyz.amorgan.blurhash']
: 'LEHV6nWB2yk8pyo0adR*.7kCMdnj';
? event.infoMap['xyz.amorgan.blurhash']
: 'LEHV6nWB2yk8pyo0adR*.7kCMdnj';
return SizedBox(
width: width,
height: height,
@ -68,11 +68,8 @@ class ImageBubble extends StatelessWidget {
if (!tapToView) return;
showDialog(
context: context,
builder: (_) => ImageViewer(
event,
timeline: timeline,
outerContext: context,
),
builder: (_) =>
ImageViewer(event, timeline: timeline, outerContext: context),
);
}
@ -94,7 +91,7 @@ class ImageBubble extends StatelessWidget {
}
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
spacing: 8,
children: [
Material(
@ -131,22 +128,21 @@ class ImageBubble extends StatelessWidget {
SizedBox(
width: width,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Linkify(
text: fileDescription,
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
style: TextStyle(
color: textColor,
fontSize: AppSettings.fontSizeFactor.value *
fontSize:
AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
),
options: const LinkifyOptions(humanize: false),
linkStyle: TextStyle(
color: linkColor,
fontSize: AppSettings.fontSizeFactor.value *
fontSize:
AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
decoration: TextDecoration.underline,
decorationColor: linkColor,

View file

@ -107,10 +107,12 @@ class Message extends StatelessWidget {
final alignment = ownMessage ? Alignment.topRight : Alignment.topLeft;
var color = theme.colorScheme.surfaceContainerHigh;
final displayTime = event.type == EventTypes.RoomCreate ||
final displayTime =
event.type == EventTypes.RoomCreate ||
nextEvent == null ||
!event.originServerTs.sameEnvironment(nextEvent!.originServerTs);
final nextEventSameSender = nextEvent != null &&
final nextEventSameSender =
nextEvent != null &&
{
EventTypes.Message,
EventTypes.Sticker,
@ -119,7 +121,8 @@ class Message extends StatelessWidget {
nextEvent!.senderId == event.senderId &&
!displayTime;
final previousEventSameSender = previousEvent != null &&
final previousEventSameSender =
previousEvent != null &&
{
EventTypes.Message,
EventTypes.Sticker,
@ -128,17 +131,19 @@ class Message extends StatelessWidget {
previousEvent!.senderId == event.senderId &&
previousEvent!.originServerTs.sameEnvironment(event.originServerTs);
final textColor =
ownMessage ? theme.onBubbleColor : theme.colorScheme.onSurface;
final textColor = ownMessage
? theme.onBubbleColor
: theme.colorScheme.onSurface;
final linkColor = ownMessage
? theme.brightness == Brightness.light
? theme.colorScheme.primaryFixed
: theme.colorScheme.onTertiaryContainer
? theme.colorScheme.primaryFixed
: theme.colorScheme.onTertiaryContainer
: theme.colorScheme.primary;
final rowMainAxisAlignment =
ownMessage ? MainAxisAlignment.end : MainAxisAlignment.start;
final rowMainAxisAlignment = ownMessage
? MainAxisAlignment.end
: MainAxisAlignment.start;
final displayEvent = event.getDisplayEvent(timeline);
const hardCorner = Radius.circular(4);
@ -146,12 +151,15 @@ class Message extends StatelessWidget {
final borderRadius = BorderRadius.only(
topLeft: !ownMessage && nextEventSameSender ? hardCorner : roundedCorner,
topRight: ownMessage && nextEventSameSender ? hardCorner : roundedCorner,
bottomLeft:
!ownMessage && previousEventSameSender ? hardCorner : roundedCorner,
bottomRight:
ownMessage && previousEventSameSender ? hardCorner : roundedCorner,
bottomLeft: !ownMessage && previousEventSameSender
? hardCorner
: roundedCorner,
bottomRight: ownMessage && previousEventSameSender
? hardCorner
: roundedCorner,
);
final noBubble = ({
final noBubble =
({
MessageTypes.Video,
MessageTypes.Image,
MessageTypes.Sticker,
@ -165,8 +173,9 @@ class Message extends StatelessWidget {
event.numberEmotes <= 3);
if (ownMessage) {
color =
displayEvent.status.isError ? Colors.redAccent : theme.bubbleColor;
color = displayEvent.status.isError
? Colors.redAccent
: theme.bubbleColor;
}
final resetAnimateIn = this.resetAnimateIn;
@ -176,10 +185,7 @@ class Message extends StatelessWidget {
if (singleSelected) {
sentReactions.addAll(
event
.aggregatedEvents(
timeline,
RelationshipTypes.reaction,
)
.aggregatedEvents(timeline, RelationshipTypes.reaction)
.where(
(event) =>
event.senderId == event.room.client.userID &&
@ -194,11 +200,15 @@ class Message extends StatelessWidget {
);
}
final showReceiptsRow =
event.hasAggregatedEvents(timeline, RelationshipTypes.reaction);
final showReceiptsRow = event.hasAggregatedEvents(
timeline,
RelationshipTypes.reaction,
);
final threadChildren =
event.aggregatedEvents(timeline, RelationshipTypes.thread);
final threadChildren = event.aggregatedEvents(
timeline,
RelationshipTypes.thread,
);
final showReactionPicker =
singleSelected && event.room.canSendDefaultMessages;
@ -210,9 +220,7 @@ class Message extends StatelessWidget {
key: ValueKey(event.eventId),
background: const Padding(
padding: EdgeInsets.symmetric(horizontal: 12.0),
child: Center(
child: Icon(Icons.check_outlined),
),
child: Center(child: Icon(Icons.check_outlined)),
),
direction: AppSettings.swipeRightToLeftToReply.value
? SwipeDirection.endToStart
@ -229,9 +237,8 @@ class Message extends StatelessWidget {
bottom: previousEventSameSender ? 1.0 : 4.0,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
mainAxisSize: .min,
crossAxisAlignment: ownMessage ? .end : .start,
children: <Widget>[
if (displayTime || selected)
Padding(
@ -242,8 +249,9 @@ class Message extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Material(
borderRadius:
BorderRadius.circular(AppConfig.borderRadius * 2),
borderRadius: BorderRadius.circular(
AppConfig.borderRadius * 2,
),
color: theme.colorScheme.surface.withAlpha(128),
child: Padding(
padding: const EdgeInsets.symmetric(
@ -305,13 +313,13 @@ class Message extends StatelessWidget {
),
color: selected || highlightMarker
? theme.colorScheme.secondaryContainer
.withAlpha(128)
.withAlpha(128)
: Colors.transparent,
),
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: .start,
mainAxisAlignment: rowMainAxisAlignment,
children: [
if (longPressSelect && !event.redacted)
@ -336,18 +344,17 @@ class Message extends StatelessWidget {
child: SizedBox(
width: 16,
height: 16,
child: event.status ==
EventStatus.error
child:
event.status == EventStatus.error
? const Icon(
Icons.error,
color: Colors.red,
)
: event.fileSendingStatus != null
? const CircularProgressIndicator
.adaptive(
strokeWidth: 1,
)
: null,
? const CircularProgressIndicator.adaptive(
strokeWidth: 1,
)
: null,
),
),
)
@ -355,17 +362,18 @@ class Message extends StatelessWidget {
FutureBuilder<User?>(
future: event.fetchSenderUser(),
builder: (context, snapshot) {
final user = snapshot.data ??
final user =
snapshot.data ??
event.senderFromMemoryOrFallback;
return Avatar(
mxContent: user.avatarUrl,
name: user.calcDisplayname(),
onTap: () =>
showMemberActionsPopupMenu(
context: context,
user: user,
onMention: onMention,
),
context: context,
user: user,
onMention: onMention,
),
presenceUserId: user.stateKey,
presenceBackgroundColor: wallpaperMode
? Colors.transparent
@ -375,9 +383,8 @@ class Message extends StatelessWidget {
),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: .start,
mainAxisSize: .min,
children: [
if (!nextEventSameSender)
Padding(
@ -385,16 +392,16 @@ class Message extends StatelessWidget {
left: 8.0,
bottom: 4,
),
child: ownMessage ||
child:
ownMessage ||
event.room.isDirectChat
? const SizedBox(height: 12)
: FutureBuilder<User?>(
future:
event.fetchSenderUser(),
builder:
(context, snapshot) {
final displayname = snapshot
.data
future: event
.fetchSenderUser(),
builder: (context, snapshot) {
final displayname =
snapshot.data
?.calcDisplayname() ??
event
.senderFromMemoryOrFallback
@ -405,29 +412,30 @@ class Message extends StatelessWidget {
fontSize: 11,
fontWeight:
FontWeight.bold,
color: (theme.brightness ==
color:
(theme.brightness ==
Brightness
.light
? displayname
.color
.color
: displayname
.lightColorText),
.lightColorText),
shadows:
!wallpaperMode
? null
: [
const Shadow(
offset:
Offset(
? null
: [
const Shadow(
offset:
Offset(
0.0,
0.0,
),
blurRadius:
3,
color: Colors
.black,
),
],
blurRadius:
3,
color: Colors
.black,
),
],
),
maxLines: 1,
overflow: TextOverflow
@ -438,25 +446,25 @@ class Message extends StatelessWidget {
),
Container(
alignment: alignment,
padding:
const EdgeInsets.only(left: 8),
padding: const EdgeInsets.only(
left: 8,
),
child: GestureDetector(
onLongPress: longPressSelect
? null
: () {
HapticFeedback
.heavyImpact();
HapticFeedback.heavyImpact();
onSelect(event);
},
child: AnimatedOpacity(
opacity: animateIn
? 0
: event.messageType ==
MessageTypes
.BadEncrypted ||
event.status.isSending
? 0.5
: 1,
MessageTypes
.BadEncrypted ||
event.status.isSending
? 0.5
: 1,
duration: FluffyThemes
.animationDuration,
curve:
@ -471,7 +479,8 @@ class Message extends StatelessWidget {
clipBehavior: Clip.antiAlias,
child: BubbleBackground(
colors: colors,
ignore: noBubble ||
ignore:
noBubble ||
!ownMessage ||
MediaQuery.highContrastOf(
context,
@ -482,24 +491,24 @@ class Message extends StatelessWidget {
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(
AppConfig.borderRadius,
),
AppConfig
.borderRadius,
),
),
constraints:
const BoxConstraints(
maxWidth: FluffyThemes
.columnWidth *
1.5,
),
maxWidth:
FluffyThemes
.columnWidth *
1.5,
),
child: Column(
mainAxisSize:
MainAxisSize.min,
mainAxisSize: .min,
crossAxisAlignment:
CrossAxisAlignment
.start,
children: <Widget>[
if (event
.inReplyToEventId(
if (event.inReplyToEventId(
includingFallback:
false,
) !=
@ -507,21 +516,23 @@ class Message extends StatelessWidget {
FutureBuilder<Event?>(
future: event
.getReplyEvent(
timeline,
),
builder: (
BuildContext
timeline,
),
builder:
(
BuildContext
context,
snapshot,
) {
final replyEvent =
snapshot
snapshot,
) {
final replyEvent =
snapshot
.hasData
? snapshot
.data!
.data!
: Event(
eventId:
event.inReplyToEventId() ?? '\$fake_event_id',
event.inReplyToEventId() ??
'\$fake_event_id',
content: {
'msgtype':
'm.text',
@ -539,45 +550,42 @@ class Message extends StatelessWidget {
originServerTs:
DateTime.now(),
);
return Padding(
padding:
const EdgeInsets
.only(
left: 16,
right: 16,
top: 8,
),
child: Material(
color: Colors
.transparent,
borderRadius:
ReplyContent
.borderRadius,
child:
InkWell(
borderRadius:
ReplyContent
.borderRadius,
onTap: () =>
scrollToEventId(
replyEvent
.eventId,
),
child:
AbsorbPointer(
child:
ReplyContent(
replyEvent,
ownMessage:
ownMessage,
timeline:
timeline,
return Padding(
padding:
const EdgeInsets.only(
left:
16,
right:
16,
top:
8,
),
child: Material(
color: Colors
.transparent,
borderRadius:
ReplyContent
.borderRadius,
child: InkWell(
borderRadius:
ReplyContent.borderRadius,
onTap: () => scrollToEventId(
replyEvent
.eventId,
),
child: AbsorbPointer(
child: ReplyContent(
replyEvent,
ownMessage:
ownMessage,
timeline:
timeline,
),
),
),
),
),
),
);
},
);
},
),
MessageContent(
displayEvent,
@ -591,18 +599,17 @@ class Message extends StatelessWidget {
),
if (event
.hasAggregatedEvents(
timeline,
RelationshipTypes
.edit,
))
timeline,
RelationshipTypes
.edit,
))
Padding(
padding:
const EdgeInsets
.only(
bottom: 8.0,
left: 16.0,
right: 16.0,
),
const EdgeInsets.only(
bottom: 8.0,
left: 16.0,
right: 16.0,
),
child: Row(
mainAxisSize:
MainAxisSize
@ -614,22 +621,21 @@ class Message extends StatelessWidget {
.edit_outlined,
color: textColor
.withAlpha(
164,
),
164,
),
size: 14,
),
Text(
displayEvent
.originServerTs
.localizedTimeShort(
context,
),
style:
TextStyle(
context,
),
style: TextStyle(
color: textColor
.withAlpha(
164,
),
164,
),
fontSize:
11,
),
@ -657,46 +663,43 @@ class Message extends StatelessWidget {
? Padding(
padding:
const EdgeInsets.all(
4.0,
),
4.0,
),
child: Material(
elevation: 4,
borderRadius:
BorderRadius.circular(
AppConfig.borderRadius,
),
AppConfig
.borderRadius,
),
shadowColor: theme
.colorScheme.surface
.colorScheme
.surface
.withAlpha(128),
child:
SingleChildScrollView(
child: SingleChildScrollView(
scrollDirection:
Axis.horizontal,
child: Row(
mainAxisSize:
MainAxisSize.min,
mainAxisSize: .min,
children: [
...AppConfig
.defaultReactions
.map(
(emoji) =>
IconButton(
...AppConfig.defaultReactions.map(
(
emoji,
) => IconButton(
padding:
EdgeInsets
.zero,
icon: Center(
child:
Opacity(
opacity: sentReactions
.contains(
emoji,
)
child: Opacity(
opacity:
sentReactions.contains(
emoji,
)
? 0.33
: 1,
child: Text(
emoji,
style:
const TextStyle(
style: const TextStyle(
fontSize:
20,
),
@ -708,19 +711,20 @@ class Message extends StatelessWidget {
),
onPressed:
sentReactions
.contains(
emoji,
)
? null
: () {
onSelect(
event,
);
event.room.sendReaction(
event.eventId,
emoji,
);
},
.contains(
emoji,
)
? null
: () {
onSelect(
event,
);
event.room.sendReaction(
event
.eventId,
emoji,
);
},
),
),
IconButton(
@ -731,72 +735,55 @@ class Message extends StatelessWidget {
tooltip: L10n.of(
context,
).customReaction,
onPressed:
() async {
final emoji =
await showAdaptiveBottomSheet<
String>(
onPressed: () async {
final emoji = await showAdaptiveBottomSheet<String>(
context:
context,
builder:
(context) =>
Scaffold(
appBar:
AppBar(
title:
Text(
L10n.of(context)
.customReaction,
),
leading:
CloseButton(
onPressed:
() =>
Navigator.of(
builder: (context) => Scaffold(
appBar: AppBar(
title: Text(
L10n.of(
context,
).pop(
null,
),
).customReaction,
),
leading: CloseButton(
onPressed: () => Navigator.of(
context,
).pop(null),
),
),
body:
SizedBox(
body: SizedBox(
height: double
.infinity,
child:
EmojiPicker(
onEmojiSelected: (
_,
emoji,
) =>
Navigator.of(
context,
).pop(
emoji
.emoji,
),
config:
Config(
locale:
Localizations.localeOf(context),
emojiViewConfig:
const EmojiViewConfig(
child: EmojiPicker(
onEmojiSelected:
(
_,
emoji,
) =>
Navigator.of(
context,
).pop(
emoji.emoji,
),
config: Config(
locale: Localizations.localeOf(
context,
),
emojiViewConfig: const EmojiViewConfig(
backgroundColor:
Colors.transparent,
),
bottomActionBarConfig:
const BottomActionBarConfig(
bottomActionBarConfig: const BottomActionBarConfig(
enabled:
false,
),
categoryViewConfig:
CategoryViewConfig(
categoryViewConfig: CategoryViewConfig(
initCategory:
Category.SMILEYS,
backspaceColor:
theme.colorScheme.primary,
iconColor:
theme.colorScheme.primary.withAlpha(
iconColor: theme.colorScheme.primary.withAlpha(
128,
),
iconColorSelected:
@ -806,10 +793,8 @@ class Message extends StatelessWidget {
backgroundColor:
theme.colorScheme.surface,
),
skinToneConfig:
SkinToneConfig(
dialogBackgroundColor:
Color.lerp(
skinToneConfig: SkinToneConfig(
dialogBackgroundColor: Color.lerp(
theme.colorScheme.surface,
theme.colorScheme.primaryContainer,
0.75,
@ -828,17 +813,18 @@ class Message extends StatelessWidget {
}
if (sentReactions
.contains(
emoji,
)) {
emoji,
)) {
return;
}
onSelect(event);
await event.room
.sendReaction(
event.eventId,
emoji,
);
event
.eventId,
emoji,
);
},
),
],
@ -904,10 +890,7 @@ class Message extends StatelessWidget {
onPressed: () => enterThread(event.eventId),
icon: const Icon(Icons.message),
label: Text(
'${L10n.of(context).countReplies(threadChildren.length)} | ${threadChildren.first.calcLocalizedBodyFallback(
MatrixLocals(L10n.of(context)),
withSenderNamePrefix: true,
)}',
'${L10n.of(context).countReplies(threadChildren.length)} | ${threadChildren.first.calcLocalizedBodyFallback(MatrixLocals(L10n.of(context)), withSenderNamePrefix: true)}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@ -933,8 +916,9 @@ class Message extends StatelessWidget {
vertical: 2,
),
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(AppConfig.borderRadius / 3),
borderRadius: BorderRadius.circular(
AppConfig.borderRadius / 3,
),
color: theme.colorScheme.surface.withAlpha(128),
),
child: Text(
@ -1005,8 +989,10 @@ class BubblePainter extends CustomPainter {
final scrollableRect = Offset.zero & scrollableBox.size;
final bubbleBox = context.findRenderObject() as RenderBox;
final origin =
bubbleBox.localToGlobal(Offset.zero, ancestor: scrollableBox);
final origin = bubbleBox.localToGlobal(
Offset.zero,
ancestor: scrollableBox,
);
final paint = Paint()
..shader = ui.Gradient.linear(
scrollableRect.topCenter,

View file

@ -50,9 +50,7 @@ class MessageContent extends StatelessWidget {
if (event.content['can_request_session'] != true) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
event.calcLocalizedBodyFallback(MatrixLocals(l10n)),
),
content: Text(event.calcLocalizedBodyFallback(MatrixLocals(l10n))),
),
);
return;
@ -91,11 +89,7 @@ class MessageContent extends StatelessWidget {
trailing: const Icon(Icons.lock_outlined),
),
const Divider(),
Text(
event.calcLocalizedBodyFallback(
MatrixLocals(l10n),
),
),
Text(event.calcLocalizedBodyFallback(MatrixLocals(l10n))),
],
),
),
@ -116,8 +110,9 @@ class MessageContent extends StatelessWidget {
case MessageTypes.Image:
case MessageTypes.Sticker:
if (event.redacted) continue textmessage;
final maxSize =
event.messageType == MessageTypes.Sticker ? 128.0 : 256.0;
final maxSize = event.messageType == MessageTypes.Sticker
? 128.0
: 256.0;
final w = event.content
.tryGetMap<String, Object?>('info')
?.tryGet<int>('w');
@ -152,12 +147,12 @@ class MessageContent extends StatelessWidget {
return CuteContent(event);
case MessageTypes.Audio:
if (PlatformInfos.isMobile ||
PlatformInfos.isMacOS ||
PlatformInfos.isWeb
// Disabled until https://github.com/bleonard252/just_audio_mpv/issues/3
// is fixed
// || PlatformInfos.isLinux
) {
PlatformInfos.isMacOS ||
PlatformInfos.isWeb
// Disabled until https://github.com/bleonard252/just_audio_mpv/issues/3
// is fixed
// || PlatformInfos.isLinux
) {
return AudioPlayerWidget(
event,
color: textColor,
@ -193,8 +188,9 @@ class MessageContent extends StatelessWidget {
fontSize: fontSize,
);
case MessageTypes.Location:
final geoUri =
Uri.tryParse(event.content.tryGet<String>('geo_uri')!);
final geoUri = Uri.tryParse(
event.content.tryGet<String>('geo_uri')!,
);
if (geoUri != null && geoUri.scheme == 'geo') {
final latlong = geoUri.path
.split(';')
@ -206,7 +202,7 @@ class MessageContent extends StatelessWidget {
latlong.first != null &&
latlong.last != null) {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
MapBubble(
latitude: latlong.first!,
@ -215,8 +211,10 @@ class MessageContent extends StatelessWidget {
const SizedBox(height: 6),
OutlinedButton.icon(
icon: Icon(Icons.location_on_outlined, color: textColor),
onPressed:
UrlLauncher(context, geoUri.toString()).launchUrl,
onPressed: UrlLauncher(
context,
geoUri.toString(),
).launchUrl,
label: Text(
L10n.of(context).openInMaps,
style: TextStyle(color: textColor),
@ -248,25 +246,25 @@ class MessageContent extends StatelessWidget {
html = '* $html';
}
final bigEmotes = event.onlyEmotes &&
final bigEmotes =
event.onlyEmotes &&
event.numberEmotes > 0 &&
event.numberEmotes <= 3;
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: HtmlMessage(
html: html,
textColor: textColor,
room: event.room,
fontSize: AppSettings.fontSizeFactor.value *
fontSize:
AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize *
(bigEmotes ? 5 : 1),
limitHeight: !selected,
linkStyle: TextStyle(
color: linkColor,
fontSize: AppSettings.fontSizeFactor.value *
fontSize:
AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
decoration: TextDecoration.underline,
decorationColor: linkColor,
@ -352,16 +350,14 @@ class RedactionWidget extends StatelessWidget {
future: event.redactedBecause?.fetchSenderUser(),
builder: (context, snapshot) {
final reason = event.redactedBecause?.content.tryGet<String>('reason');
final redactedBy = snapshot.data?.calcDisplayname() ??
final redactedBy =
snapshot.data?.calcDisplayname() ??
event.redactedBecause?.senderId.localpart ??
L10n.of(context).user;
return _ButtonContent(
label: reason == null
? L10n.of(context).redactedBy(redactedBy)
: L10n.of(context).redactedByBecause(
redactedBy,
reason,
),
: L10n.of(context).redactedByBecause(redactedBy, reason),
icon: '🗑️',
textColor: buttonTextColor.withAlpha(128),
onPressed: () => onInfoTab!(event),
@ -390,18 +386,12 @@ class _ButtonContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: InkWell(
onTap: onPressed,
child: Text(
'$icon $label',
style: TextStyle(
color: textColor,
fontSize: fontSize,
),
style: TextStyle(color: textColor, fontSize: fontSize),
),
),
);

View file

@ -27,15 +27,15 @@ class MessageDownloadContent extends StatelessWidget {
final filetype = (filename.contains('.')
? filename.split('.').last.toUpperCase()
: event.content
.tryGetMap<String, dynamic>('info')
?.tryGet<String>('mimetype')
?.toUpperCase() ??
'UNKNOWN');
.tryGetMap<String, dynamic>('info')
?.tryGet<String>('mimetype')
?.toUpperCase() ??
'UNKNOWN');
final sizeString = event.sizeString ?? '?MB';
final fileDescription = event.fileDescription;
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: .min,
crossAxisAlignment: .start,
spacing: 8,
children: [
Material(
@ -47,7 +47,7 @@ class MessageDownloadContent extends StatelessWidget {
width: 400,
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
spacing: 16,
children: [
CircleAvatar(
@ -56,8 +56,8 @@ class MessageDownloadContent extends StatelessWidget {
),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: .start,
mainAxisSize: .min,
children: [
Text(
filename,
@ -93,13 +93,15 @@ class MessageDownloadContent extends StatelessWidget {
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
style: TextStyle(
color: textColor,
fontSize: AppSettings.fontSizeFactor.value *
fontSize:
AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
),
options: const LinkifyOptions(humanize: false),
linkStyle: TextStyle(
color: linkColor,
fontSize: AppSettings.fontSizeFactor.value *
fontSize:
AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
decoration: TextDecoration.underline,
decorationColor: linkColor,

View file

@ -17,8 +17,10 @@ class MessageReactions extends StatelessWidget {
@override
Widget build(BuildContext context) {
final allReactionEvents =
event.aggregatedEvents(timeline, RelationshipTypes.reaction);
final allReactionEvents = event.aggregatedEvents(
timeline,
RelationshipTypes.reaction,
);
final reactionMap = <String, _ReactionEntry>{};
final client = Matrix.of(context).client;
@ -113,7 +115,7 @@ class _Reaction extends StatelessWidget {
Widget content;
if (reactionKey.startsWith('mxc://')) {
content = Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: <Widget>[
MxcImage(
uri: Uri.parse(reactionKey),
@ -190,17 +192,14 @@ class _AdaptableReactorsDialog extends StatelessWidget {
final Client? client;
final _ReactionEntry? reactionEntry;
const _AdaptableReactorsDialog({
this.client,
this.reactionEntry,
});
const _AdaptableReactorsDialog({this.client, this.reactionEntry});
Future<bool?> show(BuildContext context) => showAdaptiveDialog(
context: context,
builder: (context) => this,
barrierDismissible: true,
useRootNavigator: false,
);
context: context,
builder: (context) => this,
barrierDismissible: true,
useRootNavigator: false,
);
@override
Widget build(BuildContext context) {
@ -226,9 +225,6 @@ class _AdaptableReactorsDialog extends StatelessWidget {
final title = Center(child: Text(reactionEntry!.key));
return AlertDialog.adaptive(
title: title,
content: body,
);
return AlertDialog.adaptive(title: title, content: body);
}
}

View file

@ -24,10 +24,8 @@ class PollWidget extends StatelessWidget {
super.key,
});
void _endPoll(BuildContext context) => showFutureLoadingDialog(
context: context,
future: () => event.endPoll(),
);
void _endPoll(BuildContext context) =>
showFutureLoadingDialog(context: context, future: () => event.endPoll());
void _toggleVote(
BuildContext context,
@ -60,20 +58,19 @@ class PollWidget extends StatelessWidget {
}
final responses = event.getPollResponses(timeline);
final pollHasBeenEnded = event.getPollHasBeenEnded(timeline);
final canVote = event.room.canSendEvent(PollEventContent.responseType) &&
final canVote =
event.room.canSendEvent(PollEventContent.responseType) &&
!pollHasBeenEnded;
final maxPolls = responses.length;
final answersVisible =
eventContent.pollStartContent.kind == PollKind.disclosed ||
pollHasBeenEnded;
pollHasBeenEnded;
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
),
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: .min,
crossAxisAlignment: .start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
@ -82,13 +79,15 @@ class PollWidget extends StatelessWidget {
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
style: TextStyle(
color: textColor,
fontSize: AppSettings.fontSizeFactor.value *
fontSize:
AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
),
options: const LinkifyOptions(humanize: false),
linkStyle: TextStyle(
color: linkColor,
fontSize: AppSettings.fontSizeFactor.value *
fontSize:
AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
decoration: TextDecoration.underline,
decorationColor: linkColor,
@ -97,99 +96,101 @@ class PollWidget extends StatelessWidget {
),
),
Divider(color: linkColor.withAlpha(64)),
...eventContent.pollStartContent.answers.map(
(answer) {
final votedUserIds = responses.entries
.where((entry) => entry.value.contains(answer.id))
.map((entry) => entry.key)
.toSet();
return Material(
color: Colors.transparent,
clipBehavior: Clip.hardEdge,
child: CheckboxListTile.adaptive(
value: responses[event.room.client.userID!]
?.contains(answer.id) ??
false,
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
checkboxScaleFactor: 1.5,
checkboxShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(32),
...eventContent.pollStartContent.answers.map((answer) {
final votedUserIds = responses.entries
.where((entry) => entry.value.contains(answer.id))
.map((entry) => entry.key)
.toSet();
return Material(
color: Colors.transparent,
clipBehavior: Clip.hardEdge,
child: CheckboxListTile.adaptive(
value:
responses[event.room.client.userID!]?.contains(answer.id) ??
false,
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
checkboxScaleFactor: 1.5,
checkboxShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(32),
),
onChanged: !canVote
? null
: (_) => _toggleVote(
context,
answer.id,
eventContent.pollStartContent.maxSelections,
),
title: Text(
answer.mText,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: textColor,
fontSize:
AppConfig.messageFontSize *
AppSettings.fontSizeFactor.value,
),
onChanged: !canVote
? null
: (_) => _toggleVote(
context,
answer.id,
eventContent.pollStartContent.maxSelections,
),
title: Text(
answer.mText,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: textColor,
fontSize: AppConfig.messageFontSize *
AppSettings.fontSizeFactor.value,
),
),
subtitle: answersVisible
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
Text(
L10n.of(context)
.countVotes(votedUserIds.length),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: linkColor,
fontSize:
),
subtitle: answersVisible
? Column(
crossAxisAlignment: .start,
mainAxisSize: .min,
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
Text(
L10n.of(
context,
).countVotes(votedUserIds.length),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: linkColor,
fontSize:
12 * AppSettings.fontSizeFactor.value,
),
),
const SizedBox(width: 2),
...votedUserIds.map((userId) {
final user = event.room
.getState(EventTypes.RoomMember, userId)
?.asUser(event.room);
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 2.0,
),
child: Avatar(
mxContent: user?.avatarUrl,
name:
user?.calcDisplayname() ??
userId.localpart,
size:
12 * AppSettings.fontSizeFactor.value,
),
),
const SizedBox(width: 2),
...votedUserIds.map((userId) {
final user = event.room
.getState(EventTypes.RoomMember, userId)
?.asUser(event.room);
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 2.0,
),
child: Avatar(
mxContent: user?.avatarUrl,
name: user?.calcDisplayname() ??
userId.localpart,
size: 12 *
AppSettings.fontSizeFactor.value,
),
);
}),
const SizedBox(width: 2),
],
),
);
}),
const SizedBox(width: 2),
],
),
LinearProgressIndicator(
color: linkColor,
backgroundColor: linkColor.withAlpha(128),
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
value: maxPolls == 0
? 0
: votedUserIds.length / maxPolls,
),
LinearProgressIndicator(
color: linkColor,
backgroundColor: linkColor.withAlpha(128),
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
],
)
: null,
),
);
},
),
value: maxPolls == 0
? 0
: votedUserIds.length / maxPolls,
),
],
)
: null,
),
);
}),
if (!pollHasBeenEnded && event.senderId == event.room.client.userID)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),

View file

@ -29,21 +29,22 @@ class ReplyContent extends StatelessWidget {
final theme = Theme.of(context);
final timeline = this.timeline;
final displayEvent =
timeline != null ? replyEvent.getDisplayEvent(timeline) : replyEvent;
final displayEvent = timeline != null
? replyEvent.getDisplayEvent(timeline)
: replyEvent;
final fontSize =
AppConfig.messageFontSize * AppSettings.fontSizeFactor.value;
final color = theme.brightness == Brightness.dark
? theme.colorScheme.onTertiaryContainer
: ownMessage
? theme.colorScheme.tertiaryContainer
: theme.colorScheme.tertiary;
? theme.colorScheme.tertiaryContainer
: theme.colorScheme.tertiary;
return Material(
color: Colors.transparent,
borderRadius: borderRadius,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: <Widget>[
Container(
width: 5,
@ -56,8 +57,8 @@ class ReplyContent extends StatelessWidget {
const SizedBox(width: 6),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: .start,
mainAxisAlignment: .center,
children: <Widget>[
FutureBuilder<User?>(
initialData: displayEvent.senderFromMemoryOrFallback,
@ -87,8 +88,8 @@ class ReplyContent extends StatelessWidget {
color: theme.brightness == Brightness.dark
? theme.colorScheme.onSurface
: ownMessage
? theme.colorScheme.onTertiary
: theme.colorScheme.onSurface,
? theme.colorScheme.onTertiary
: theme.colorScheme.onSurface,
fontSize: fontSize,
),
),

View file

@ -30,7 +30,7 @@ class RoomCreationStateEvent extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
Avatar(
mxContent: event.room.avatar,

View file

@ -35,8 +35,9 @@ class StateMessage extends StatelessWidget {
padding: const EdgeInsets.all(4),
child: Material(
color: theme.colorScheme.surface.withAlpha(128),
borderRadius:
BorderRadius.circular(AppConfig.borderRadius / 3),
borderRadius: BorderRadius.circular(
AppConfig.borderRadius / 3,
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,

View file

@ -35,8 +35,10 @@ class EventVideoPlayer extends StatelessWidget {
Widget build(BuildContext context) {
final supportsVideoPlayer = PlatformInfos.supportsVideoPlayer;
final blurHash = (event.infoMap as Map<String, dynamic>)
.tryGet<String>('xyz.amorgan.blurhash') ??
final blurHash =
(event.infoMap as Map<String, dynamic>).tryGet<String>(
'xyz.amorgan.blurhash',
) ??
fallbackBlurHash;
final fileDescription = event.fileDescription;
const maxDimension = 300.0;
@ -49,11 +51,12 @@ class EventVideoPlayer extends StatelessWidget {
final height = videoHeight / modifier;
final durationInt = infoMap?.tryGet<int>('duration');
final duration =
durationInt == null ? null : Duration(milliseconds: durationInt);
final duration = durationInt == null
? null
: Duration(milliseconds: durationInt);
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
spacing: 8,
children: [
Material(
@ -128,22 +131,21 @@ class EventVideoPlayer extends StatelessWidget {
SizedBox(
width: width,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Linkify(
text: fileDescription,
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
style: TextStyle(
color: textColor,
fontSize: AppSettings.fontSizeFactor.value *
fontSize:
AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
),
options: const LinkifyOptions(humanize: false),
linkStyle: TextStyle(
color: linkColor,
fontSize: AppSettings.fontSizeFactor.value *
fontSize:
AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
decoration: TextDecoration.underline,
decorationColor: linkColor,

View file

@ -62,10 +62,7 @@ class InputBar extends StatelessWidget {
final commandSearch = commandMatch[1]!.toLowerCase();
for (final command in room.client.commands.keys) {
if (command.contains(commandSearch)) {
ret.add({
'type': 'command',
'name': command,
});
ret.add({'type': 'command', 'name': command});
}
if (ret.length > maxResults) return ret;
@ -107,8 +104,8 @@ class InputBar extends StatelessWidget {
'type': 'emote',
'name': emote.key,
'pack': packSearch,
'pack_avatar_url':
emotePacks[packSearch]!.pack.avatarUrl?.toString(),
'pack_avatar_url': emotePacks[packSearch]!.pack.avatarUrl
?.toString(),
'pack_display_name':
emotePacks[packSearch]!.pack.displayName ?? packSearch,
'mxc': emote.value.url.toString(),
@ -159,8 +156,9 @@ class InputBar extends StatelessWidget {
for (final user in room.getParticipants()) {
if ((user.displayName != null &&
(user.displayName!.toLowerCase().contains(userSearch) ||
slugify(user.displayName!.toLowerCase())
.contains(userSearch))) ||
slugify(
user.displayName!.toLowerCase(),
).contains(userSearch))) ||
user.id.split(':')[0].toLowerCase().contains(userSearch)) {
ret.add({
'type': 'user',
@ -258,11 +256,7 @@ class InputBar extends StatelessWidget {
style: const TextStyle(fontSize: 16),
),
),
title: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
title: Text(label, maxLines: 1, overflow: TextOverflow.ellipsis),
),
);
}
@ -280,7 +274,7 @@ class InputBar extends StatelessWidget {
isThumbnail: false,
),
title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
crossAxisAlignment: .center,
children: <Widget>[
Text(suggestion['name']!),
Expanded(
@ -311,7 +305,8 @@ class InputBar extends StatelessWidget {
onTap: () => onSelected(suggestion),
leading: Avatar(
mxContent: url,
name: suggestion.tryGet<String>('displayname') ??
name:
suggestion.tryGet<String>('displayname') ??
suggestion.tryGet<String>('mxid'),
size: size,
client: client,
@ -323,8 +318,10 @@ class InputBar extends StatelessWidget {
}
String insertSuggestion(Map<String, String?> suggestion) {
final replaceText =
controller!.text.substring(0, controller!.selection.baseOffset);
final replaceText = controller!.text.substring(
0,
controller!.selection.baseOffset,
);
var startText = '';
final afterText = replaceText == controller!.text
? ''
@ -409,10 +406,7 @@ class InputBar extends StatelessWidget {
bytes: data,
name: content.uri.split('/').last,
);
room.sendFileEvent(
file,
shrinkImageMaxDimension: 1600,
);
room.sendFileEvent(file, shrinkImageMaxDimension: 1600);
},
),
minLines: minLines,

View file

@ -39,7 +39,8 @@ class PinnedEvents extends StatelessWidget {
(event) => AdaptiveModalAction(
value: event?.eventId ?? '',
icon: const Icon(Icons.push_pin_outlined),
label: event?.calcLocalizedBodyFallback(
label:
event?.calcLocalizedBodyFallback(
MatrixLocals(L10n.of(context)),
withSenderNamePrefix: true,
hideReply: true,
@ -68,7 +69,8 @@ class PinnedEvents extends StatelessWidget {
builder: (context, snapshot) {
final event = snapshot.data;
return ChatAppBarListTile(
title: event?.calcLocalizedBodyFallback(
title:
event?.calcLocalizedBodyFallback(
MatrixLocals(L10n.of(context)),
withSenderNamePrefix: true,
hideReply: true,

View file

@ -46,8 +46,8 @@ class RecordingInputRow extends StatelessWidget {
builder: (context, constraints) {
const width = 4;
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: .min,
mainAxisAlignment: .end,
children: state.amplitudeTimeline.reversed
.take((constraints.maxWidth / (width + 2)).floor())
.toList()

View file

@ -20,10 +20,7 @@ import 'events/audio_player.dart';
class RecordingViewModel extends StatefulWidget {
final Widget Function(BuildContext, RecordingViewModelState) builder;
const RecordingViewModel({
required this.builder,
super.key,
});
const RecordingViewModel({required this.builder, super.key});
@override
RecordingViewModelState createState() => RecordingViewModelState();
@ -70,10 +67,10 @@ class RecordingViewModelState extends State<RecordingViewModel> {
? AudioEncoder.wav
// Everywhere else we use opus if supported by the platform:
: !PlatformInfos
.isIOS && // Blocked by https://github.com/llfbandit/record/issues/560
await audioRecorder.isEncoderSupported(AudioEncoder.opus)
? AudioEncoder.opus
: AudioEncoder.aacLc;
.isIOS && // Blocked by https://github.com/llfbandit/record/issues/560
await audioRecorder.isEncoderSupported(AudioEncoder.opus)
? AudioEncoder.opus
: AudioEncoder.aacLc;
fileName =
'recording${DateTime.now().microsecondsSinceEpoch}.${codec.fileExtension}';
String? path;
@ -126,8 +123,9 @@ class RecordingViewModelState extends State<RecordingViewModel> {
void _subscribe() {
_recorderSubscription?.cancel();
_recorderSubscription =
Timer.periodic(const Duration(milliseconds: 100), (_) async {
_recorderSubscription = Timer.periodic(const Duration(milliseconds: 100), (
_,
) async {
final amplitude = await _audioRecorder!.getAmplitude();
var value = 100 + amplitude.current * 2;
value = value < 1 ? 1 : value;
@ -178,7 +176,8 @@ class RecordingViewModelState extends State<RecordingViewModel> {
int duration,
List<int> waveform,
String? fileName,
) onSend,
)
onSend,
) async {
_recorderSubscription?.cancel();
final path = await _audioRecorder?.stop();

View file

@ -23,9 +23,7 @@ class ReplyDisplay extends StatelessWidget {
? 56
: 0,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
color: theme.colorScheme.onInverseSurface,
),
decoration: BoxDecoration(color: theme.colorScheme.onInverseSurface),
child: Row(
children: <Widget>[
IconButton(
@ -63,10 +61,7 @@ class _EditContent extends StatelessWidget {
}
return Row(
children: <Widget>[
Icon(
Icons.edit,
color: theme.colorScheme.primary,
),
Icon(Icons.edit, color: theme.colorScheme.primary),
Container(width: 15.0),
Text(
event.calcLocalizedBodyFallback(
@ -76,9 +71,7 @@ class _EditContent extends StatelessWidget {
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle(
color: theme.textTheme.bodyMedium!.color,
),
style: TextStyle(color: theme.textTheme.bodyMedium!.color),
),
],
);

View file

@ -20,14 +20,16 @@ class SeenByRow extends StatelessWidget {
width: double.infinity,
alignment: Alignment.center,
child: AnimatedContainer(
constraints:
const BoxConstraints(maxWidth: FluffyThemes.maxTimelineWidth),
constraints: const BoxConstraints(
maxWidth: FluffyThemes.maxTimelineWidth,
),
height: seenByUsers.isEmpty ? 0 : 24,
duration: seenByUsers.isEmpty
? Duration.zero
: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
alignment: controller.timeline!.events.isNotEmpty &&
alignment:
controller.timeline!.events.isNotEmpty &&
controller.timeline!.events.first.senderId ==
Matrix.of(context).client.userID
? Alignment.topRight
@ -40,12 +42,12 @@ class SeenByRow extends StatelessWidget {
? seenByUsers.sublist(0, maxAvatars)
: seenByUsers)
.map(
(user) => Avatar(
mxContent: user.avatarUrl,
name: user.calcDisplayname(),
size: 16,
),
),
(user) => Avatar(
mxContent: user.avatarUrl,
name: user.calcDisplayname(),
size: 16,
),
),
if (seenByUsers.length > maxAvatars)
SizedBox(
width: 16,

View file

@ -119,8 +119,9 @@ class SendFileDialogState extends State<SendFileDialog> {
if (e.error != MatrixError.M_LIMIT_EXCEEDED || retryAfterMs == null) {
rethrow;
}
final retryAfterDuration =
Duration(milliseconds: retryAfterMs + 1000);
final retryAfterDuration = Duration(
milliseconds: retryAfterMs + 1000,
);
scaffoldMessenger.showSnackBar(
SnackBar(
@ -164,8 +165,9 @@ class SendFileDialogState extends State<SendFileDialog> {
}
Future<String> _calcCombinedFileSize() async {
final lengths =
await Future.wait(widget.files.map((file) => file.length()));
final lengths = await Future.wait(
widget.files.map((file) => file.length()),
);
return lengths.fold<double>(0, (p, length) => p + length).sizeString;
}
@ -216,7 +218,7 @@ class SendFileDialogState extends State<SendFileDialog> {
width: 256,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
const SizedBox(height: 12),
if (uniqueFileType == 'image')
@ -243,8 +245,8 @@ class SendFileDialogState extends State<SendFileDialog> {
final bytes = snapshot.data;
if (bytes == null) {
return const Center(
child: CircularProgressIndicator
.adaptive(),
child:
CircularProgressIndicator.adaptive(),
);
}
if (snapshot.error != null) {
@ -272,8 +274,11 @@ class SendFileDialogState extends State<SendFileDialog> {
: null,
fit: BoxFit.contain,
errorBuilder: (context, e, s) {
Logs()
.w('Unable to preview image', e, s);
Logs().w(
'Unable to preview image',
e,
s,
);
return const Center(
child: SizedBox(
width: 256,
@ -303,17 +308,17 @@ class SendFileDialogState extends State<SendFileDialog> {
uniqueFileType == null
? Icons.description_outlined
: uniqueFileType == 'video'
? Icons.video_file_outlined
: uniqueFileType == 'audio'
? Icons.audio_file_outlined
: Icons.description_outlined,
? Icons.video_file_outlined
: uniqueFileType == 'audio'
? Icons.audio_file_outlined
: Icons.description_outlined,
size: 32,
),
const SizedBox(width: 8),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: .min,
crossAxisAlignment: .start,
children: [
Text(
fileName,
@ -347,10 +352,12 @@ class SendFileDialogState extends State<SendFileDialog> {
// Workaround for SwitchListTile.adaptive crashes in CupertinoDialog
if ({'image', 'video'}.contains(uniqueFileType))
Row(
crossAxisAlignment: CrossAxisAlignment.center,
crossAxisAlignment: .center,
children: [
if ({TargetPlatform.iOS, TargetPlatform.macOS}
.contains(theme.platform))
if ({
TargetPlatform.iOS,
TargetPlatform.macOS,
}.contains(theme.platform))
CupertinoSwitch(
value: compressionSupported && compress,
onChanged: compressionSupported
@ -367,11 +374,11 @@ class SendFileDialogState extends State<SendFileDialog> {
const SizedBox(width: 16),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: .min,
crossAxisAlignment: .start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
Text(
L10n.of(context).compress,
@ -430,9 +437,7 @@ extension on ScaffoldMessengerState {
const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator.adaptive(
strokeWidth: 2,
),
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
),
const SizedBox(width: 16),
Text(title),

View file

@ -14,10 +14,7 @@ import 'package:fluffychat/widgets/future_loading_dialog.dart';
class SendLocationDialog extends StatefulWidget {
final Room room;
const SendLocationDialog({
required this.room,
super.key,
});
const SendLocationDialog({required this.room, super.key});
@override
SendLocationDialogState createState() => SendLocationDialogState();
@ -102,12 +99,13 @@ class SendLocationDialogState extends State<SendLocationDialog> {
} else if (denied) {
contentWidget = Text(L10n.of(context).locationPermissionDeniedNotice);
} else if (error != null) {
contentWidget =
Text(L10n.of(context).errorObtainingLocation(error.toString()));
contentWidget = Text(
L10n.of(context).errorObtainingLocation(error.toString()),
);
} else {
contentWidget = Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: .min,
mainAxisAlignment: .center,
children: [
const CupertinoActivityIndicator(),
const SizedBox(width: 12),

View file

@ -50,14 +50,15 @@ class _StartPollBottomSheetState extends State<StartPollBottomSheet> {
} catch (e, s) {
Logs().w('Unable to create poll', e, s);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.toLocalizedString(context))),
);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
}
}
void _updateCanCreate([dynamic _]) {
final newCanCreate = _bodyController.text.trim().isNotEmpty &&
final newCanCreate =
_bodyController.text.trim().isNotEmpty &&
!_answers.any((controller) => controller.text.trim().isEmpty);
if (_canCreate != newCanCreate) {
setState(() {
@ -71,9 +72,7 @@ class _StartPollBottomSheetState extends State<StartPollBottomSheet> {
const maxAnswers = 10;
return Scaffold(
appBar: AppBar(
leading: CloseButton(
onPressed: Navigator.of(context).pop,
),
leading: CloseButton(onPressed: Navigator.of(context).pop),
title: Text(L10n.of(context).startPoll),
),
body: ListView(
@ -119,8 +118,8 @@ class _StartPollBottomSheetState extends State<StartPollBottomSheet> {
icon: const Icon(Icons.add_outlined),
onPressed: _answers.length < maxAnswers
? () => setState(() {
_answers.add(TextEditingController());
})
_answers.add(TextEditingController());
})
: null,
label: Text(L10n.of(context).addAnswerOption),
),

View file

@ -38,15 +38,17 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
final filteredImagePackImageEntried = pack.images.entries.toList();
if (searchFilter?.isNotEmpty ?? false) {
filteredImagePackImageEntried.removeWhere(
(e) => !(e.key.toLowerCase().contains(searchFilter!.toLowerCase()) ||
(e.value.body
?.toLowerCase()
.contains(searchFilter!.toLowerCase()) ??
false)),
(e) =>
!(e.key.toLowerCase().contains(searchFilter!.toLowerCase()) ||
(e.value.body?.toLowerCase().contains(
searchFilter!.toLowerCase(),
) ??
false)),
);
}
final imageKeys =
filteredImagePackImageEntried.map((e) => e.key).toList();
final imageKeys = filteredImagePackImageEntried
.map((e) => e.key)
.toList();
if (imageKeys.isEmpty) {
return const SizedBox.shrink();
}
@ -82,8 +84,9 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
key: ValueKey(image.url.toString()),
onTap: () {
// copy the image
final imageCopy =
ImagePackImageContent.fromJson(image.toJson().copy());
final imageCopy = ImagePackImageContent.fromJson(
image.toJson().copy(),
);
// set the body, if it doesn't exist, to the key
imageCopy.body ??= imageKeys[imageIndex];
widget.onSelected(imageCopy);
@ -137,7 +140,7 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
SliverFillRemaining(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
Text(L10n.of(context).noEmotesFound),
const SizedBox(height: 12),

View file

@ -21,8 +21,9 @@ class TypingIndicators extends StatelessWidget {
return StreamBuilder<Object>(
stream: controller.room.client.onSync.stream.where(
(syncUpdate) =>
syncUpdate.rooms?.join?[controller.room.id]?.ephemeral
?.any((ephemeral) => ephemeral.type == 'm.typing') ??
syncUpdate.rooms?.join?[controller.room.id]?.ephemeral?.any(
(ephemeral) => ephemeral.type == 'm.typing',
) ??
false,
),
builder: (context, _) {
@ -33,22 +34,21 @@ class TypingIndicators extends StatelessWidget {
width: double.infinity,
alignment: Alignment.center,
child: AnimatedContainer(
constraints:
const BoxConstraints(maxWidth: FluffyThemes.maxTimelineWidth),
constraints: const BoxConstraints(
maxWidth: FluffyThemes.maxTimelineWidth,
),
height: typingUsers.isEmpty ? 0 : avatarSize + 8,
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
alignment: controller.timeline!.events.isNotEmpty &&
alignment:
controller.timeline!.events.isNotEmpty &&
controller.timeline!.events.first.senderId ==
Matrix.of(context).client.userID
? Alignment.topRight
: Alignment.topLeft,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 4.0,
),
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
child: Row(
children: [
Container(
@ -115,17 +115,14 @@ class __TypingDotsState extends State<_TypingDots> {
@override
void initState() {
_timer = Timer.periodic(
animationDuration,
(_) {
if (!mounted) {
return;
}
setState(() {
_tick = (_tick + 1) % 4;
});
},
);
_timer = Timer.periodic(animationDuration, (_) {
if (!mounted) {
return;
}
setState(() {
_tick = (_tick + 1) % 4;
});
});
super.initState();
}
@ -141,7 +138,7 @@ class __TypingDotsState extends State<_TypingDots> {
const size = 8.0;
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
for (var i = 1; i <= 3; i++)
AnimatedContainer(