refactor: Added and applied require_trailing_commas linter rule
This commit is contained in:
parent
adb3f766e5
commit
ec7acc5385
112 changed files with 3466 additions and 2855 deletions
|
|
@ -75,7 +75,8 @@ class AddWidgetTileState extends State<AddWidgetTile> {
|
|||
Navigator.of(context).pop();
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(L10n.of(context)!.errorAddingWidget)));
|
||||
SnackBar(content: Text(L10n.of(context)!.errorAddingWidget)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,12 +26,15 @@ class AddWidgetTileView extends StatelessWidget {
|
|||
'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(
|
||||
}.map(
|
||||
(key, value) => MapEntry(
|
||||
key,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: value,
|
||||
))),
|
||||
),
|
||||
),
|
||||
),
|
||||
onValueChanged: controller.setWidgetType,
|
||||
),
|
||||
Padding(
|
||||
|
|
|
|||
|
|
@ -82,10 +82,12 @@ class ChatController extends State<Chat> {
|
|||
|
||||
final matrixFiles = <MatrixFile>[];
|
||||
for (var i = 0; i < bytesList.result!.length; i++) {
|
||||
matrixFiles.add(MatrixFile(
|
||||
bytes: bytesList.result![i],
|
||||
name: details.files[i].name,
|
||||
).detectFileType);
|
||||
matrixFiles.add(
|
||||
MatrixFile(
|
||||
bytes: bytesList.result![i],
|
||||
name: details.files[i].name,
|
||||
).detectFileType,
|
||||
);
|
||||
}
|
||||
|
||||
await showDialog(
|
||||
|
|
@ -139,18 +141,20 @@ class ChatController extends State<Chat> {
|
|||
final userId = room?.directChatMatrixID;
|
||||
if (room == null || userId == null) {
|
||||
throw Exception(
|
||||
'Try to recreate a room with is not a DM room. This should not be possible from the UI!');
|
||||
'Try to recreate a room with is not a DM room. This should not be possible from the UI!',
|
||||
);
|
||||
}
|
||||
final success = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
final client = room.client;
|
||||
final waitForSync = client.onSync.stream
|
||||
.firstWhere((s) => s.rooms?.leave?.containsKey(room.id) ?? false);
|
||||
await room.leave();
|
||||
await waitForSync;
|
||||
return await client.startDirectChat(userId);
|
||||
});
|
||||
context: context,
|
||||
future: () async {
|
||||
final client = room.client;
|
||||
final waitForSync = client.onSync.stream
|
||||
.firstWhere((s) => s.rooms?.leave?.containsKey(room.id) ?? false);
|
||||
await room.leave();
|
||||
await waitForSync;
|
||||
return await client.startDirectChat(userId);
|
||||
},
|
||||
);
|
||||
final roomId = success.result;
|
||||
if (roomId == null) return;
|
||||
VRouter.of(context).toSegments(['rooms', roomId]);
|
||||
|
|
@ -160,7 +164,8 @@ class ChatController extends State<Chat> {
|
|||
final room = this.room;
|
||||
if (room == null) {
|
||||
throw Exception(
|
||||
'Leave room button clicked while room is null. This should not be possible from the UI!');
|
||||
'Leave room button clicked while room is null. This should not be possible from the UI!',
|
||||
);
|
||||
}
|
||||
final success = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
|
|
@ -327,10 +332,12 @@ class ChatController extends State<Chat> {
|
|||
}
|
||||
|
||||
// ignore: unawaited_futures
|
||||
room!.sendTextEvent(sendController.text,
|
||||
inReplyTo: replyEvent,
|
||||
editEventId: editEvent?.eventId,
|
||||
parseCommands: parseCommands);
|
||||
room!.sendTextEvent(
|
||||
sendController.text,
|
||||
inReplyTo: replyEvent,
|
||||
editEventId: editEvent?.eventId,
|
||||
parseCommands: parseCommands,
|
||||
);
|
||||
sendController.value = TextEditingValue(
|
||||
text: pendingText,
|
||||
selection: const TextSelection.collapsed(offset: 0),
|
||||
|
|
@ -354,10 +361,12 @@ class ChatController extends State<Chat> {
|
|||
useRootNavigator: false,
|
||||
builder: (c) => SendFileDialog(
|
||||
files: result
|
||||
.map((xfile) => MatrixFile(
|
||||
bytes: xfile.toUint8List(),
|
||||
name: xfile.fileName!,
|
||||
).detectFileType)
|
||||
.map(
|
||||
(xfile) => MatrixFile(
|
||||
bytes: xfile.toUint8List(),
|
||||
name: xfile.fileName!,
|
||||
).detectFileType,
|
||||
)
|
||||
.toList(),
|
||||
room: room!,
|
||||
),
|
||||
|
|
@ -375,10 +384,12 @@ class ChatController extends State<Chat> {
|
|||
useRootNavigator: false,
|
||||
builder: (c) => SendFileDialog(
|
||||
files: result
|
||||
.map((xfile) => MatrixFile(
|
||||
bytes: xfile.toUint8List(),
|
||||
name: xfile.fileName!,
|
||||
).detectFileType)
|
||||
.map(
|
||||
(xfile) => MatrixFile(
|
||||
bytes: xfile.toUint8List(),
|
||||
name: xfile.fileName!,
|
||||
).detectFileType,
|
||||
)
|
||||
.toList(),
|
||||
room: room!,
|
||||
),
|
||||
|
|
@ -537,8 +548,9 @@ class ChatController extends State<Chat> {
|
|||
for (final event in selectedEvents) {
|
||||
if (copyString.isNotEmpty) copyString += '\n\n';
|
||||
copyString += event.getDisplayEvent(timeline!).calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: true);
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: true,
|
||||
);
|
||||
}
|
||||
return copyString;
|
||||
}
|
||||
|
|
@ -554,33 +566,35 @@ class ChatController extends State<Chat> {
|
|||
void reportEventAction() async {
|
||||
final event = selectedEvents.single;
|
||||
final score = await showConfirmationDialog<int>(
|
||||
context: context,
|
||||
title: L10n.of(context)!.reportMessage,
|
||||
message: L10n.of(context)!.howOffensiveIsThisContent,
|
||||
cancelLabel: L10n.of(context)!.cancel,
|
||||
okLabel: L10n.of(context)!.ok,
|
||||
actions: [
|
||||
AlertDialogAction(
|
||||
key: -100,
|
||||
label: L10n.of(context)!.extremeOffensive,
|
||||
),
|
||||
AlertDialogAction(
|
||||
key: -50,
|
||||
label: L10n.of(context)!.offensive,
|
||||
),
|
||||
AlertDialogAction(
|
||||
key: 0,
|
||||
label: L10n.of(context)!.inoffensive,
|
||||
),
|
||||
]);
|
||||
context: context,
|
||||
title: L10n.of(context)!.reportMessage,
|
||||
message: L10n.of(context)!.howOffensiveIsThisContent,
|
||||
cancelLabel: L10n.of(context)!.cancel,
|
||||
okLabel: L10n.of(context)!.ok,
|
||||
actions: [
|
||||
AlertDialogAction(
|
||||
key: -100,
|
||||
label: L10n.of(context)!.extremeOffensive,
|
||||
),
|
||||
AlertDialogAction(
|
||||
key: -50,
|
||||
label: L10n.of(context)!.offensive,
|
||||
),
|
||||
AlertDialogAction(
|
||||
key: 0,
|
||||
label: L10n.of(context)!.inoffensive,
|
||||
),
|
||||
],
|
||||
);
|
||||
if (score == null) return;
|
||||
final reason = await showTextInputDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
title: L10n.of(context)!.whyDoYouWantToReportThis,
|
||||
okLabel: L10n.of(context)!.ok,
|
||||
cancelLabel: L10n.of(context)!.cancel,
|
||||
textFields: [DialogTextField(hintText: L10n.of(context)!.reason)]);
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
title: L10n.of(context)!.whyDoYouWantToReportThis,
|
||||
okLabel: L10n.of(context)!.ok,
|
||||
cancelLabel: L10n.of(context)!.cancel,
|
||||
textFields: [DialogTextField(hintText: L10n.of(context)!.reason)],
|
||||
);
|
||||
if (reason == null || reason.single.isEmpty) return;
|
||||
final result = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
|
|
@ -597,7 +611,8 @@ class ChatController extends State<Chat> {
|
|||
selectedEvents.clear();
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(L10n.of(context)!.contentHasBeenReported)));
|
||||
SnackBar(content: Text(L10n.of(context)!.contentHasBeenReported)),
|
||||
);
|
||||
}
|
||||
|
||||
void redactEventsAction() async {
|
||||
|
|
@ -612,25 +627,27 @@ class ChatController extends State<Chat> {
|
|||
if (!confirmed) return;
|
||||
for (final event in selectedEvents) {
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
if (event.status.isSent) {
|
||||
if (event.canRedact) {
|
||||
await event.redactEvent();
|
||||
} else {
|
||||
final client = currentRoomBundle.firstWhere(
|
||||
(cl) => selectedEvents.first.senderId == cl!.userID,
|
||||
orElse: () => null);
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
final room = client.getRoomById(roomId!)!;
|
||||
await Event.fromJson(event.toJson(), room).redactEvent();
|
||||
}
|
||||
context: context,
|
||||
future: () async {
|
||||
if (event.status.isSent) {
|
||||
if (event.canRedact) {
|
||||
await event.redactEvent();
|
||||
} else {
|
||||
await event.remove();
|
||||
final client = currentRoomBundle.firstWhere(
|
||||
(cl) => selectedEvents.first.senderId == cl!.userID,
|
||||
orElse: () => null,
|
||||
);
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
final room = client.getRoomById(roomId!)!;
|
||||
await Event.fromJson(event.toJson(), room).redactEvent();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await event.remove();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
setState(() {
|
||||
showEmojiPicker = false;
|
||||
|
|
@ -706,41 +723,42 @@ class ChatController extends State<Chat> {
|
|||
// event id not found...maybe we can fetch it?
|
||||
// the try...finally is here to start and close the loading dialog reliably
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
// okay, we first have to fetch if the event is in the room
|
||||
context: context,
|
||||
future: () async {
|
||||
// okay, we first have to fetch if the event is in the room
|
||||
try {
|
||||
final event = await timeline!.getEventById(eventId);
|
||||
if (event == null) {
|
||||
// event is null...meaning something is off
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
if (err is MatrixException && err.errcode == 'M_NOT_FOUND') {
|
||||
// event wasn't found, as the server gave a 404 or something
|
||||
return;
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
// okay, we know that the event *is* in the room
|
||||
while (eventIndex == -1) {
|
||||
if (!canLoadMore) {
|
||||
// we can't load any more events but still haven't found ours yet...better stop here
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final event = await timeline!.getEventById(eventId);
|
||||
if (event == null) {
|
||||
// event is null...meaning something is off
|
||||
return;
|
||||
}
|
||||
await timeline!.requestHistory(historyCount: _loadHistoryCount);
|
||||
} catch (err) {
|
||||
if (err is MatrixException && err.errcode == 'M_NOT_FOUND') {
|
||||
// event wasn't found, as the server gave a 404 or something
|
||||
if (err is TimeoutException) {
|
||||
// loading the history timed out...so let's do nothing
|
||||
return;
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
// okay, we know that the event *is* in the room
|
||||
while (eventIndex == -1) {
|
||||
if (!canLoadMore) {
|
||||
// we can't load any more events but still haven't found ours yet...better stop here
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await timeline!.requestHistory(historyCount: _loadHistoryCount);
|
||||
} catch (err) {
|
||||
if (err is TimeoutException) {
|
||||
// loading the history timed out...so let's do nothing
|
||||
return;
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
eventIndex =
|
||||
timeline!.events.indexWhere((e) => e.eventId == eventId);
|
||||
}
|
||||
});
|
||||
eventIndex =
|
||||
timeline!.events.indexWhere((e) => e.eventId == eventId);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
if (!mounted) {
|
||||
return;
|
||||
|
|
@ -811,7 +829,8 @@ class ChatController extends State<Chat> {
|
|||
sendController
|
||||
..text = sendController.text.characters.skipLast(1).toString()
|
||||
..selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: sendController.text.length));
|
||||
TextPosition(offset: sendController.text.length),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -846,8 +865,9 @@ class ChatController extends State<Chat> {
|
|||
|
||||
void editSelectedEventAction() {
|
||||
final client = currentRoomBundle.firstWhere(
|
||||
(cl) => selectedEvents.first.senderId == cl!.userID,
|
||||
orElse: () => null);
|
||||
(cl) => selectedEvents.first.senderId == cl!.userID,
|
||||
orElse: () => null,
|
||||
);
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -855,10 +875,12 @@ class ChatController extends State<Chat> {
|
|||
setState(() {
|
||||
pendingText = sendController.text;
|
||||
editEvent = selectedEvents.first;
|
||||
inputText = sendController.text = editEvent!
|
||||
.getDisplayEvent(timeline!)
|
||||
.calcLocalizedBodyFallback(MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: false, hideReply: true);
|
||||
inputText = sendController.text =
|
||||
editEvent!.getDisplayEvent(timeline!).calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: false,
|
||||
hideReply: true,
|
||||
);
|
||||
selectedEvents.clear();
|
||||
});
|
||||
inputFocus.requestFocus();
|
||||
|
|
@ -881,10 +903,12 @@ class ChatController extends State<Chat> {
|
|||
}
|
||||
final result = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => room!.client.joinRoom(room!
|
||||
.getState(EventTypes.RoomTombstone)!
|
||||
.parsedTombstoneContent
|
||||
.replacementRoom),
|
||||
future: () => room!.client.joinRoom(
|
||||
room!
|
||||
.getState(EventTypes.RoomTombstone)!
|
||||
.parsedTombstoneContent
|
||||
.replacementRoom,
|
||||
),
|
||||
);
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
|
|
@ -1077,9 +1101,10 @@ class ChatController extends State<Chat> {
|
|||
if (callType == null) return;
|
||||
|
||||
final success = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () =>
|
||||
Matrix.of(context).voipPlugin!.voip.requestTurnServerCredentials());
|
||||
context: context,
|
||||
future: () =>
|
||||
Matrix.of(context).voipPlugin!.voip.requestTurnServerCredentials(),
|
||||
);
|
||||
if (success.result != null) {
|
||||
final voipPlugin = Matrix.of(context).voipPlugin;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -86,19 +86,20 @@ class ChatEventList extends StatelessWidget {
|
|||
index: i - 1,
|
||||
controller: controller.scrollController,
|
||||
child: event.isVisibleInGui
|
||||
? Message(event,
|
||||
? Message(
|
||||
event,
|
||||
onSwipe: (direction) =>
|
||||
controller.replyAction(replyTo: event),
|
||||
onInfoTab: controller.showEventInfo,
|
||||
onAvatarTab: (Event event) => showAdaptiveBottomSheet(
|
||||
context: context,
|
||||
builder: (c) => UserBottomSheet(
|
||||
user: event.senderFromMemoryOrFallback,
|
||||
outerContext: context,
|
||||
onMention: () => controller.sendController.text +=
|
||||
'${event.senderFromMemoryOrFallback.mention} ',
|
||||
),
|
||||
),
|
||||
context: context,
|
||||
builder: (c) => UserBottomSheet(
|
||||
user: event.senderFromMemoryOrFallback,
|
||||
outerContext: context,
|
||||
onMention: () => controller.sendController.text +=
|
||||
'${event.senderFromMemoryOrFallback.mention} ',
|
||||
),
|
||||
),
|
||||
onSelect: controller.onSelectMessage,
|
||||
scrollToEventId: (String eventId) =>
|
||||
controller.scrollToEventId(eventId),
|
||||
|
|
@ -108,7 +109,8 @@ class ChatEventList extends StatelessWidget {
|
|||
timeline: controller.timeline!,
|
||||
nextEvent: i < controller.timeline!.events.length
|
||||
? controller.timeline!.events[i]
|
||||
: null)
|
||||
: null,
|
||||
)
|
||||
: Container(),
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -300,23 +300,24 @@ class _ChatAccountPicker extends StatelessWidget {
|
|||
builder: (context, snapshot) => PopupMenuButton<String>(
|
||||
onSelected: _popupMenuButtonSelected,
|
||||
itemBuilder: (BuildContext context) => clients
|
||||
.map((client) => PopupMenuItem<String>(
|
||||
value: client!.userID,
|
||||
child: FutureBuilder<Profile>(
|
||||
future: client.fetchOwnProfile(),
|
||||
builder: (context, snapshot) => ListTile(
|
||||
leading: Avatar(
|
||||
mxContent: snapshot.data?.avatarUrl,
|
||||
name: snapshot.data?.displayName ??
|
||||
client.userID!.localpart,
|
||||
size: 20,
|
||||
),
|
||||
title:
|
||||
Text(snapshot.data?.displayName ?? client.userID!),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
.map(
|
||||
(client) => PopupMenuItem<String>(
|
||||
value: client!.userID,
|
||||
child: FutureBuilder<Profile>(
|
||||
future: client.fetchOwnProfile(),
|
||||
builder: (context, snapshot) => ListTile(
|
||||
leading: Avatar(
|
||||
mxContent: snapshot.data?.avatarUrl,
|
||||
name: snapshot.data?.displayName ??
|
||||
client.userID!.localpart,
|
||||
size: 20,
|
||||
),
|
||||
title: Text(snapshot.data?.displayName ?? client.userID!),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
))
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
child: Avatar(
|
||||
mxContent: snapshot.data?.avatarUrl,
|
||||
|
|
|
|||
|
|
@ -49,11 +49,12 @@ class ChatView extends StatelessWidget {
|
|||
if (controller.canSaveSelectedEvent)
|
||||
// Use builder context to correctly position the share dialog on iPad
|
||||
Builder(
|
||||
builder: (context) => IconButton(
|
||||
icon: Icon(Icons.adaptive.share),
|
||||
tooltip: L10n.of(context)!.share,
|
||||
onPressed: () => controller.saveSelectedEvent(context),
|
||||
)),
|
||||
builder: (context) => IconButton(
|
||||
icon: Icon(Icons.adaptive.share),
|
||||
tooltip: L10n.of(context)!.share,
|
||||
onPressed: () => controller.saveSelectedEvent(context),
|
||||
),
|
||||
),
|
||||
if (controller.canRedactSelectedEvents)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
|
|
@ -155,7 +156,9 @@ class ChatView extends StatelessWidget {
|
|||
|
||||
if (controller.room!.membership == Membership.invite) {
|
||||
showFutureLoadingDialog(
|
||||
context: context, future: () => controller.room!.join());
|
||||
context: context,
|
||||
future: () => controller.room!.join(),
|
||||
);
|
||||
}
|
||||
final bottomSheetPadding = FluffyThemes.isColumnMode(context) ? 16.0 : 8.0;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
|
@ -249,21 +252,23 @@ class ChatView extends StatelessWidget {
|
|||
PinnedEvents(controller),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: controller.clearSingleSelectedEvent,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
if (controller.timeline == null) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator
|
||||
.adaptive(strokeWidth: 2),
|
||||
);
|
||||
}
|
||||
|
||||
return ChatEventList(
|
||||
controller: controller,
|
||||
onTap: controller.clearSingleSelectedEvent,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
if (controller.timeline == null) {
|
||||
return const Center(
|
||||
child:
|
||||
CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2,),
|
||||
);
|
||||
},
|
||||
)),
|
||||
}
|
||||
|
||||
return ChatEventList(
|
||||
controller: controller,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
if (controller.room!.canSendDefaultMessages &&
|
||||
controller.room!.membership == Membership.join)
|
||||
|
|
@ -274,7 +279,8 @@ class ChatView extends StatelessWidget {
|
|||
right: bottomSheetPadding,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 2.5),
|
||||
maxWidth: FluffyThemes.columnWidth * 2.5,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
borderRadius: const BorderRadius.only(
|
||||
|
|
@ -324,7 +330,8 @@ class ChatView extends StatelessWidget {
|
|||
onPressed:
|
||||
controller.recreateChat,
|
||||
label: Text(
|
||||
L10n.of(context)!.reopenChat),
|
||||
L10n.of(context)!.reopenChat,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -15,15 +15,18 @@ class EditWidgetsDialog extends StatelessWidget {
|
|||
return SimpleDialog(
|
||||
title: Text(L10n.of(context)!.editWidgets),
|
||||
children: [
|
||||
...room.widgets.map((e) => ListTile(
|
||||
title: Text(e.name ?? e.type),
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
room.deleteWidget(e.id!);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
icon: const Icon(Icons.delete)),
|
||||
)),
|
||||
...room.widgets.map(
|
||||
(e) => ListTile(
|
||||
title: Text(e.name ?? e.type),
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
room.deleteWidget(e.id!);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
icon: const Icon(Icons.delete),
|
||||
),
|
||||
),
|
||||
),
|
||||
AddWidgetTile(room: room),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,34 +13,34 @@ class EncryptionButton extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder<SyncUpdate>(
|
||||
stream: Matrix.of(context)
|
||||
.client
|
||||
.onSync
|
||||
.stream
|
||||
.where((s) => s.deviceLists != null),
|
||||
builder: (context, snapshot) {
|
||||
return FutureBuilder<EncryptionHealthState>(
|
||||
future: room.calcEncryptionHealthState(),
|
||||
builder: (BuildContext context, snapshot) => IconButton(
|
||||
tooltip: room.encrypted
|
||||
? L10n.of(context)!.encrypted
|
||||
: L10n.of(context)!.encryptionNotEnabled,
|
||||
icon: Icon(
|
||||
room.encrypted
|
||||
? Icons.lock_outlined
|
||||
: Icons.lock_open_outlined,
|
||||
size: 20,
|
||||
color: room.joinRules != JoinRules.public &&
|
||||
!room.encrypted
|
||||
? Colors.red
|
||||
: room.joinRules != JoinRules.public &&
|
||||
snapshot.data ==
|
||||
EncryptionHealthState.unverifiedDevices
|
||||
? Colors.orange
|
||||
: null),
|
||||
onPressed: () => VRouter.of(context)
|
||||
.toSegments(['rooms', room.id, 'encryption']),
|
||||
));
|
||||
});
|
||||
stream: Matrix.of(context)
|
||||
.client
|
||||
.onSync
|
||||
.stream
|
||||
.where((s) => s.deviceLists != null),
|
||||
builder: (context, snapshot) {
|
||||
return FutureBuilder<EncryptionHealthState>(
|
||||
future: room.calcEncryptionHealthState(),
|
||||
builder: (BuildContext context, snapshot) => IconButton(
|
||||
tooltip: room.encrypted
|
||||
? L10n.of(context)!.encrypted
|
||||
: L10n.of(context)!.encryptionNotEnabled,
|
||||
icon: Icon(
|
||||
room.encrypted ? Icons.lock_outlined : Icons.lock_open_outlined,
|
||||
size: 20,
|
||||
color: room.joinRules != JoinRules.public && !room.encrypted
|
||||
? Colors.red
|
||||
: room.joinRules != JoinRules.public &&
|
||||
snapshot.data ==
|
||||
EncryptionHealthState.unverifiedDevices
|
||||
? Colors.orange
|
||||
: null,
|
||||
),
|
||||
onPressed: () => VRouter.of(context)
|
||||
.toSegments(['rooms', room.id, 'encryption']),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,8 @@ class EventInfoDialog extends StatelessWidget {
|
|||
),
|
||||
title: Text(L10n.of(context)!.sender),
|
||||
subtitle: Text(
|
||||
'${event.senderFromMemoryOrFallback.calcDisplayname()} [${event.senderId}]'),
|
||||
'${event.senderFromMemoryOrFallback.calcDisplayname()} [${event.senderId}]',
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(L10n.of(context)!.time),
|
||||
|
|
|
|||
|
|
@ -69,7 +69,8 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
|||
if (!kIsWeb) {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final fileName = Uri.encodeComponent(
|
||||
widget.event.attachmentOrThumbnailMxcUrl()!.pathSegments.last);
|
||||
widget.event.attachmentOrThumbnailMxcUrl()!.pathSegments.last,
|
||||
);
|
||||
file = File('${tempDir.path}/${fileName}_${matrixFile.name}');
|
||||
await file.writeAsBytes(matrixFile.bytes);
|
||||
}
|
||||
|
|
@ -224,23 +225,27 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
|||
for (var i = 0; i < AudioPlayerWidget.wavesCount; i++)
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () => audioPlayer?.seek(Duration(
|
||||
onTap: () => audioPlayer?.seek(
|
||||
Duration(
|
||||
milliseconds:
|
||||
(maxPosition / AudioPlayerWidget.wavesCount)
|
||||
.round() *
|
||||
i)),
|
||||
i,
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
height: 32,
|
||||
alignment: Alignment.center,
|
||||
child: Opacity(
|
||||
opacity: currentPosition > i ? 1 : 0.5,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 1),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.color,
|
||||
borderRadius: BorderRadius.circular(64),
|
||||
),
|
||||
height: 32 * (waveform[i] / 1024)),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 1),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.color,
|
||||
borderRadius: BorderRadius.circular(64),
|
||||
),
|
||||
height: 32 * (waveform[i] / 1024),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -38,9 +38,14 @@ class HtmlMessage extends StatelessWidget {
|
|||
// miss-matching tags, and this way we actually correctly identify what we want to strip and, well,
|
||||
// strip it.
|
||||
final renderHtml = html.replaceAll(
|
||||
RegExp('<mx-reply>.*</mx-reply>',
|
||||
caseSensitive: false, multiLine: false, dotAll: true),
|
||||
'');
|
||||
RegExp(
|
||||
'<mx-reply>.*</mx-reply>',
|
||||
caseSensitive: false,
|
||||
multiLine: false,
|
||||
dotAll: true,
|
||||
),
|
||||
'',
|
||||
);
|
||||
|
||||
// there is no need to pre-validate the html, as we validate it while rendering
|
||||
|
||||
|
|
@ -61,8 +66,12 @@ class HtmlMessage extends StatelessWidget {
|
|||
maxLines: maxLines,
|
||||
onLinkTap: (url) => UrlLauncher(context, url).launchUrl(),
|
||||
onPillTap: (url) => UrlLauncher(context, url).launchUrl(),
|
||||
getMxcUrl: (String mxc, double? width, double? height,
|
||||
{bool? animated = false}) {
|
||||
getMxcUrl: (
|
||||
String mxc,
|
||||
double? width,
|
||||
double? height, {
|
||||
bool? animated = false,
|
||||
}) {
|
||||
final ratio = MediaQuery.of(context).devicePixelRatio;
|
||||
return Uri.parse(mxc)
|
||||
.getThumbnail(
|
||||
|
|
@ -75,19 +84,23 @@ class HtmlMessage extends StatelessWidget {
|
|||
.toString();
|
||||
},
|
||||
onImageTap: (String mxc) => showDialog(
|
||||
context: Matrix.of(context).navigatorContext,
|
||||
useRootNavigator: false,
|
||||
builder: (_) => ImageViewer(Event(
|
||||
type: EventTypes.Message,
|
||||
content: <String, dynamic>{
|
||||
'body': mxc,
|
||||
'url': mxc,
|
||||
'msgtype': MessageTypes.Image,
|
||||
},
|
||||
senderId: room.client.userID!,
|
||||
originServerTs: DateTime.now(),
|
||||
eventId: 'fake_event',
|
||||
room: room))),
|
||||
context: Matrix.of(context).navigatorContext,
|
||||
useRootNavigator: false,
|
||||
builder: (_) => ImageViewer(
|
||||
Event(
|
||||
type: EventTypes.Message,
|
||||
content: <String, dynamic>{
|
||||
'body': mxc,
|
||||
'url': mxc,
|
||||
'msgtype': MessageTypes.Image,
|
||||
},
|
||||
senderId: room.client.userID!,
|
||||
originServerTs: DateTime.now(),
|
||||
eventId: 'fake_event',
|
||||
room: room,
|
||||
),
|
||||
),
|
||||
),
|
||||
setCodeLanguage: (String key, String value) async {
|
||||
await matrix.store.setItem('${SettingKeys.codeLanguage}.$key', value);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -27,18 +27,19 @@ class Message extends StatelessWidget {
|
|||
final bool selected;
|
||||
final Timeline timeline;
|
||||
|
||||
const Message(this.event,
|
||||
{this.nextEvent,
|
||||
this.longPressSelect = false,
|
||||
this.onSelect,
|
||||
this.onInfoTab,
|
||||
this.onAvatarTab,
|
||||
this.scrollToEventId,
|
||||
required this.onSwipe,
|
||||
this.selected = false,
|
||||
required this.timeline,
|
||||
Key? key})
|
||||
: super(key: key);
|
||||
const Message(
|
||||
this.event, {
|
||||
this.nextEvent,
|
||||
this.longPressSelect = false,
|
||||
this.onSelect,
|
||||
this.onInfoTab,
|
||||
this.onAvatarTab,
|
||||
this.scrollToEventId,
|
||||
required this.onSwipe,
|
||||
this.selected = false,
|
||||
required this.timeline,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Indicates wheither the user may use a mouse instead
|
||||
/// of touchscreen.
|
||||
|
|
@ -126,13 +127,15 @@ class Message extends StatelessWidget {
|
|||
height: 16 * AppConfig.bubbleSizeFactor,
|
||||
child: event.status == EventStatus.sending
|
||||
? const CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2)
|
||||
strokeWidth: 2,
|
||||
)
|
||||
: event.status == EventStatus.error
|
||||
? const Icon(Icons.error, color: Colors.red)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
))
|
||||
),
|
||||
)
|
||||
: FutureBuilder<User?>(
|
||||
future: event.fetchSenderUser(),
|
||||
builder: (context, snapshot) {
|
||||
|
|
@ -142,7 +145,8 @@ class Message extends StatelessWidget {
|
|||
name: user.calcDisplayname(),
|
||||
onTap: () => onAvatarTab!(event),
|
||||
);
|
||||
}),
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
|
@ -171,7 +175,8 @@ class Message extends StatelessWidget {
|
|||
: displayname.lightColorText),
|
||||
),
|
||||
);
|
||||
}),
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
alignment: alignment,
|
||||
|
|
@ -198,7 +203,8 @@ class Message extends StatelessWidget {
|
|||
? EdgeInsets.zero
|
||||
: EdgeInsets.all(16 * AppConfig.bubbleSizeFactor),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 1.5),
|
||||
maxWidth: FluffyThemes.columnWidth * 1.5,
|
||||
),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Column(
|
||||
|
|
@ -233,11 +239,14 @@ class Message extends StatelessWidget {
|
|||
child: AbsorbPointer(
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(
|
||||
vertical: 4.0 *
|
||||
AppConfig.bubbleSizeFactor),
|
||||
child: ReplyContent(replyEvent,
|
||||
ownMessage: ownMessage,
|
||||
timeline: timeline),
|
||||
vertical:
|
||||
4.0 * AppConfig.bubbleSizeFactor,
|
||||
),
|
||||
child: ReplyContent(
|
||||
replyEvent,
|
||||
ownMessage: ownMessage,
|
||||
timeline: timeline,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -249,10 +258,13 @@ class Message extends StatelessWidget {
|
|||
onInfoTab: onInfoTab,
|
||||
),
|
||||
if (event.hasAggregatedEvents(
|
||||
timeline, RelationshipTypes.edit))
|
||||
timeline,
|
||||
RelationshipTypes.edit,
|
||||
))
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 4.0 * AppConfig.bubbleSizeFactor),
|
||||
top: 4.0 * AppConfig.bubbleSizeFactor,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
|
@ -301,26 +313,29 @@ class Message extends StatelessWidget {
|
|||
Padding(
|
||||
padding: displayTime
|
||||
? EdgeInsets.symmetric(
|
||||
vertical: 8.0 * AppConfig.bubbleSizeFactor)
|
||||
vertical: 8.0 * AppConfig.bubbleSizeFactor,
|
||||
)
|
||||
: EdgeInsets.zero,
|
||||
child: Center(
|
||||
child: Material(
|
||||
color: displayTime
|
||||
? Theme.of(context).colorScheme.background
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.background
|
||||
.withOpacity(0.33),
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: Text(
|
||||
event.originServerTs.localizedTime(context),
|
||||
style: TextStyle(fontSize: 14 * AppConfig.fontSizeFactor),
|
||||
child: Material(
|
||||
color: displayTime
|
||||
? Theme.of(context).colorScheme.background
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.background
|
||||
.withOpacity(0.33),
|
||||
borderRadius:
|
||||
BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: Text(
|
||||
event.originServerTs.localizedTime(context),
|
||||
style: TextStyle(fontSize: 14 * AppConfig.fontSizeFactor),
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
row,
|
||||
if (event.hasAggregatedEvents(timeline, RelationshipTypes.reaction))
|
||||
|
|
|
|||
|
|
@ -27,21 +27,27 @@ class MessageContent extends StatelessWidget {
|
|||
final Color textColor;
|
||||
final void Function(Event)? onInfoTab;
|
||||
|
||||
const MessageContent(this.event,
|
||||
{this.onInfoTab, Key? key, required this.textColor})
|
||||
: super(key: key);
|
||||
const MessageContent(
|
||||
this.event, {
|
||||
this.onInfoTab,
|
||||
Key? key,
|
||||
required this.textColor,
|
||||
}) : super(key: key);
|
||||
|
||||
void _verifyOrRequestKey(BuildContext context) async {
|
||||
final l10n = L10n.of(context)!;
|
||||
if (event.content['can_request_session'] != true) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
event.type == EventTypes.Encrypted
|
||||
? l10n.needPantalaimonWarning
|
||||
: event.calcLocalizedBodyFallback(
|
||||
MatrixLocals(l10n),
|
||||
),
|
||||
)));
|
||||
event.type == EventTypes.Encrypted
|
||||
? l10n.needPantalaimonWarning
|
||||
: event.calcLocalizedBodyFallback(
|
||||
MatrixLocals(l10n),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
final client = Matrix.of(context).client;
|
||||
|
|
@ -213,73 +219,83 @@ class MessageContent extends StatelessWidget {
|
|||
default:
|
||||
if (event.redacted) {
|
||||
return FutureBuilder<User?>(
|
||||
future: event.redactedBecause?.fetchSenderUser(),
|
||||
builder: (context, snapshot) {
|
||||
return _ButtonContent(
|
||||
label: L10n.of(context)!.redactedAnEvent(snapshot.data
|
||||
?.calcDisplayname() ??
|
||||
event.senderFromMemoryOrFallback.calcDisplayname()),
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
textColor: buttonTextColor,
|
||||
onPressed: () => onInfoTab!(event),
|
||||
);
|
||||
});
|
||||
future: event.redactedBecause?.fetchSenderUser(),
|
||||
builder: (context, snapshot) {
|
||||
return _ButtonContent(
|
||||
label: L10n.of(context)!.redactedAnEvent(
|
||||
snapshot.data?.calcDisplayname() ??
|
||||
event.senderFromMemoryOrFallback.calcDisplayname(),
|
||||
),
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
textColor: buttonTextColor,
|
||||
onPressed: () => onInfoTab!(event),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
final bigEmotes = event.onlyEmotes &&
|
||||
event.numberEmotes > 0 &&
|
||||
event.numberEmotes <= 10;
|
||||
return FutureBuilder<String>(
|
||||
future: event.calcLocalizedBody(MatrixLocals(L10n.of(context)!),
|
||||
hideReply: true),
|
||||
builder: (context, snapshot) {
|
||||
return LinkText(
|
||||
text: snapshot.data ??
|
||||
event.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
hideReply: true),
|
||||
textStyle: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: bigEmotes ? fontSize * 3 : fontSize,
|
||||
decoration:
|
||||
event.redacted ? TextDecoration.lineThrough : null,
|
||||
),
|
||||
linkStyle: TextStyle(
|
||||
color: textColor.withAlpha(150),
|
||||
fontSize: bigEmotes ? fontSize * 3 : fontSize,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: textColor.withAlpha(150),
|
||||
),
|
||||
onLinkTap: (url) => UrlLauncher(context, url).launchUrl(),
|
||||
);
|
||||
});
|
||||
future: event.calcLocalizedBody(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
hideReply: true,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
return LinkText(
|
||||
text: snapshot.data ??
|
||||
event.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
hideReply: true,
|
||||
),
|
||||
textStyle: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: bigEmotes ? fontSize * 3 : fontSize,
|
||||
decoration:
|
||||
event.redacted ? TextDecoration.lineThrough : null,
|
||||
),
|
||||
linkStyle: TextStyle(
|
||||
color: textColor.withAlpha(150),
|
||||
fontSize: bigEmotes ? fontSize * 3 : fontSize,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: textColor.withAlpha(150),
|
||||
),
|
||||
onLinkTap: (url) => UrlLauncher(context, url).launchUrl(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
case EventTypes.CallInvite:
|
||||
return FutureBuilder<User?>(
|
||||
future: event.fetchSenderUser(),
|
||||
builder: (context, snapshot) {
|
||||
return _ButtonContent(
|
||||
label: L10n.of(context)!.startedACall(
|
||||
snapshot.data?.calcDisplayname() ??
|
||||
event.senderFromMemoryOrFallback.calcDisplayname()),
|
||||
icon: const Icon(Icons.phone_outlined),
|
||||
textColor: buttonTextColor,
|
||||
onPressed: () => onInfoTab!(event),
|
||||
);
|
||||
});
|
||||
future: event.fetchSenderUser(),
|
||||
builder: (context, snapshot) {
|
||||
return _ButtonContent(
|
||||
label: L10n.of(context)!.startedACall(
|
||||
snapshot.data?.calcDisplayname() ??
|
||||
event.senderFromMemoryOrFallback.calcDisplayname(),
|
||||
),
|
||||
icon: const Icon(Icons.phone_outlined),
|
||||
textColor: buttonTextColor,
|
||||
onPressed: () => onInfoTab!(event),
|
||||
);
|
||||
},
|
||||
);
|
||||
default:
|
||||
return FutureBuilder<User?>(
|
||||
future: event.fetchSenderUser(),
|
||||
builder: (context, snapshot) {
|
||||
return _ButtonContent(
|
||||
label: L10n.of(context)!.userSentUnknownEvent(
|
||||
snapshot.data?.calcDisplayname() ??
|
||||
event.senderFromMemoryOrFallback.calcDisplayname(),
|
||||
event.type),
|
||||
icon: const Icon(Icons.info_outlined),
|
||||
textColor: buttonTextColor,
|
||||
onPressed: () => onInfoTab!(event),
|
||||
);
|
||||
});
|
||||
future: event.fetchSenderUser(),
|
||||
builder: (context, snapshot) {
|
||||
return _ButtonContent(
|
||||
label: L10n.of(context)!.userSentUnknownEvent(
|
||||
snapshot.data?.calcDisplayname() ??
|
||||
event.senderFromMemoryOrFallback.calcDisplayname(),
|
||||
event.type,
|
||||
),
|
||||
icon: const Icon(Icons.info_outlined),
|
||||
textColor: buttonTextColor,
|
||||
onPressed: () => onInfoTab!(event),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,45 +46,51 @@ class MessageReactions extends StatelessWidget {
|
|||
|
||||
final reactionList = reactionMap.values.toList();
|
||||
reactionList.sort((a, b) => b.count - a.count > 0 ? 1 : -1);
|
||||
return Wrap(spacing: 4.0, runSpacing: 4.0, children: [
|
||||
...reactionList
|
||||
.map(
|
||||
(r) => _Reaction(
|
||||
reactionKey: r.key,
|
||||
count: r.count,
|
||||
reacted: r.reacted,
|
||||
onTap: () {
|
||||
if (r.reacted) {
|
||||
final evt = allReactionEvents.firstWhereOrNull((e) =>
|
||||
e.senderId == e.room.client.userID &&
|
||||
e.content['m.relates_to']['key'] == r.key);
|
||||
if (evt != null) {
|
||||
showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => evt.redactEvent(),
|
||||
return Wrap(
|
||||
spacing: 4.0,
|
||||
runSpacing: 4.0,
|
||||
children: [
|
||||
...reactionList
|
||||
.map(
|
||||
(r) => _Reaction(
|
||||
reactionKey: r.key,
|
||||
count: r.count,
|
||||
reacted: r.reacted,
|
||||
onTap: () {
|
||||
if (r.reacted) {
|
||||
final evt = allReactionEvents.firstWhereOrNull(
|
||||
(e) =>
|
||||
e.senderId == e.room.client.userID &&
|
||||
e.content['m.relates_to']['key'] == r.key,
|
||||
);
|
||||
if (evt != null) {
|
||||
showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => evt.redactEvent(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
event.room.sendReaction(event.eventId, r.key!);
|
||||
}
|
||||
} else {
|
||||
event.room.sendReaction(event.eventId, r.key!);
|
||||
}
|
||||
},
|
||||
onLongPress: () async => await _AdaptableReactorsDialog(
|
||||
client: client,
|
||||
reactionEntry: r,
|
||||
).show(context),
|
||||
},
|
||||
onLongPress: () async => await _AdaptableReactorsDialog(
|
||||
client: client,
|
||||
reactionEntry: r,
|
||||
).show(context),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
if (allReactionEvents.any((e) => e.status.isSending))
|
||||
const SizedBox(
|
||||
width: 28,
|
||||
height: 28,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
child: CircularProgressIndicator.adaptive(strokeWidth: 1),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
if (allReactionEvents.any((e) => e.status.isSending))
|
||||
const SizedBox(
|
||||
width: 28,
|
||||
height: 28,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
child: CircularProgressIndicator.adaptive(strokeWidth: 1),
|
||||
),
|
||||
),
|
||||
]);
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -121,11 +127,13 @@ class _Reaction extends StatelessWidget {
|
|||
height: fontSize,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(count.toString(),
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: DefaultTextStyle.of(context).style.fontSize,
|
||||
)),
|
||||
Text(
|
||||
count.toString(),
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: DefaultTextStyle.of(context).style.fontSize,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
|
|
@ -133,11 +141,13 @@ class _Reaction extends StatelessWidget {
|
|||
if (renderKey.length > 10) {
|
||||
renderKey = renderKey.getRange(0, 9) + Characters('…');
|
||||
}
|
||||
content = Text('$renderKey $count',
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: DefaultTextStyle.of(context).style.fontSize,
|
||||
));
|
||||
content = Text(
|
||||
'$renderKey $count',
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: DefaultTextStyle.of(context).style.fontSize,
|
||||
),
|
||||
);
|
||||
}
|
||||
return InkWell(
|
||||
onTap: () => onTap != null ? onTap!() : null,
|
||||
|
|
|
|||
|
|
@ -84,21 +84,22 @@ class ReplyContent extends StatelessWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
FutureBuilder<User?>(
|
||||
future: displayEvent.fetchSenderUser(),
|
||||
builder: (context, snapshot) {
|
||||
return Text(
|
||||
'${snapshot.data?.calcDisplayname() ?? displayEvent.senderFromMemoryOrFallback.calcDisplayname()}:',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: ownMessage
|
||||
? Theme.of(context).colorScheme.onPrimary
|
||||
: Theme.of(context).colorScheme.onBackground,
|
||||
fontSize: fontSize,
|
||||
),
|
||||
);
|
||||
}),
|
||||
future: displayEvent.fetchSenderUser(),
|
||||
builder: (context, snapshot) {
|
||||
return Text(
|
||||
'${snapshot.data?.calcDisplayname() ?? displayEvent.senderFromMemoryOrFallback.calcDisplayname()}:',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: ownMessage
|
||||
? Theme.of(context).colorScheme.onPrimary
|
||||
: Theme.of(context).colorScheme.onBackground,
|
||||
fontSize: fontSize,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
replyBody,
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -25,22 +25,23 @@ class StateMessage extends StatelessWidget {
|
|||
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
),
|
||||
child: FutureBuilder<String>(
|
||||
future: event.calcLocalizedBody(MatrixLocals(L10n.of(context)!)),
|
||||
builder: (context, snapshot) {
|
||||
return Text(
|
||||
snapshot.data ??
|
||||
event.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14 * AppConfig.fontSizeFactor,
|
||||
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
decoration:
|
||||
event.redacted ? TextDecoration.lineThrough : null,
|
||||
),
|
||||
);
|
||||
}),
|
||||
future: event.calcLocalizedBody(MatrixLocals(L10n.of(context)!)),
|
||||
builder: (context, snapshot) {
|
||||
return Text(
|
||||
snapshot.data ??
|
||||
event.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14 * AppConfig.fontSizeFactor,
|
||||
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
decoration:
|
||||
event.redacted ? TextDecoration.lineThrough : null,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,9 +9,11 @@ class VerificationRequestContent extends StatelessWidget {
|
|||
final Event event;
|
||||
final Timeline timeline;
|
||||
|
||||
const VerificationRequestContent(
|
||||
{required this.event, required this.timeline, Key? key})
|
||||
: super(key: key);
|
||||
const VerificationRequestContent({
|
||||
required this.event,
|
||||
required this.timeline,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -43,18 +45,22 @@ class VerificationRequestContent extends StatelessWidget {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Icon(Icons.lock_outlined,
|
||||
color: canceled
|
||||
? Colors.red
|
||||
: (fullyDone ? Colors.green : Colors.grey)),
|
||||
Icon(
|
||||
Icons.lock_outlined,
|
||||
color: canceled
|
||||
? Colors.red
|
||||
: (fullyDone ? Colors.green : Colors.grey),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(canceled
|
||||
? 'Error ${cancel.first.content.tryGet<String>('code')}: ${cancel.first.content.tryGet<String>('reason')}'
|
||||
: (fullyDone
|
||||
? L10n.of(context)!.verifySuccess
|
||||
: (started
|
||||
? L10n.of(context)!.loadingPleaseWait
|
||||
: L10n.of(context)!.newVerificationRequest)))
|
||||
Text(
|
||||
canceled
|
||||
? 'Error ${cancel.first.content.tryGet<String>('code')}: ${cancel.first.content.tryGet<String>('reason')}'
|
||||
: (fullyDone
|
||||
? L10n.of(context)!.verifySuccess
|
||||
: (started
|
||||
? L10n.of(context)!.loadingPleaseWait
|
||||
: L10n.of(context)!.newVerificationRequest)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
|
|||
} else {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final fileName = Uri.encodeComponent(
|
||||
widget.event.attachmentOrThumbnailMxcUrl()!.pathSegments.last);
|
||||
widget.event.attachmentOrThumbnailMxcUrl()!.pathSegments.last,
|
||||
);
|
||||
final file = File('${tempDir.path}/${fileName}_${videoFile.name}');
|
||||
if (await file.exists() == false) {
|
||||
await file.writeAsBytes(videoFile.bytes);
|
||||
|
|
@ -62,13 +63,17 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
|
|||
);
|
||||
}
|
||||
} on MatrixConnectionException catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(e.toLocalizedString(context)),
|
||||
));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(e.toLocalizedString(context)),
|
||||
),
|
||||
);
|
||||
} catch (e, s) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(e.toLocalizedString(context)),
|
||||
));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(e.toLocalizedString(context)),
|
||||
),
|
||||
);
|
||||
Logs().w('Error while playing video', e, s);
|
||||
} finally {
|
||||
// Workaround for Chewie needs time to get the aspectRatio
|
||||
|
|
@ -120,14 +125,16 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
|
|||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2),
|
||||
strokeWidth: 2,
|
||||
),
|
||||
)
|
||||
: const Icon(Icons.download_outlined),
|
||||
label: Text(
|
||||
_isDownloading
|
||||
? L10n.of(context)!.loadingPleaseWait
|
||||
: L10n.of(context)!.videoWithSize(
|
||||
widget.event.sizeString ?? '?MB'),
|
||||
widget.event.sizeString ?? '?MB',
|
||||
),
|
||||
),
|
||||
onPressed: _isDownloading ? null : _downloadAction,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -117,8 +117,10 @@ class InputBar extends StatelessWidget {
|
|||
}
|
||||
// aside of emote packs, also propose normal (tm) unicode emojis
|
||||
final matchingUnicodeEmojis = Emoji.all()
|
||||
.where((element) => [element.name, ...element.keywords]
|
||||
.any((element) => element.toLowerCase().contains(emoteSearch)))
|
||||
.where(
|
||||
(element) => [element.name, ...element.keywords]
|
||||
.any((element) => element.toLowerCase().contains(emoteSearch)),
|
||||
)
|
||||
.toList();
|
||||
// sort by the index of the search term in the name in order to have
|
||||
// best matches first
|
||||
|
|
@ -186,12 +188,14 @@ class InputBar extends StatelessWidget {
|
|||
.toLowerCase()
|
||||
.contains(roomSearch)) ||
|
||||
(state.content['alt_aliases'] is List &&
|
||||
state.content['alt_aliases'].any((l) =>
|
||||
l is String &&
|
||||
l
|
||||
.split(':')[0]
|
||||
.toLowerCase()
|
||||
.contains(roomSearch))))) ||
|
||||
state.content['alt_aliases'].any(
|
||||
(l) =>
|
||||
l is String &&
|
||||
l
|
||||
.split(':')[0]
|
||||
.toLowerCase()
|
||||
.contains(roomSearch),
|
||||
)))) ||
|
||||
(r.name.toLowerCase().contains(roomSearch))) {
|
||||
ret.add({
|
||||
'type': 'room',
|
||||
|
|
@ -226,8 +230,10 @@ class InputBar extends StatelessWidget {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('/$command',
|
||||
style: const TextStyle(fontFamily: 'monospace')),
|
||||
Text(
|
||||
'/$command',
|
||||
style: const TextStyle(fontFamily: 'monospace'),
|
||||
),
|
||||
Text(
|
||||
hint,
|
||||
maxLines: 1,
|
||||
|
|
@ -273,8 +279,8 @@ class InputBar extends StatelessWidget {
|
|||
child: suggestion['pack_avatar_url'] != null
|
||||
? Avatar(
|
||||
mxContent: Uri.tryParse(
|
||||
suggestion.tryGet<String>('pack_avatar_url') ??
|
||||
''),
|
||||
suggestion.tryGet<String>('pack_avatar_url') ?? '',
|
||||
),
|
||||
name: suggestion.tryGet<String>('pack_display_name'),
|
||||
size: size * 0.9,
|
||||
client: client,
|
||||
|
|
@ -397,23 +403,27 @@ class InputBar extends StatelessWidget {
|
|||
actions: !useShortCuts
|
||||
? {}
|
||||
: {
|
||||
NewLineIntent: CallbackAction(onInvoke: (i) {
|
||||
final val = controller!.value;
|
||||
final selection = val.selection.start;
|
||||
final messageWithoutNewLine =
|
||||
'${controller!.text.substring(0, val.selection.start)}\n${controller!.text.substring(val.selection.end)}';
|
||||
controller!.value = TextEditingValue(
|
||||
text: messageWithoutNewLine,
|
||||
selection: TextSelection.fromPosition(
|
||||
TextPosition(offset: selection + 1),
|
||||
),
|
||||
);
|
||||
return null;
|
||||
}),
|
||||
SubmitLineIntent: CallbackAction(onInvoke: (i) {
|
||||
onSubmitted!(controller!.text);
|
||||
return null;
|
||||
}),
|
||||
NewLineIntent: CallbackAction(
|
||||
onInvoke: (i) {
|
||||
final val = controller!.value;
|
||||
final selection = val.selection.start;
|
||||
final messageWithoutNewLine =
|
||||
'${controller!.text.substring(0, val.selection.start)}\n${controller!.text.substring(val.selection.end)}';
|
||||
controller!.value = TextEditingValue(
|
||||
text: messageWithoutNewLine,
|
||||
selection: TextSelection.fromPosition(
|
||||
TextPosition(offset: selection + 1),
|
||||
),
|
||||
);
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SubmitLineIntent: CallbackAction(
|
||||
onInvoke: (i) {
|
||||
onSubmitted!(controller!.text);
|
||||
return null;
|
||||
},
|
||||
),
|
||||
},
|
||||
child: TypeAheadField<Map<String, String?>>(
|
||||
direction: AxisDirection.up,
|
||||
|
|
|
|||
|
|
@ -18,23 +18,28 @@ class PinnedEvents extends StatelessWidget {
|
|||
const PinnedEvents(this.controller, {Key? key}) : super(key: key);
|
||||
|
||||
Future<void> _displayPinnedEventsDialog(
|
||||
BuildContext context, List<Event?> events) async {
|
||||
BuildContext context,
|
||||
List<Event?> events,
|
||||
) async {
|
||||
final eventId = events.length == 1
|
||||
? events.single?.eventId
|
||||
: await showConfirmationDialog<String>(
|
||||
context: context,
|
||||
title: L10n.of(context)!.pinMessage,
|
||||
actions: events
|
||||
.map((event) => AlertDialogAction(
|
||||
key: event?.eventId ?? '',
|
||||
label: event?.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: true,
|
||||
hideReply: true,
|
||||
) ??
|
||||
'UNKNOWN',
|
||||
))
|
||||
.toList());
|
||||
.map(
|
||||
(event) => AlertDialogAction(
|
||||
key: event?.eventId ?? '',
|
||||
label: event?.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: true,
|
||||
hideReply: true,
|
||||
) ??
|
||||
'UNKNOWN',
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
if (eventId != null) controller.scrollToEventId(eventId);
|
||||
}
|
||||
|
|
@ -54,87 +59,86 @@ class PinnedEvents extends StatelessWidget {
|
|||
return completer;
|
||||
});
|
||||
return FutureBuilder<List<Event?>>(
|
||||
future: Future.wait(completers.map((e) => e.future).toList()),
|
||||
builder: (context, snapshot) {
|
||||
final pinnedEvents = snapshot.data;
|
||||
final event = (pinnedEvents != null && pinnedEvents.isNotEmpty)
|
||||
? snapshot.data?.last
|
||||
: null;
|
||||
future: Future.wait(completers.map((e) => e.future).toList()),
|
||||
builder: (context, snapshot) {
|
||||
final pinnedEvents = snapshot.data;
|
||||
final event = (pinnedEvents != null && pinnedEvents.isNotEmpty)
|
||||
? snapshot.data?.last
|
||||
: null;
|
||||
|
||||
if (event == null || pinnedEvents == null) {
|
||||
return Container();
|
||||
}
|
||||
if (event == null || pinnedEvents == null) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor;
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
child: InkWell(
|
||||
onTap: () => _displayPinnedEventsDialog(
|
||||
context,
|
||||
pinnedEvents,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
splashRadius: 20,
|
||||
iconSize: 20,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
icon: const Icon(Icons.push_pin),
|
||||
tooltip: L10n.of(context)!.unpin,
|
||||
onPressed: controller.room
|
||||
?.canSendEvent(EventTypes.RoomPinnedEvents) ??
|
||||
false
|
||||
? () => controller.unpinEvent(event.eventId)
|
||||
: null,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: FutureBuilder<String>(
|
||||
future: event.calcLocalizedBody(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: true,
|
||||
hideReply: true,
|
||||
final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor;
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
child: InkWell(
|
||||
onTap: () => _displayPinnedEventsDialog(
|
||||
context,
|
||||
pinnedEvents,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
splashRadius: 20,
|
||||
iconSize: 20,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
icon: const Icon(Icons.push_pin),
|
||||
tooltip: L10n.of(context)!.unpin,
|
||||
onPressed: controller.room
|
||||
?.canSendEvent(EventTypes.RoomPinnedEvents) ??
|
||||
false
|
||||
? () => controller.unpinEvent(event.eventId)
|
||||
: null,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: FutureBuilder<String>(
|
||||
future: event.calcLocalizedBody(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: true,
|
||||
hideReply: true,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
return LinkText(
|
||||
text: snapshot.data ??
|
||||
event.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: true,
|
||||
hideReply: true,
|
||||
),
|
||||
maxLines: 2,
|
||||
textStyle: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
fontSize: fontSize,
|
||||
decoration: event.redacted
|
||||
? TextDecoration.lineThrough
|
||||
: null,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
return LinkText(
|
||||
text: snapshot.data ??
|
||||
event.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: true,
|
||||
hideReply: true,
|
||||
),
|
||||
maxLines: 2,
|
||||
textStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
fontSize: fontSize,
|
||||
decoration: event.redacted
|
||||
? TextDecoration.lineThrough
|
||||
: null,
|
||||
),
|
||||
linkStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
fontSize: fontSize,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
),
|
||||
onLinkTap: (url) =>
|
||||
UrlLauncher(context, url).launchUrl(),
|
||||
);
|
||||
}),
|
||||
linkStyle: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
fontSize: fontSize,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
onLinkTap: (url) =>
|
||||
UrlLauncher(context, url).launchUrl(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,37 +26,45 @@ class ReactionsPicker extends StatelessWidget {
|
|||
height: (display) ? 56 : 0,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Builder(builder: (context) {
|
||||
if (!display) {
|
||||
return Container();
|
||||
}
|
||||
final proposals = proposeEmojis(
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
if (!display) {
|
||||
return Container();
|
||||
}
|
||||
final proposals = proposeEmojis(
|
||||
controller.selectedEvents.first.plaintextBody,
|
||||
number: 25,
|
||||
languageCodes: EmojiProposalLanguageCodes.values.toSet());
|
||||
final emojis = proposals.isNotEmpty
|
||||
? proposals.map((e) => e.char).toList()
|
||||
: List<String>.from(AppEmojis.emojis);
|
||||
final allReactionEvents = controller.selectedEvents.first
|
||||
.aggregatedEvents(
|
||||
controller.timeline!, RelationshipTypes.reaction)
|
||||
.where((event) =>
|
||||
event.senderId == event.room.client.userID &&
|
||||
event.type == 'm.reaction');
|
||||
languageCodes: EmojiProposalLanguageCodes.values.toSet(),
|
||||
);
|
||||
final emojis = proposals.isNotEmpty
|
||||
? proposals.map((e) => e.char).toList()
|
||||
: List<String>.from(AppEmojis.emojis);
|
||||
final allReactionEvents = controller.selectedEvents.first
|
||||
.aggregatedEvents(
|
||||
controller.timeline!,
|
||||
RelationshipTypes.reaction,
|
||||
)
|
||||
.where(
|
||||
(event) =>
|
||||
event.senderId == event.room.client.userID &&
|
||||
event.type == 'm.reaction',
|
||||
);
|
||||
|
||||
for (final event in allReactionEvents) {
|
||||
try {
|
||||
emojis.remove(event.content['m.relates_to']['key']);
|
||||
} catch (_) {}
|
||||
}
|
||||
return Row(children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
for (final event in allReactionEvents) {
|
||||
try {
|
||||
emojis.remove(event.content['m.relates_to']['key']);
|
||||
} catch (_) {}
|
||||
}
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).secondaryHeaderColor,
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomRight:
|
||||
Radius.circular(AppConfig.borderRadius))),
|
||||
color: Theme.of(context).secondaryHeaderColor,
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomRight: Radius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.only(right: 1),
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
|
|
@ -74,23 +82,28 @@ class ReactionsPicker extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
))),
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||
width: 36,
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).secondaryHeaderColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
child: const Icon(Icons.add_outlined),
|
||||
),
|
||||
onTap: () =>
|
||||
controller.pickEmojiReactionAction(allReactionEvents))
|
||||
]);
|
||||
}),
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||
width: 36,
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).secondaryHeaderColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.add_outlined),
|
||||
),
|
||||
onTap: () =>
|
||||
controller.pickEmojiReactionAction(allReactionEvents),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,7 +130,8 @@ class RecordingDialogState extends State<RecordingDialog> {
|
|||
.take(26)
|
||||
.toList()
|
||||
.reversed
|
||||
.map((amplitude) => Container(
|
||||
.map(
|
||||
(amplitude) => Container(
|
||||
margin: const EdgeInsets.only(left: 2),
|
||||
width: 4,
|
||||
decoration: BoxDecoration(
|
||||
|
|
@ -138,7 +139,9 @@ class RecordingDialogState extends State<RecordingDialog> {
|
|||
borderRadius:
|
||||
BorderRadius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
height: maxDecibalWidth * (amplitude / 100)))
|
||||
height: maxDecibalWidth * (amplitude / 100),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -33,10 +33,14 @@ class ReplyDisplay extends StatelessWidget {
|
|||
),
|
||||
Expanded(
|
||||
child: controller.replyEvent != null
|
||||
? ReplyContent(controller.replyEvent!,
|
||||
timeline: controller.timeline!)
|
||||
: _EditContent(controller.editEvent
|
||||
?.getDisplayEvent(controller.timeline!)),
|
||||
? ReplyContent(
|
||||
controller.replyEvent!,
|
||||
timeline: controller.timeline!,
|
||||
)
|
||||
: _EditContent(
|
||||
controller.editEvent
|
||||
?.getDisplayEvent(controller.timeline!),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -64,26 +68,27 @@ class _EditContent extends StatelessWidget {
|
|||
),
|
||||
Container(width: 15.0),
|
||||
FutureBuilder<String>(
|
||||
future: event.calcLocalizedBody(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: false,
|
||||
hideReply: true,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
return Text(
|
||||
snapshot.data ??
|
||||
event.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: false,
|
||||
hideReply: true,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodyMedium!.color,
|
||||
),
|
||||
);
|
||||
}),
|
||||
future: event.calcLocalizedBody(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: false,
|
||||
hideReply: true,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
return Text(
|
||||
snapshot.data ??
|
||||
event.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: false,
|
||||
hideReply: true,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodyMedium!.color,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,11 +33,12 @@ class SendFileDialogState extends State<SendFileDialog> {
|
|||
MatrixImageFile? thumbnail;
|
||||
if (file is MatrixVideoFile && file.bytes.length > minSizeToCompress) {
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
file = await file.resizeVideo();
|
||||
thumbnail = await file.getVideoThumbnail();
|
||||
});
|
||||
context: context,
|
||||
future: () async {
|
||||
file = await file.resizeVideo();
|
||||
thumbnail = await file.getVideoThumbnail();
|
||||
},
|
||||
);
|
||||
}
|
||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||
widget.room
|
||||
|
|
@ -79,26 +80,29 @@ class SendFileDialogState extends State<SendFileDialog> {
|
|||
}
|
||||
Widget contentWidget;
|
||||
if (allFilesAreImages) {
|
||||
contentWidget = Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
|
||||
Flexible(
|
||||
child: Image.memory(
|
||||
widget.files.first.bytes,
|
||||
fit: BoxFit.contain,
|
||||
contentWidget = Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: Image.memory(
|
||||
widget.files.first.bytes,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Checkbox(
|
||||
value: origImage,
|
||||
onChanged: (v) => setState(() => origImage = v ?? false),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () => setState(() => origImage = !origImage),
|
||||
child: Text('${L10n.of(context)!.sendOriginal} ($sizeString)'),
|
||||
),
|
||||
],
|
||||
)
|
||||
]);
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Checkbox(
|
||||
value: origImage,
|
||||
onChanged: (v) => setState(() => origImage = v ?? false),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () => setState(() => origImage = !origImage),
|
||||
child: Text('${L10n.of(context)!.sendOriginal} ($sizeString)'),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
contentWidget = Text('$fileName ($sizeString)');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,12 +28,13 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
|
|||
final pack = stickerPacks[packSlugs[packIndex]]!;
|
||||
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)));
|
||||
filteredImagePackImageEntried.removeWhere(
|
||||
(e) => !(e.key.toLowerCase().contains(searchFilter!.toLowerCase()) ||
|
||||
(e.value.body
|
||||
?.toLowerCase()
|
||||
.contains(searchFilter!.toLowerCase()) ??
|
||||
false)),
|
||||
);
|
||||
}
|
||||
final imageKeys =
|
||||
filteredImagePackImageEntried.map((e) => e.key).toList();
|
||||
|
|
@ -57,7 +58,8 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
|
|||
GridView.builder(
|
||||
itemCount: imageKeys.length,
|
||||
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 100),
|
||||
maxCrossAxisExtent: 100,
|
||||
),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (BuildContext context, int imageIndex) {
|
||||
|
|
@ -127,10 +129,11 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
|
|||
),
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
packBuilder,
|
||||
childCount: packSlugs.length,
|
||||
)),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
packBuilder,
|
||||
childCount: packSlugs.length,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue