refactor: Update to Dart 3.10 with . shorthands
This commit is contained in:
parent
75a37f3f7c
commit
1ea649f01e
167 changed files with 3351 additions and 3912 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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>(
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue