refactor: Migrate to null safety
This commit is contained in:
parent
5269f04a99
commit
55f0300f9f
163 changed files with 1759 additions and 1903 deletions
|
|
@ -34,31 +34,31 @@ import 'send_location_dialog.dart';
|
|||
import 'sticker_picker_dialog.dart';
|
||||
|
||||
class Chat extends StatefulWidget {
|
||||
final Widget sideView;
|
||||
final Widget? sideView;
|
||||
|
||||
const Chat({Key key, this.sideView}) : super(key: key);
|
||||
const Chat({Key? key, this.sideView}) : super(key: key);
|
||||
|
||||
@override
|
||||
ChatController createState() => ChatController();
|
||||
}
|
||||
|
||||
class ChatController extends State<Chat> {
|
||||
Room room;
|
||||
Room? room;
|
||||
|
||||
Client sendingClient;
|
||||
Client? sendingClient;
|
||||
|
||||
Timeline timeline;
|
||||
Timeline? timeline;
|
||||
|
||||
MatrixState matrix;
|
||||
MatrixState? matrix;
|
||||
|
||||
String get roomId => context.vRouter.pathParameters['roomid'];
|
||||
String? get roomId => context.vRouter.pathParameters['roomid'];
|
||||
|
||||
final AutoScrollController scrollController = AutoScrollController();
|
||||
|
||||
FocusNode inputFocus = FocusNode();
|
||||
|
||||
Timer typingCoolDown;
|
||||
Timer typingTimeout;
|
||||
Timer? typingCoolDown;
|
||||
Timer? typingTimeout;
|
||||
bool currentlyTyping = false;
|
||||
bool dragging = false;
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ class ChatController extends State<Chat> {
|
|||
bytes: bytes,
|
||||
name: xfile.name,
|
||||
).detectFileType,
|
||||
room: room,
|
||||
room: room!,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -96,13 +96,13 @@ class ChatController extends State<Chat> {
|
|||
|
||||
List<Event> selectedEvents = [];
|
||||
|
||||
List<Event> filteredEvents;
|
||||
late List<Event> filteredEvents;
|
||||
|
||||
final Set<String> unfolded = {};
|
||||
|
||||
Event replyEvent;
|
||||
Event? replyEvent;
|
||||
|
||||
Event editEvent;
|
||||
Event? editEvent;
|
||||
|
||||
bool showScrollDownButton = false;
|
||||
|
||||
|
|
@ -115,8 +115,8 @@ class ChatController extends State<Chat> {
|
|||
String pendingText = '';
|
||||
|
||||
bool get canLoadMore =>
|
||||
timeline.events.isEmpty ||
|
||||
timeline.events.last.type != EventTypes.RoomCreate;
|
||||
timeline!.events.isEmpty ||
|
||||
timeline!.events.last.type != EventTypes.RoomCreate;
|
||||
|
||||
bool showEmojiPicker = false;
|
||||
|
||||
|
|
@ -126,7 +126,7 @@ class ChatController extends State<Chat> {
|
|||
|
||||
final success = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => room.sendEvent({
|
||||
future: () => room!.sendEvent({
|
||||
'msgtype': Matrix.callNamespace,
|
||||
'body': url,
|
||||
}));
|
||||
|
|
@ -137,12 +137,12 @@ class ChatController extends State<Chat> {
|
|||
void requestHistory() async {
|
||||
if (canLoadMore) {
|
||||
try {
|
||||
await timeline.requestHistory(historyCount: _loadHistoryCount);
|
||||
await timeline!.requestHistory(historyCount: _loadHistoryCount);
|
||||
} catch (err) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
(err as Object).toLocalizedString(context),
|
||||
(err).toLocalizedString(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -157,8 +157,8 @@ class ChatController extends State<Chat> {
|
|||
}
|
||||
if (scrollController.position.pixels ==
|
||||
scrollController.position.maxScrollExtent &&
|
||||
timeline.events.isNotEmpty &&
|
||||
timeline.events[timeline.events.length - 1].type !=
|
||||
timeline!.events.isNotEmpty &&
|
||||
timeline!.events[timeline!.events.length - 1].type !=
|
||||
EventTypes.RoomCreate) {
|
||||
requestHistory();
|
||||
}
|
||||
|
|
@ -180,7 +180,7 @@ class ChatController extends State<Chat> {
|
|||
if (!mounted) return;
|
||||
setState(
|
||||
() {
|
||||
filteredEvents = timeline.getFilteredEvents(unfolded: unfolded);
|
||||
filteredEvents = timeline!.getFilteredEvents(unfolded: unfolded);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -192,22 +192,22 @@ class ChatController extends State<Chat> {
|
|||
unfolded.add(filteredEvents[i].eventId);
|
||||
i++;
|
||||
}
|
||||
filteredEvents = timeline.getFilteredEvents(unfolded: unfolded);
|
||||
filteredEvents = timeline!.getFilteredEvents(unfolded: unfolded);
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> getTimeline() async {
|
||||
if (timeline == null) {
|
||||
timeline = await room.getTimeline(onUpdate: updateView);
|
||||
if (timeline.events.isNotEmpty) {
|
||||
timeline = await room!.getTimeline(onUpdate: updateView);
|
||||
if (timeline!.events.isNotEmpty) {
|
||||
// ignore: unawaited_futures
|
||||
if (room.markedUnread) room.markUnread(false);
|
||||
if (room!.markedUnread) room!.markUnread(false);
|
||||
}
|
||||
|
||||
// when the scroll controller is attached we want to scroll to an event id, if specified
|
||||
// and update the scroll controller...which will trigger a request history, if the
|
||||
// "load more" button is visible on the screen
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) async {
|
||||
SchedulerBinding.instance!.addPostFrameCallback((_) async {
|
||||
if (mounted) {
|
||||
final event = VRouter.of(context).queryParameters['event'];
|
||||
if (event != null) {
|
||||
|
|
@ -217,16 +217,15 @@ class ChatController extends State<Chat> {
|
|||
}
|
||||
});
|
||||
}
|
||||
filteredEvents = timeline.getFilteredEvents(unfolded: unfolded);
|
||||
timeline.requestKeys();
|
||||
if (room.notificationCount != null &&
|
||||
room.notificationCount > 0 &&
|
||||
filteredEvents = timeline!.getFilteredEvents(unfolded: unfolded);
|
||||
timeline!.requestKeys();
|
||||
if (room!.notificationCount > 0 &&
|
||||
timeline != null &&
|
||||
timeline.events.isNotEmpty &&
|
||||
timeline!.events.isNotEmpty &&
|
||||
Matrix.of(context).webHasFocus) {
|
||||
// ignore: unawaited_futures
|
||||
timeline.setReadMarker();
|
||||
room.client.updateIosBadge();
|
||||
timeline!.setReadMarker();
|
||||
room!.client.updateIosBadge();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -240,13 +239,13 @@ class ChatController extends State<Chat> {
|
|||
|
||||
TextEditingController sendController = TextEditingController();
|
||||
|
||||
void setSendingClient(Client c) {
|
||||
void setSendingClient(Client? c) {
|
||||
// first cancle typing with the old sending client
|
||||
if (currentlyTyping) {
|
||||
// no need to have the setting typing to false be blocking
|
||||
typingCoolDown?.cancel();
|
||||
typingCoolDown = null;
|
||||
room.setTyping(false);
|
||||
room!.setTyping(false);
|
||||
currentlyTyping = false;
|
||||
}
|
||||
// then set the new sending client
|
||||
|
|
@ -263,22 +262,22 @@ class ChatController extends State<Chat> {
|
|||
|
||||
final commandMatch = RegExp(r'^\/(\w+)').firstMatch(sendController.text);
|
||||
if (commandMatch != null &&
|
||||
!room.client.commands.keys.contains(commandMatch[1].toLowerCase())) {
|
||||
final l10n = L10n.of(context);
|
||||
!room!.client.commands.keys.contains(commandMatch[1]!.toLowerCase())) {
|
||||
final l10n = L10n.of(context)!;
|
||||
final dialogResult = await showOkCancelAlertDialog(
|
||||
context: context,
|
||||
useRootNavigator: false,
|
||||
title: l10n.commandInvalid,
|
||||
message: l10n.commandMissing(commandMatch[0]),
|
||||
message: l10n.commandMissing(commandMatch[0]!),
|
||||
okLabel: l10n.sendAsText,
|
||||
cancelLabel: l10n.cancel,
|
||||
);
|
||||
if (dialogResult == null || dialogResult == OkCancelResult.cancel) return;
|
||||
if (dialogResult == OkCancelResult.cancel) return;
|
||||
parseCommands = false;
|
||||
}
|
||||
|
||||
// ignore: unawaited_futures
|
||||
room.sendTextEvent(sendController.text,
|
||||
room!.sendTextEvent(sendController.text,
|
||||
inReplyTo: replyEvent,
|
||||
editEventId: editEvent?.eventId,
|
||||
parseCommands: parseCommands);
|
||||
|
|
@ -298,16 +297,16 @@ class ChatController extends State<Chat> {
|
|||
void sendFileAction() async {
|
||||
final result =
|
||||
await FilePickerCross.importFromStorage(type: FileTypeCross.any);
|
||||
if (result == null) return;
|
||||
if (result.fileName == null) return;
|
||||
await showDialog(
|
||||
context: context,
|
||||
useRootNavigator: false,
|
||||
builder: (c) => SendFileDialog(
|
||||
file: MatrixFile(
|
||||
bytes: result.toUint8List(),
|
||||
name: result.fileName,
|
||||
name: result.fileName!,
|
||||
).detectFileType,
|
||||
room: room,
|
||||
room: room!,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -315,16 +314,16 @@ class ChatController extends State<Chat> {
|
|||
void sendImageAction() async {
|
||||
final result =
|
||||
await FilePickerCross.importFromStorage(type: FileTypeCross.image);
|
||||
if (result == null) return;
|
||||
if (result.fileName == null) return;
|
||||
await showDialog(
|
||||
context: context,
|
||||
useRootNavigator: false,
|
||||
builder: (c) => SendFileDialog(
|
||||
file: MatrixImageFile(
|
||||
bytes: result.toUint8List(),
|
||||
name: result.fileName,
|
||||
name: result.fileName!,
|
||||
),
|
||||
room: room,
|
||||
room: room!,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -343,7 +342,7 @@ class ChatController extends State<Chat> {
|
|||
bytes: bytes,
|
||||
name: file.path,
|
||||
),
|
||||
room: room,
|
||||
room: room!,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -362,7 +361,7 @@ class ChatController extends State<Chat> {
|
|||
bytes: bytes,
|
||||
name: file.path,
|
||||
),
|
||||
room: room,
|
||||
room: room!,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -371,7 +370,7 @@ class ChatController extends State<Chat> {
|
|||
final sticker = await showModalBottomSheet<ImagePackImageContent>(
|
||||
context: context,
|
||||
useRootNavigator: false,
|
||||
builder: (c) => StickerPickerDialog(room: room),
|
||||
builder: (c) => StickerPickerDialog(room: room!),
|
||||
);
|
||||
if (sticker == null) return;
|
||||
final eventContent = <String, dynamic>{
|
||||
|
|
@ -382,7 +381,7 @@ class ChatController extends State<Chat> {
|
|||
// send the sticker
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => room.sendEvent(
|
||||
future: () => room!.sendEvent(
|
||||
eventContent,
|
||||
type: EventTypes.Sticker,
|
||||
),
|
||||
|
|
@ -405,7 +404,7 @@ class ChatController extends State<Chat> {
|
|||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () =>
|
||||
room.sendFileEvent(file, inReplyTo: replyEvent, extraContent: {
|
||||
room!.sendFileEvent(file, inReplyTo: replyEvent, extraContent: {
|
||||
'info': {
|
||||
...file.info,
|
||||
'duration': result.duration,
|
||||
|
|
@ -426,7 +425,7 @@ class ChatController extends State<Chat> {
|
|||
await showDialog(
|
||||
context: context,
|
||||
useRootNavigator: false,
|
||||
builder: (c) => SendLocationDialog(room: room),
|
||||
builder: (c) => SendLocationDialog(room: room!),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -434,13 +433,13 @@ class ChatController extends State<Chat> {
|
|||
var copyString = '';
|
||||
if (selectedEvents.length == 1) {
|
||||
return selectedEvents.first
|
||||
.getDisplayEvent(timeline)
|
||||
.getLocalizedBody(MatrixLocals(L10n.of(context)));
|
||||
.getDisplayEvent(timeline!)
|
||||
.getLocalizedBody(MatrixLocals(L10n.of(context)!));
|
||||
}
|
||||
for (final event in selectedEvents) {
|
||||
if (copyString.isNotEmpty) copyString += '\n\n';
|
||||
copyString += event.getDisplayEvent(timeline).getLocalizedBody(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
copyString += event.getDisplayEvent(timeline!).getLocalizedBody(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: true);
|
||||
}
|
||||
return copyString;
|
||||
|
|
@ -458,37 +457,37 @@ class ChatController extends State<Chat> {
|
|||
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,
|
||||
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,
|
||||
label: L10n.of(context)!.extremeOffensive,
|
||||
),
|
||||
AlertDialogAction(
|
||||
key: -50,
|
||||
label: L10n.of(context).offensive,
|
||||
label: L10n.of(context)!.offensive,
|
||||
),
|
||||
AlertDialogAction(
|
||||
key: 0,
|
||||
label: L10n.of(context).inoffensive,
|
||||
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)]);
|
||||
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,
|
||||
future: () => Matrix.of(context).client.reportContent(
|
||||
event.roomId,
|
||||
event.roomId!,
|
||||
event.eventId,
|
||||
reason: reason.single,
|
||||
score: score,
|
||||
|
|
@ -500,16 +499,16 @@ 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 {
|
||||
final confirmed = await showOkCancelAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
title: L10n.of(context).messageWillBeRemovedWarning,
|
||||
okLabel: L10n.of(context).remove,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
title: L10n.of(context)!.messageWillBeRemovedWarning,
|
||||
okLabel: L10n.of(context)!.remove,
|
||||
cancelLabel: L10n.of(context)!.cancel,
|
||||
) ==
|
||||
OkCancelResult.ok;
|
||||
if (!confirmed) return;
|
||||
|
|
@ -522,12 +521,12 @@ class ChatController extends State<Chat> {
|
|||
await event.redactEvent();
|
||||
} else {
|
||||
final client = currentRoomBundle.firstWhere(
|
||||
(cl) => selectedEvents.first.senderId == cl.userID,
|
||||
(cl) => selectedEvents.first.senderId == cl!.userID,
|
||||
orElse: () => null);
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
final room = client.getRoomById(roomId);
|
||||
final room = client.getRoomById(roomId!)!;
|
||||
await Event.fromJson(event.toJson(), room).redactEvent();
|
||||
}
|
||||
} else {
|
||||
|
|
@ -541,17 +540,17 @@ class ChatController extends State<Chat> {
|
|||
});
|
||||
}
|
||||
|
||||
List<Client> get currentRoomBundle {
|
||||
final clients = matrix.currentBundle;
|
||||
clients.removeWhere((c) => c.getRoomById(roomId) == null);
|
||||
List<Client?> get currentRoomBundle {
|
||||
final clients = matrix!.currentBundle!;
|
||||
clients.removeWhere((c) => c!.getRoomById(roomId!) == null);
|
||||
return clients;
|
||||
}
|
||||
|
||||
bool get canRedactSelectedEvents {
|
||||
final clients = matrix.currentBundle;
|
||||
final clients = matrix!.currentBundle;
|
||||
for (final event in selectedEvents) {
|
||||
if (event.canRedact == false &&
|
||||
!(clients.any((cl) => event.senderId == cl.userID))) return false;
|
||||
!(clients!.any((cl) => event.senderId == cl!.userID))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -561,7 +560,7 @@ class ChatController extends State<Chat> {
|
|||
return false;
|
||||
}
|
||||
return currentRoomBundle
|
||||
.any((cl) => selectedEvents.first.senderId == cl.userID);
|
||||
.any((cl) => selectedEvents.first.senderId == cl!.userID);
|
||||
}
|
||||
|
||||
void forwardEventsAction() async {
|
||||
|
|
@ -583,7 +582,7 @@ class ChatController extends State<Chat> {
|
|||
event.sendAgain();
|
||||
}
|
||||
final allEditEvents = event
|
||||
.aggregatedEvents(timeline, RelationshipTypes.edit)
|
||||
.aggregatedEvents(timeline!, RelationshipTypes.edit)
|
||||
.where((e) => e.status.isError);
|
||||
for (final e in allEditEvents) {
|
||||
e.sendAgain();
|
||||
|
|
@ -591,7 +590,7 @@ class ChatController extends State<Chat> {
|
|||
setState(() => selectedEvents.clear());
|
||||
}
|
||||
|
||||
void replyAction({Event replyTo}) {
|
||||
void replyAction({Event? replyTo}) {
|
||||
setState(() {
|
||||
replyEvent = replyTo ?? selectedEvents.first;
|
||||
selectedEvents.clear();
|
||||
|
|
@ -609,7 +608,7 @@ class ChatController extends State<Chat> {
|
|||
future: () async {
|
||||
// okay, we first have to fetch if the event is in the room
|
||||
try {
|
||||
final event = await timeline.getEventById(eventId);
|
||||
final event = await timeline!.getEventById(eventId);
|
||||
if (event == null) {
|
||||
// event is null...meaning something is off
|
||||
return;
|
||||
|
|
@ -628,7 +627,7 @@ class ChatController extends State<Chat> {
|
|||
return;
|
||||
}
|
||||
try {
|
||||
await timeline.requestHistory(historyCount: _loadHistoryCount);
|
||||
await timeline!.requestHistory(historyCount: _loadHistoryCount);
|
||||
} catch (err) {
|
||||
if (err is TimeoutException) {
|
||||
// loading the history timed out...so let's do nothing
|
||||
|
|
@ -662,7 +661,7 @@ class ChatController extends State<Chat> {
|
|||
return sendEmojiAction(emoji.emoji);
|
||||
}
|
||||
|
||||
Iterable<Event> _allReactionEvents;
|
||||
late Iterable<Event> _allReactionEvents;
|
||||
|
||||
void cancelEmojiPicker() => setState(() => showEmojiPicker = false);
|
||||
|
||||
|
|
@ -671,13 +670,13 @@ class ChatController extends State<Chat> {
|
|||
setState(() => showEmojiPicker = true);
|
||||
}
|
||||
|
||||
void sendEmojiAction(String emoji) async {
|
||||
void sendEmojiAction(String? emoji) async {
|
||||
final events = List<Event>.from(selectedEvents);
|
||||
setState(() => selectedEvents.clear());
|
||||
for (final event in events) {
|
||||
await room.sendReaction(
|
||||
await room!.sendReaction(
|
||||
event.eventId,
|
||||
emoji,
|
||||
emoji!,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -695,7 +694,7 @@ class ChatController extends State<Chat> {
|
|||
|
||||
void editSelectedEventAction() {
|
||||
final client = currentRoomBundle.firstWhere(
|
||||
(cl) => selectedEvents.first.senderId == cl.userID,
|
||||
(cl) => selectedEvents.first.senderId == cl!.userID,
|
||||
orElse: () => null);
|
||||
if (client == null) {
|
||||
return;
|
||||
|
|
@ -704,9 +703,9 @@ class ChatController extends State<Chat> {
|
|||
setState(() {
|
||||
pendingText = sendController.text;
|
||||
editEvent = selectedEvents.first;
|
||||
inputText = sendController.text = editEvent
|
||||
.getDisplayEvent(timeline)
|
||||
.getLocalizedBody(MatrixLocals(L10n.of(context)),
|
||||
inputText = sendController.text = editEvent!
|
||||
.getDisplayEvent(timeline!)
|
||||
.getLocalizedBody(MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: false, hideReply: true);
|
||||
selectedEvents.clear();
|
||||
});
|
||||
|
|
@ -718,29 +717,29 @@ class ChatController extends State<Chat> {
|
|||
await showOkCancelAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
title: L10n.of(context).goToTheNewRoom,
|
||||
message: room
|
||||
.getState(EventTypes.RoomTombstone)
|
||||
title: L10n.of(context)!.goToTheNewRoom,
|
||||
message: room!
|
||||
.getState(EventTypes.RoomTombstone)!
|
||||
.parsedTombstoneContent
|
||||
.body,
|
||||
okLabel: L10n.of(context).ok,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
okLabel: L10n.of(context)!.ok,
|
||||
cancelLabel: L10n.of(context)!.cancel,
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
final result = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => room.client.joinRoom(room
|
||||
.getState(EventTypes.RoomTombstone)
|
||||
future: () => room!.client.joinRoom(room!
|
||||
.getState(EventTypes.RoomTombstone)!
|
||||
.parsedTombstoneContent
|
||||
.replacementRoom),
|
||||
);
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: room.leave,
|
||||
future: room!.leave,
|
||||
);
|
||||
if (result.error == null) {
|
||||
VRouter.of(context).toSegments(['rooms', result.result]);
|
||||
VRouter.of(context).toSegments(['rooms', result.result!]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -761,12 +760,12 @@ class ChatController extends State<Chat> {
|
|||
}
|
||||
}
|
||||
|
||||
int findChildIndexCallback(Key key, Map<String, int> thisEventsKeyMap) {
|
||||
int? findChildIndexCallback(Key key, Map<String, int> thisEventsKeyMap) {
|
||||
// this method is called very often. As such, it has to be optimized for speed.
|
||||
if (key is! ValueKey) {
|
||||
return null;
|
||||
}
|
||||
final eventId = (key as ValueKey).value;
|
||||
final eventId = key.value;
|
||||
if (eventId is! String) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -809,11 +808,11 @@ class ChatController extends State<Chat> {
|
|||
}
|
||||
|
||||
void onInputBarChanged(String text) {
|
||||
if (text.endsWith(' ') && matrix.hasComplexBundles) {
|
||||
if (text.endsWith(' ') && matrix!.hasComplexBundles) {
|
||||
final clients = currentRoomBundle;
|
||||
for (final client in clients) {
|
||||
final prefix = client.sendPrefix;
|
||||
if ((prefix?.isNotEmpty ?? false) &&
|
||||
final prefix = client!.sendPrefix;
|
||||
if ((prefix.isNotEmpty) &&
|
||||
text.toLowerCase() == '${prefix.toLowerCase()} ') {
|
||||
setSendingClient(client);
|
||||
setState(() {
|
||||
|
|
@ -828,7 +827,7 @@ class ChatController extends State<Chat> {
|
|||
typingCoolDown = Timer(const Duration(seconds: 2), () {
|
||||
typingCoolDown = null;
|
||||
currentlyTyping = false;
|
||||
room.setTyping(false);
|
||||
room!.setTyping(false);
|
||||
});
|
||||
typingTimeout ??= Timer(const Duration(seconds: 30), () {
|
||||
typingTimeout = null;
|
||||
|
|
@ -836,12 +835,13 @@ class ChatController extends State<Chat> {
|
|||
});
|
||||
if (!currentlyTyping) {
|
||||
currentlyTyping = true;
|
||||
room.setTyping(true, timeout: const Duration(seconds: 30).inMilliseconds);
|
||||
room!
|
||||
.setTyping(true, timeout: const Duration(seconds: 30).inMilliseconds);
|
||||
}
|
||||
setState(() => inputText = text);
|
||||
}
|
||||
|
||||
void showEventInfo([Event event]) =>
|
||||
void showEventInfo([Event? event]) =>
|
||||
(event ?? selectedEvents.single).showInfoDialog(context);
|
||||
|
||||
void cancelReplyEventAction() => setState(() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
//@dart=2.12
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
|
@ -19,41 +17,42 @@ class ChatAppBarTitle extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final room = controller.room;
|
||||
if (room == null) {
|
||||
return Container();
|
||||
}
|
||||
if (controller.selectedEvents.isNotEmpty) {
|
||||
return Text(controller.selectedEvents.length.toString());
|
||||
}
|
||||
final directChatMatrixID = controller.room.directChatMatrixID;
|
||||
final directChatMatrixID = room.directChatMatrixID;
|
||||
return ListTile(
|
||||
leading: Avatar(
|
||||
mxContent: controller.room.avatar,
|
||||
name: controller.room.displayname,
|
||||
mxContent: room.avatar,
|
||||
name: room.displayname,
|
||||
),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
onTap: directChatMatrixID != null
|
||||
? () => showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (c) => UserBottomSheet(
|
||||
user: controller.room.getUserByMXIDSync(directChatMatrixID),
|
||||
user: room.getUserByMXIDSync(directChatMatrixID),
|
||||
outerContext: context,
|
||||
onMention: () => controller.sendController.text +=
|
||||
'${controller.room.getUserByMXIDSync(directChatMatrixID).mention} ',
|
||||
'${room.getUserByMXIDSync(directChatMatrixID).mention} ',
|
||||
),
|
||||
)
|
||||
: () => VRouter.of(context)
|
||||
.toSegments(['rooms', controller.room.id, 'details']),
|
||||
title: Text(
|
||||
controller.room
|
||||
.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
|
||||
: () => VRouter.of(context).toSegments(['rooms', room.id, 'details']),
|
||||
title: Text(room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
|
||||
maxLines: 1),
|
||||
subtitle: StreamBuilder<Object>(
|
||||
stream: Matrix.of(context)
|
||||
.client
|
||||
.onPresence
|
||||
.stream
|
||||
.where((p) => p.senderId == controller.room.directChatMatrixID)
|
||||
.where((p) => p.senderId == room.directChatMatrixID)
|
||||
.rateLimit(const Duration(seconds: 1)),
|
||||
builder: (context, snapshot) => Text(
|
||||
controller.room.getLocalizedStatus(context),
|
||||
room.getLocalizedStatus(context),
|
||||
maxLines: 1,
|
||||
//overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
//@dart=2.12
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import 'input_bar.dart';
|
|||
|
||||
class ChatInputRow extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
const ChatInputRow(this.controller, {Key key}) : super(key: key);
|
||||
const ChatInputRow(this.controller, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -30,14 +30,14 @@ class ChatInputRow extends StatelessWidget {
|
|||
child: Row(
|
||||
children: <Widget>[
|
||||
const Icon(Icons.keyboard_arrow_left_outlined),
|
||||
Text(L10n.of(context).forward),
|
||||
Text(L10n.of(context)!.forward),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
controller.selectedEvents.length == 1
|
||||
? controller.selectedEvents.first
|
||||
.getDisplayEvent(controller.timeline)
|
||||
.getDisplayEvent(controller.timeline!)
|
||||
.status
|
||||
.isSent
|
||||
? SizedBox(
|
||||
|
|
@ -46,7 +46,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
onPressed: controller.replyAction,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Text(L10n.of(context).reply),
|
||||
Text(L10n.of(context)!.reply),
|
||||
const Icon(Icons.keyboard_arrow_right),
|
||||
],
|
||||
),
|
||||
|
|
@ -58,7 +58,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
onPressed: controller.sendAgainAction,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Text(L10n.of(context).tryToSendAgain),
|
||||
Text(L10n.of(context)!.tryToSendAgain),
|
||||
const SizedBox(width: 4),
|
||||
const Icon(Icons.send_outlined, size: 16),
|
||||
],
|
||||
|
|
@ -88,7 +88,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.video_call_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).videoCall),
|
||||
title: Text(L10n.of(context)!.videoCall),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
|
|
@ -100,7 +100,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.attachment_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendFile),
|
||||
title: Text(L10n.of(context)!.sendFile),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
|
|
@ -112,7 +112,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.image_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendImage),
|
||||
title: Text(L10n.of(context)!.sendImage),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
|
|
@ -125,7 +125,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.camera_alt_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).openCamera),
|
||||
title: Text(L10n.of(context)!.openCamera),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
|
|
@ -138,11 +138,11 @@ class ChatInputRow extends StatelessWidget {
|
|||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.videocam_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).openVideoCamera),
|
||||
title: Text(L10n.of(context)!.openVideoCamera),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
if (controller.room
|
||||
if (controller.room!
|
||||
.getImagePacks(ImagePackUsage.sticker)
|
||||
.isNotEmpty)
|
||||
PopupMenuItem<String>(
|
||||
|
|
@ -153,7 +153,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.emoji_emotions_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendSticker),
|
||||
title: Text(L10n.of(context)!.sendSticker),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
|
|
@ -166,7 +166,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.gps_fixed_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).shareLocation),
|
||||
title: Text(L10n.of(context)!.shareLocation),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
|
|
@ -176,11 +176,11 @@ class ChatInputRow extends StatelessWidget {
|
|||
Container(
|
||||
height: 56,
|
||||
alignment: Alignment.center,
|
||||
child: EncryptionButton(controller.room),
|
||||
child: EncryptionButton(controller.room!),
|
||||
),
|
||||
if (controller.matrix.isMultiAccount &&
|
||||
controller.matrix.hasComplexBundles &&
|
||||
controller.matrix.currentBundle.length > 1)
|
||||
if (controller.matrix!.isMultiAccount &&
|
||||
controller.matrix!.hasComplexBundles &&
|
||||
controller.matrix!.currentBundle!.length > 1)
|
||||
Container(
|
||||
height: 56,
|
||||
alignment: Alignment.center,
|
||||
|
|
@ -190,7 +190,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: InputBar(
|
||||
room: controller.room,
|
||||
room: controller.room!,
|
||||
minLines: 1,
|
||||
maxLines: 8,
|
||||
autofocus: !PlatformInfos.isMobile,
|
||||
|
|
@ -201,7 +201,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
focusNode: controller.inputFocus,
|
||||
controller: controller.sendController,
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.of(context).writeAMessage,
|
||||
hintText: L10n.of(context)!.writeAMessage,
|
||||
hintMaxLines: 1,
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
|
|
@ -216,7 +216,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
height: 56,
|
||||
alignment: Alignment.center,
|
||||
child: IconButton(
|
||||
tooltip: L10n.of(context).voiceMessage,
|
||||
tooltip: L10n.of(context)!.voiceMessage,
|
||||
icon: const Icon(Icons.mic_none_outlined),
|
||||
onPressed: controller.voiceMessageAction,
|
||||
),
|
||||
|
|
@ -228,7 +228,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
child: IconButton(
|
||||
icon: const Icon(Icons.send_outlined),
|
||||
onPressed: controller.send,
|
||||
tooltip: L10n.of(context).send,
|
||||
tooltip: L10n.of(context)!.send,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -239,11 +239,11 @@ class ChatInputRow extends StatelessWidget {
|
|||
class _ChatAccountPicker extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
|
||||
const _ChatAccountPicker(this.controller, {Key key}) : super(key: key);
|
||||
const _ChatAccountPicker(this.controller, {Key? key}) : super(key: key);
|
||||
|
||||
void _popupMenuButtonSelected(String mxid) {
|
||||
final client = controller.matrix.currentBundle
|
||||
.firstWhere((cl) => cl.userID == mxid, orElse: () => null);
|
||||
final client = controller.matrix!.currentBundle!
|
||||
.firstWhere((cl) => cl!.userID == mxid, orElse: () => null);
|
||||
if (client == null) {
|
||||
Logs().w('Attempted to switch to a non-existing client $mxid');
|
||||
return;
|
||||
|
|
@ -258,23 +258,23 @@ class _ChatAccountPicker extends StatelessWidget {
|
|||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: FutureBuilder<Profile>(
|
||||
future: controller.sendingClient.ownProfile,
|
||||
future: controller.sendingClient!.ownProfile,
|
||||
builder: (context, snapshot) => PopupMenuButton<String>(
|
||||
onSelected: _popupMenuButtonSelected,
|
||||
itemBuilder: (BuildContext context) => clients
|
||||
.map((client) => PopupMenuItem<String>(
|
||||
value: client.userID,
|
||||
value: client!.userID,
|
||||
child: FutureBuilder<Profile>(
|
||||
future: client.ownProfile,
|
||||
builder: (context, snapshot) => ListTile(
|
||||
leading: Avatar(
|
||||
mxContent: snapshot.data?.avatarUrl,
|
||||
name: snapshot.data?.displayName ??
|
||||
client.userID.localpart,
|
||||
client.userID!.localpart,
|
||||
size: 20,
|
||||
),
|
||||
title:
|
||||
Text(snapshot.data?.displayName ?? client.userID),
|
||||
Text(snapshot.data?.displayName ?? client.userID!),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
|
|
@ -283,7 +283,7 @@ class _ChatAccountPicker extends StatelessWidget {
|
|||
child: Avatar(
|
||||
mxContent: snapshot.data?.avatarUrl,
|
||||
name: snapshot.data?.displayName ??
|
||||
controller.matrix.client.userID.localpart,
|
||||
controller.matrix!.client.userID!.localpart,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -34,31 +34,31 @@ enum _EventContextAction { info, report }
|
|||
class ChatView extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
|
||||
const ChatView(this.controller, {Key key}) : super(key: key);
|
||||
const ChatView(this.controller, {Key? key}) : super(key: key);
|
||||
|
||||
List<Widget> _appBarActions(BuildContext context) => controller.selectMode
|
||||
? [
|
||||
if (controller.canEditSelectedEvents)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
tooltip: L10n.of(context).edit,
|
||||
tooltip: L10n.of(context)!.edit,
|
||||
onPressed: controller.editSelectedEventAction,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.copy_outlined),
|
||||
tooltip: L10n.of(context).copy,
|
||||
tooltip: L10n.of(context)!.copy,
|
||||
onPressed: controller.copyEventsAction,
|
||||
),
|
||||
if (controller.canSaveSelectedEvent)
|
||||
IconButton(
|
||||
icon: Icon(Icons.adaptive.share),
|
||||
tooltip: L10n.of(context).share,
|
||||
tooltip: L10n.of(context)!.share,
|
||||
onPressed: controller.saveSelectedEvent,
|
||||
),
|
||||
if (controller.canRedactSelectedEvents)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
tooltip: L10n.of(context).redactMessage,
|
||||
tooltip: L10n.of(context)!.redactMessage,
|
||||
onPressed: controller.redactEventsAction,
|
||||
),
|
||||
if (controller.selectedEvents.length == 1)
|
||||
|
|
@ -82,7 +82,7 @@ class ChatView extends StatelessWidget {
|
|||
children: [
|
||||
const Icon(Icons.info_outlined),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context).messageInfo),
|
||||
Text(L10n.of(context)!.messageInfo),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -96,7 +96,7 @@ class ChatView extends StatelessWidget {
|
|||
color: Colors.red,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context).reportMessage),
|
||||
Text(L10n.of(context)!.reportMessage),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -104,29 +104,30 @@ class ChatView extends StatelessWidget {
|
|||
),
|
||||
]
|
||||
: [
|
||||
ChatSettingsPopupMenu(controller.room, !controller.room.isDirectChat),
|
||||
ChatSettingsPopupMenu(
|
||||
controller.room!, !controller.room!.isDirectChat),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
controller.matrix ??= Matrix.of(context);
|
||||
final client = controller.matrix.client;
|
||||
final client = controller.matrix!.client;
|
||||
controller.sendingClient ??= client;
|
||||
controller.room = controller.sendingClient.getRoomById(controller.roomId);
|
||||
controller.room = controller.sendingClient!.getRoomById(controller.roomId!);
|
||||
if (controller.room == null) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(L10n.of(context).oopsSomethingWentWrong),
|
||||
title: Text(L10n.of(context)!.oopsSomethingWentWrong),
|
||||
),
|
||||
body: Center(
|
||||
child: Text(L10n.of(context).youAreNoLongerParticipatingInThisChat),
|
||||
child: Text(L10n.of(context)!.youAreNoLongerParticipatingInThisChat),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (controller.room.membership == Membership.invite) {
|
||||
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 horizontalPadding = FluffyThemes.isColumnMode(context) ? 8.0 : 0.0;
|
||||
|
|
@ -139,7 +140,7 @@ class ChatView extends StatelessWidget {
|
|||
}
|
||||
},
|
||||
child: StreamBuilder(
|
||||
stream: controller.room.onUpdate.stream
|
||||
stream: controller.room!.onUpdate.stream
|
||||
.rateLimit(const Duration(milliseconds: 250)),
|
||||
builder: (context, snapshot) => Scaffold(
|
||||
appBar: AppBar(
|
||||
|
|
@ -152,10 +153,10 @@ class ChatView extends StatelessWidget {
|
|||
? IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: controller.clearSelectedEvents,
|
||||
tooltip: L10n.of(context).close,
|
||||
tooltip: L10n.of(context)!.close,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
: UnreadBadgeBackButton(roomId: controller.roomId),
|
||||
: UnreadBadgeBackButton(roomId: controller.roomId!),
|
||||
titleSpacing: 0,
|
||||
title: ChatAppBarTitle(controller),
|
||||
actions: _appBarActions(context),
|
||||
|
|
@ -167,7 +168,7 @@ class ChatView extends StatelessWidget {
|
|||
child: FloatingActionButton(
|
||||
onPressed: controller.scrollDown,
|
||||
foregroundColor:
|
||||
Theme.of(context).textTheme.bodyText2.color,
|
||||
Theme.of(context).textTheme.bodyText2!.color,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
mini: true,
|
||||
child: Icon(Icons.arrow_downward_outlined,
|
||||
|
|
@ -184,7 +185,7 @@ class ChatView extends StatelessWidget {
|
|||
children: <Widget>[
|
||||
if (Matrix.of(context).wallpaper != null)
|
||||
Image.file(
|
||||
Matrix.of(context).wallpaper,
|
||||
Matrix.of(context).wallpaper!,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
|
|
@ -240,7 +241,7 @@ class ChatView extends StatelessWidget {
|
|||
controller.filteredEvents.length +
|
||||
1
|
||||
? controller
|
||||
.timeline.isRequestingHistory
|
||||
.timeline!.isRequestingHistory
|
||||
? const Center(
|
||||
child:
|
||||
CircularProgressIndicator
|
||||
|
|
@ -259,7 +260,7 @@ class ChatView extends StatelessWidget {
|
|||
onPressed: controller
|
||||
.requestHistory,
|
||||
child: Text(
|
||||
L10n.of(context)
|
||||
L10n.of(context)!
|
||||
.loadMore),
|
||||
),
|
||||
)
|
||||
|
|
@ -337,7 +338,7 @@ class ChatView extends StatelessWidget {
|
|||
selected: controller
|
||||
.selectedEvents
|
||||
.any((e) => e.eventId == controller.filteredEvents[i - 1].eventId),
|
||||
timeline: controller.timeline,
|
||||
timeline: controller.timeline!,
|
||||
nextEvent: i < controller.filteredEvents.length ? controller.filteredEvents[i] : null),
|
||||
),
|
||||
);
|
||||
|
|
@ -352,8 +353,8 @@ class ChatView extends StatelessWidget {
|
|||
},
|
||||
)),
|
||||
),
|
||||
if (controller.room.canSendDefaultMessages &&
|
||||
controller.room.membership == Membership.join)
|
||||
if (controller.room!.canSendDefaultMessages &&
|
||||
controller.room!.membership == Membership.join)
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: bottomSheetPadding,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@dart=2.12
|
||||
// This file is auto-generated using scripts/generate_command_hints_glue.sh.
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ import '../../widgets/matrix.dart';
|
|||
|
||||
class EncryptionButton extends StatefulWidget {
|
||||
final Room room;
|
||||
const EncryptionButton(this.room, {Key key}) : super(key: key);
|
||||
const EncryptionButton(this.room, {Key? key}) : super(key: key);
|
||||
@override
|
||||
_EncryptionButtonState createState() => _EncryptionButtonState();
|
||||
}
|
||||
|
||||
class _EncryptionButtonState extends State<EncryptionButton> {
|
||||
StreamSubscription _onSyncSub;
|
||||
StreamSubscription? _onSyncSub;
|
||||
|
||||
void _enableEncryptionAction() async {
|
||||
if (widget.room.encrypted) {
|
||||
|
|
@ -29,20 +29,20 @@ class _EncryptionButtonState extends State<EncryptionButton> {
|
|||
await showOkAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
okLabel: L10n.of(context).ok,
|
||||
message: L10n.of(context).noEncryptionForPublicRooms,
|
||||
okLabel: L10n.of(context)!.ok,
|
||||
message: L10n.of(context)!.noEncryptionForPublicRooms,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (await showOkCancelAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
title: L10n.of(context).enableEncryption,
|
||||
title: L10n.of(context)!.enableEncryption,
|
||||
message: widget.room.client.encryptionEnabled
|
||||
? L10n.of(context).enableEncryptionWarning
|
||||
: L10n.of(context).needPantalaimonWarning,
|
||||
okLabel: L10n.of(context).yes,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
? L10n.of(context)!.enableEncryptionWarning
|
||||
: L10n.of(context)!.needPantalaimonWarning,
|
||||
okLabel: L10n.of(context)!.yes,
|
||||
cancelLabel: L10n.of(context)!.cancel,
|
||||
) ==
|
||||
OkCancelResult.ok) {
|
||||
await showFutureLoadingDialog(
|
||||
|
|
@ -50,7 +50,7 @@ class _EncryptionButtonState extends State<EncryptionButton> {
|
|||
future: () => widget.room.enableEncryption(),
|
||||
);
|
||||
// we want to enable the lock icon
|
||||
setState(() => null);
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -68,22 +68,22 @@ class _EncryptionButtonState extends State<EncryptionButton> {
|
|||
.onSync
|
||||
.stream
|
||||
.where((s) => s.deviceLists != null)
|
||||
.listen((s) => setState(() => null));
|
||||
.listen((s) => setState(() {}));
|
||||
}
|
||||
return FutureBuilder<List<User>>(
|
||||
future:
|
||||
widget.room.encrypted ? widget.room.requestParticipants() : null,
|
||||
builder: (BuildContext context, snapshot) {
|
||||
Color color;
|
||||
Color? color;
|
||||
if (widget.room.encrypted && snapshot.hasData) {
|
||||
final users = snapshot.data;
|
||||
final users = snapshot.data!;
|
||||
users.removeWhere((u) =>
|
||||
!{Membership.invite, Membership.join}.contains(u.membership) ||
|
||||
!widget.room.client.userDeviceKeys.containsKey(u.id));
|
||||
var allUsersValid = true;
|
||||
var oneUserInvalid = false;
|
||||
for (final u in users) {
|
||||
final status = widget.room.client.userDeviceKeys[u.id].verified;
|
||||
final status = widget.room.client.userDeviceKeys[u.id]!.verified;
|
||||
if (status != UserVerifiedStatus.verified) {
|
||||
allUsersValid = false;
|
||||
}
|
||||
|
|
@ -99,8 +99,8 @@ class _EncryptionButtonState extends State<EncryptionButton> {
|
|||
}
|
||||
return IconButton(
|
||||
tooltip: widget.room.encrypted
|
||||
? L10n.of(context).encrypted
|
||||
: L10n.of(context).encryptionNotEnabled,
|
||||
? L10n.of(context)!.encrypted
|
||||
: L10n.of(context)!.encryptionNotEnabled,
|
||||
icon: Icon(
|
||||
widget.room.encrypted
|
||||
? Icons.lock_outlined
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ extension EventInfoDialogExtension on Event {
|
|||
void showInfoDialog(BuildContext context) => showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
EventInfoDialog(l10n: L10n.of(context), event: this),
|
||||
EventInfoDialog(l10n: L10n.of(context)!, event: this),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -21,9 +21,9 @@ class EventInfoDialog extends StatelessWidget {
|
|||
final Event event;
|
||||
final L10n l10n;
|
||||
const EventInfoDialog({
|
||||
@required this.event,
|
||||
@required this.l10n,
|
||||
Key key,
|
||||
required this.event,
|
||||
required this.l10n,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
String get prettyJson {
|
||||
|
|
@ -37,11 +37,11 @@ class EventInfoDialog extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(L10n.of(context).messageInfo),
|
||||
title: Text(L10n.of(context)!.messageInfo),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_downward_outlined),
|
||||
onPressed: Navigator.of(context, rootNavigator: false).pop,
|
||||
tooltip: L10n.of(context).close,
|
||||
tooltip: L10n.of(context)!.close,
|
||||
),
|
||||
),
|
||||
body: ListView(
|
||||
|
|
@ -51,19 +51,19 @@ class EventInfoDialog extends StatelessWidget {
|
|||
mxContent: event.sender.avatarUrl,
|
||||
name: event.sender.calcDisplayname(),
|
||||
),
|
||||
title: Text(L10n.of(context).sender),
|
||||
title: Text(L10n.of(context)!.sender),
|
||||
subtitle:
|
||||
Text('${event.sender.calcDisplayname()} [${event.senderId}]'),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(L10n.of(context).time),
|
||||
title: Text(L10n.of(context)!.time),
|
||||
subtitle: Text(event.originServerTs.localizedTime(context)),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(L10n.of(context).messageType),
|
||||
title: Text(L10n.of(context)!.messageType),
|
||||
subtitle: Text(event.humanreadableType),
|
||||
),
|
||||
ListTile(title: Text('${L10n.of(context).sourceCode}:')),
|
||||
ListTile(title: Text('${L10n.of(context)!.sourceCode}:')),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Material(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
//@dart=2.12
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
|
|
|
|||
|
|
@ -13,17 +13,17 @@ import '../../../utils/url_launcher.dart';
|
|||
|
||||
class HtmlMessage extends StatelessWidget {
|
||||
final String html;
|
||||
final int maxLines;
|
||||
final int? maxLines;
|
||||
final Room room;
|
||||
final TextStyle defaultTextStyle;
|
||||
final TextStyle linkStyle;
|
||||
final double emoteSize;
|
||||
final TextStyle? defaultTextStyle;
|
||||
final TextStyle? linkStyle;
|
||||
final double? emoteSize;
|
||||
|
||||
const HtmlMessage({
|
||||
Key key,
|
||||
this.html,
|
||||
Key? key,
|
||||
required this.html,
|
||||
this.maxLines,
|
||||
this.room,
|
||||
required this.room,
|
||||
this.defaultTextStyle,
|
||||
this.linkStyle,
|
||||
this.emoteSize,
|
||||
|
|
@ -52,7 +52,7 @@ class HtmlMessage extends StatelessWidget {
|
|||
defaultTextStyle: defaultTextStyle,
|
||||
emoteSize: emoteSize,
|
||||
linkStyle: linkStyle ??
|
||||
themeData.textTheme.bodyText2.copyWith(
|
||||
themeData.textTheme.bodyText2!.copyWith(
|
||||
color: themeData.colorScheme.secondary,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
|
|
@ -60,11 +60,11 @@ 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(
|
||||
.getThumbnail(
|
||||
matrix.client,
|
||||
width: (width ?? 800) * ratio,
|
||||
height: (height ?? 800) * ratio,
|
||||
|
|
@ -92,9 +92,6 @@ class HtmlMessage extends StatelessWidget {
|
|||
return await matrix.store.getItem('${SettingKeys.codeLanguage}.$key');
|
||||
},
|
||||
getPillInfo: (String url) async {
|
||||
if (room == null) {
|
||||
return null;
|
||||
}
|
||||
final identityParts = url.parseIdentifierIntoParts();
|
||||
final identifier = identityParts?.primaryIdentifier;
|
||||
if (identifier == null) {
|
||||
|
|
@ -108,13 +105,10 @@ class HtmlMessage extends StatelessWidget {
|
|||
}
|
||||
// there might still be a profile...
|
||||
final profile = await room.client.getProfileFromUserId(identifier);
|
||||
if (profile != null) {
|
||||
return {
|
||||
'displayname': profile.displayName,
|
||||
'avatar_url': profile.avatarUrl.toString(),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
return {
|
||||
'displayname': profile.displayName,
|
||||
'avatar_url': profile.avatarUrl.toString(),
|
||||
};
|
||||
}
|
||||
if (identifier.sigil == '#') {
|
||||
// we have an alias pill
|
||||
|
|
@ -128,7 +122,7 @@ class HtmlMessage extends StatelessWidget {
|
|||
// we have a room!
|
||||
return {
|
||||
'displayname':
|
||||
r.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
|
||||
r.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
|
||||
'avatar_url': r.getState('m.room.avatar')?.content['url'],
|
||||
};
|
||||
}
|
||||
|
|
@ -143,12 +137,12 @@ class HtmlMessage extends StatelessWidget {
|
|||
}
|
||||
return {
|
||||
'displayname':
|
||||
r.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
|
||||
r.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
|
||||
'avatar_url': r.getState('m.room.avatar')?.content['url'],
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
} as Future<Map<String, dynamic>> Function(String)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@ class ImageBubble extends StatefulWidget {
|
|||
final bool tapToView;
|
||||
final BoxFit fit;
|
||||
final bool maxSize;
|
||||
final Color backgroundColor;
|
||||
final Color? backgroundColor;
|
||||
final bool thumbnailOnly;
|
||||
final bool animated;
|
||||
final double width;
|
||||
final double height;
|
||||
final void Function() onLoaded;
|
||||
final void Function() onTap;
|
||||
final void Function()? onLoaded;
|
||||
final void Function()? onTap;
|
||||
|
||||
const ImageBubble(
|
||||
this.event, {
|
||||
|
|
@ -39,7 +39,7 @@ class ImageBubble extends StatefulWidget {
|
|||
this.height = 300,
|
||||
this.animated = false,
|
||||
this.onTap,
|
||||
Key key,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -48,17 +48,17 @@ class ImageBubble extends StatefulWidget {
|
|||
|
||||
class _ImageBubbleState extends State<ImageBubble> {
|
||||
// for plaintext: holds the http URL for the thumbnail
|
||||
String thumbnailUrl;
|
||||
String? thumbnailUrl;
|
||||
// for plaintext. holds the http URL for the thumbnial, without the animated flag
|
||||
String thumbnailUrlNoAnimated;
|
||||
String? thumbnailUrlNoAnimated;
|
||||
// for plaintext: holds the http URL of the original
|
||||
String attachmentUrl;
|
||||
MatrixFile _file;
|
||||
MatrixFile _thumbnail;
|
||||
String? attachmentUrl;
|
||||
MatrixFile? _file;
|
||||
MatrixFile? _thumbnail;
|
||||
bool _requestedThumbnailOnFailure = false;
|
||||
// In case we have animated = false, this will hold the first frame so that we make
|
||||
// sure that things are never animated
|
||||
Widget _firstFrame;
|
||||
Widget? _firstFrame;
|
||||
|
||||
// the mimetypes that we know how to render, from the flutter Image widget
|
||||
final _knownMimetypes = <String>{
|
||||
|
|
@ -82,8 +82,8 @@ class _ImageBubbleState extends State<ImageBubble> {
|
|||
? widget.event.thumbnailMimetype.toLowerCase()
|
||||
: widget.event.attachmentMimetype.toLowerCase();
|
||||
|
||||
MatrixFile get _displayFile => _file ?? _thumbnail;
|
||||
String get displayUrl => widget.thumbnailOnly ? thumbnailUrl : attachmentUrl;
|
||||
MatrixFile? get _displayFile => _file ?? _thumbnail;
|
||||
String? get displayUrl => widget.thumbnailOnly ? thumbnailUrl : attachmentUrl;
|
||||
|
||||
dynamic _error;
|
||||
|
||||
|
|
@ -91,14 +91,14 @@ class _ImageBubbleState extends State<ImageBubble> {
|
|||
try {
|
||||
final res = await widget.event
|
||||
.downloadAndDecryptAttachmentCached(getThumbnail: getThumbnail);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
WidgetsBinding.instance!.addPostFrameCallback((_) {
|
||||
if (getThumbnail) {
|
||||
if (mounted) {
|
||||
setState(() => _thumbnail = res);
|
||||
}
|
||||
} else {
|
||||
if (widget.onLoaded != null) {
|
||||
widget.onLoaded();
|
||||
widget.onLoaded!();
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() => _file = res);
|
||||
|
|
@ -106,7 +106,7 @@ class _ImageBubbleState extends State<ImageBubble> {
|
|||
}
|
||||
});
|
||||
} catch (err) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
WidgetsBinding.instance!.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
setState(() => _error = err);
|
||||
}
|
||||
|
|
@ -114,7 +114,7 @@ class _ImageBubbleState extends State<ImageBubble> {
|
|||
}
|
||||
}
|
||||
|
||||
Widget frameBuilder(_, Widget child, int frame, __) {
|
||||
Widget frameBuilder(_, Widget child, int? frame, __) {
|
||||
// as servers might return animated gifs as thumbnails and we want them to *not* play
|
||||
// animated, we'll have to store the first frame in a variable and display that instead
|
||||
if (widget.animated) {
|
||||
|
|
@ -135,12 +135,14 @@ class _ImageBubbleState extends State<ImageBubble> {
|
|||
key: ValueKey(key),
|
||||
fit: widget.fit,
|
||||
),
|
||||
network: (String url) => SvgPicture.network(
|
||||
url,
|
||||
key: ValueKey(url),
|
||||
placeholderBuilder: (context) => getPlaceholderWidget(),
|
||||
fit: widget.fit,
|
||||
),
|
||||
network: (String? url) => url == null
|
||||
? Container()
|
||||
: SvgPicture.network(
|
||||
url,
|
||||
key: ValueKey(url),
|
||||
placeholderBuilder: (context) => getPlaceholderWidget(),
|
||||
fit: widget.fit,
|
||||
),
|
||||
);
|
||||
_contentRenderers['image/lottie+json'] = _ImageBubbleContentRenderer(
|
||||
memory: (Uint8List bytes, String key) => Lottie.memory(
|
||||
|
|
@ -151,14 +153,16 @@ class _ImageBubbleState extends State<ImageBubble> {
|
|||
getErrorWidget(context, error),
|
||||
animate: widget.animated,
|
||||
),
|
||||
network: (String url) => Lottie.network(
|
||||
url,
|
||||
key: ValueKey(url),
|
||||
fit: widget.fit,
|
||||
errorBuilder: (context, error, stacktrace) =>
|
||||
getErrorWidget(context, error),
|
||||
animate: widget.animated,
|
||||
),
|
||||
network: (String? url) => url == null
|
||||
? Container()
|
||||
: Lottie.network(
|
||||
url,
|
||||
key: ValueKey(url),
|
||||
fit: widget.fit,
|
||||
errorBuilder: (context, error, stacktrace) =>
|
||||
getErrorWidget(context, error),
|
||||
animate: widget.animated,
|
||||
),
|
||||
);
|
||||
|
||||
// add all the custom content renderer mimetypes to the known mimetypes set
|
||||
|
|
@ -203,7 +207,7 @@ class _ImageBubbleState extends State<ImageBubble> {
|
|||
OutlinedButton.icon(
|
||||
style: OutlinedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
primary: Theme.of(context).textTheme.bodyText1.color,
|
||||
primary: Theme.of(context).textTheme.bodyText1!.color,
|
||||
),
|
||||
icon: const Icon(Icons.download_outlined),
|
||||
onPressed: () => widget.event.saveFile(context),
|
||||
|
|
@ -215,7 +219,7 @@ class _ImageBubbleState extends State<ImageBubble> {
|
|||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (widget.event.sizeString != null) Text(widget.event.sizeString),
|
||||
if (widget.event.sizeString != null) Text(widget.event.sizeString!),
|
||||
const SizedBox(height: 8),
|
||||
Text((error ?? _error).toString()),
|
||||
],
|
||||
|
|
@ -223,8 +227,8 @@ class _ImageBubbleState extends State<ImageBubble> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget getPlaceholderWidget({Widget child}) {
|
||||
Widget blurhash;
|
||||
Widget getPlaceholderWidget({Widget? child}) {
|
||||
Widget? blurhash;
|
||||
if (widget.event.infoMap['xyz.amorgan.blurhash'] is String) {
|
||||
final ratio =
|
||||
widget.event.infoMap['w'] is int && widget.event.infoMap['h'] is int
|
||||
|
|
@ -265,16 +269,16 @@ class _ImageBubbleState extends State<ImageBubble> {
|
|||
: widget.event.thumbnailMxcUrl.toString();
|
||||
final mimetype = getMimetype(!isOriginal);
|
||||
if (_contentRenderers.containsKey(mimetype)) {
|
||||
return _contentRenderers[mimetype].memory(_displayFile.bytes, key);
|
||||
return _contentRenderers[mimetype]!.memory!(_displayFile!.bytes, key);
|
||||
} else {
|
||||
return Image.memory(
|
||||
_displayFile.bytes,
|
||||
_displayFile!.bytes,
|
||||
key: ValueKey(key),
|
||||
fit: widget.fit,
|
||||
errorBuilder: (context, error, stacktrace) {
|
||||
if (widget.event.hasThumbnail && !_requestedThumbnailOnFailure) {
|
||||
_requestedThumbnailOnFailure = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
WidgetsBinding.instance!.addPostFrameCallback((_) {
|
||||
setState(() {
|
||||
_file = null;
|
||||
_requestFile(getThumbnail: true);
|
||||
|
|
@ -299,12 +303,12 @@ class _ImageBubbleState extends State<ImageBubble> {
|
|||
final mimetype = getMimetype(_requestedThumbnailOnFailure);
|
||||
if (displayUrl == attachmentUrl &&
|
||||
_contentRenderers.containsKey(mimetype)) {
|
||||
return _contentRenderers[mimetype].network(displayUrl);
|
||||
return _contentRenderers[mimetype]!.network!(displayUrl);
|
||||
} else {
|
||||
return CachedNetworkImage(
|
||||
// as we change the url on-error we need a key so that the widget actually updates
|
||||
key: ValueKey(displayUrl),
|
||||
imageUrl: displayUrl,
|
||||
imageUrl: displayUrl!,
|
||||
placeholder: (context, url) {
|
||||
if (!widget.thumbnailOnly &&
|
||||
displayUrl != thumbnailUrl &&
|
||||
|
|
@ -313,7 +317,7 @@ class _ImageBubbleState extends State<ImageBubble> {
|
|||
return FutureBuilder<bool>(
|
||||
future: (() async {
|
||||
return await DefaultCacheManager()
|
||||
.getFileFromCache(thumbnailUrl) !=
|
||||
.getFileFromCache(thumbnailUrl!) !=
|
||||
null;
|
||||
})(),
|
||||
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
|
||||
|
|
@ -321,8 +325,8 @@ class _ImageBubbleState extends State<ImageBubble> {
|
|||
return getPlaceholderWidget();
|
||||
}
|
||||
final effectiveUrl = snapshot.data == true
|
||||
? thumbnailUrl
|
||||
: thumbnailUrlNoAnimated;
|
||||
? thumbnailUrl!
|
||||
: thumbnailUrlNoAnimated!;
|
||||
return CachedNetworkImage(
|
||||
key: ValueKey(effectiveUrl),
|
||||
imageUrl: effectiveUrl,
|
||||
|
|
@ -348,7 +352,7 @@ class _ImageBubbleState extends State<ImageBubble> {
|
|||
// the image failed to load but the event has a thumbnail attached....so we can
|
||||
// try to load this one!
|
||||
_requestedThumbnailOnFailure = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
WidgetsBinding.instance!.addPostFrameCallback((_) {
|
||||
setState(() {
|
||||
thumbnailUrl = widget.event
|
||||
.getAttachmentUrl(
|
||||
|
|
@ -382,11 +386,11 @@ class _ImageBubbleState extends State<ImageBubble> {
|
|||
OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
primary: Theme.of(context).textTheme.bodyText1.color,
|
||||
primary: Theme.of(context).textTheme.bodyText1!.color,
|
||||
),
|
||||
onPressed: () => onTap(context),
|
||||
child: Text(
|
||||
L10n.of(context).tapToShowImage,
|
||||
L10n.of(context)!.tapToShowImage,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
maxLines: 1,
|
||||
|
|
@ -394,7 +398,7 @@ class _ImageBubbleState extends State<ImageBubble> {
|
|||
),
|
||||
if (widget.event.sizeString != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(widget.event.sizeString),
|
||||
Text(widget.event.sizeString!),
|
||||
]
|
||||
],
|
||||
));
|
||||
|
|
@ -451,7 +455,7 @@ class _ImageBubbleState extends State<ImageBubble> {
|
|||
|
||||
void onTap(BuildContext context) {
|
||||
if (widget.onTap != null) {
|
||||
widget.onTap();
|
||||
widget.onTap!();
|
||||
return;
|
||||
}
|
||||
if (!widget.tapToView) return;
|
||||
|
|
@ -476,8 +480,8 @@ class _ImageBubbleState extends State<ImageBubble> {
|
|||
}
|
||||
|
||||
class _ImageBubbleContentRenderer {
|
||||
final Widget Function(Uint8List, String) memory;
|
||||
final Widget Function(String) network;
|
||||
final Widget Function(Uint8List, String)? memory;
|
||||
final Widget Function(String?)? network;
|
||||
|
||||
_ImageBubbleContentRenderer({this.memory, this.network});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ class MapBubble extends StatelessWidget {
|
|||
final double height;
|
||||
final double radius;
|
||||
const MapBubble({
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
this.zoom = 14.0,
|
||||
this.width = 400,
|
||||
this.height = 400,
|
||||
this.radius = 10.0,
|
||||
Key key,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -16,14 +16,14 @@ import 'verification_request_content.dart';
|
|||
|
||||
class Message extends StatelessWidget {
|
||||
final Event event;
|
||||
final Event nextEvent;
|
||||
final void Function(Event) onSelect;
|
||||
final void Function(Event) onAvatarTab;
|
||||
final void Function(Event) onInfoTab;
|
||||
final void Function(String) scrollToEventId;
|
||||
final Event? nextEvent;
|
||||
final void Function(Event)? onSelect;
|
||||
final void Function(Event)? onAvatarTab;
|
||||
final void Function(Event)? onInfoTab;
|
||||
final void Function(String)? scrollToEventId;
|
||||
final void Function(String) unfold;
|
||||
final bool longPressSelect;
|
||||
final bool selected;
|
||||
final bool? longPressSelect;
|
||||
final bool? selected;
|
||||
final Timeline timeline;
|
||||
|
||||
const Message(this.event,
|
||||
|
|
@ -33,10 +33,10 @@ class Message extends StatelessWidget {
|
|||
this.onInfoTab,
|
||||
this.onAvatarTab,
|
||||
this.scrollToEventId,
|
||||
@required this.unfold,
|
||||
required this.unfold,
|
||||
this.selected,
|
||||
this.timeline,
|
||||
Key key})
|
||||
required this.timeline,
|
||||
Key? key})
|
||||
: super(key: key);
|
||||
|
||||
/// Indicates wheither the user may use a mouse instead
|
||||
|
|
@ -61,14 +61,14 @@ class Message extends StatelessWidget {
|
|||
var color = Theme.of(context).appBarTheme.backgroundColor;
|
||||
final displayTime = event.type == EventTypes.RoomCreate ||
|
||||
nextEvent == null ||
|
||||
!event.originServerTs.sameEnvironment(nextEvent.originServerTs);
|
||||
!event.originServerTs.sameEnvironment(nextEvent!.originServerTs);
|
||||
final sameSender = nextEvent != null &&
|
||||
[
|
||||
EventTypes.Message,
|
||||
EventTypes.Sticker,
|
||||
EventTypes.Encrypted,
|
||||
].contains(nextEvent.type)
|
||||
? nextEvent.sender.id == event.sender.id && !displayTime
|
||||
].contains(nextEvent!.type)
|
||||
? nextEvent!.sender.id == event.sender.id && !displayTime
|
||||
: false;
|
||||
final textColor = ownMessage
|
||||
? Colors.white
|
||||
|
|
@ -119,7 +119,7 @@ class Message extends StatelessWidget {
|
|||
: Avatar(
|
||||
mxContent: event.sender.avatarUrl,
|
||||
name: event.sender.calcDisplayname(),
|
||||
onTap: () => onAvatarTab(event),
|
||||
onTap: () => onAvatarTab!(event),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
|
|
@ -152,10 +152,11 @@ class Message extends StatelessWidget {
|
|||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onHover: (b) => useMouse = true,
|
||||
onTap: !useMouse && longPressSelect
|
||||
? () => null
|
||||
: () => onSelect(event),
|
||||
onLongPress: !longPressSelect ? null : () => onSelect(event),
|
||||
onTap: !useMouse && longPressSelect!
|
||||
? () {}
|
||||
: () => onSelect!(event),
|
||||
onLongPress:
|
||||
!longPressSelect! ? null : () => onSelect!(event),
|
||||
borderRadius: borderRadius,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
|
|
@ -175,13 +176,13 @@ class Message extends StatelessWidget {
|
|||
children: <Widget>[
|
||||
if (event.relationshipType ==
|
||||
RelationshipTypes.reply)
|
||||
FutureBuilder<Event>(
|
||||
FutureBuilder<Event?>(
|
||||
future: event.getReplyEvent(timeline),
|
||||
builder: (BuildContext context, snapshot) {
|
||||
final replyEvent = snapshot.hasData
|
||||
? snapshot.data
|
||||
? snapshot.data!
|
||||
: Event(
|
||||
eventId: event.relationshipEventId,
|
||||
eventId: event.relationshipEventId!,
|
||||
content: {
|
||||
'msgtype': 'm.text',
|
||||
'body': '...'
|
||||
|
|
@ -195,7 +196,7 @@ class Message extends StatelessWidget {
|
|||
return InkWell(
|
||||
onTap: () {
|
||||
if (scrollToEventId != null) {
|
||||
scrollToEventId(replyEvent.eventId);
|
||||
scrollToEventId!(replyEvent.eventId);
|
||||
}
|
||||
},
|
||||
child: AbsorbPointer(
|
||||
|
|
@ -300,7 +301,7 @@ class Message extends StatelessWidget {
|
|||
|
||||
return Center(
|
||||
child: Container(
|
||||
color: selected
|
||||
color: selected!
|
||||
? Theme.of(context).primaryColor.withAlpha(100)
|
||||
: Theme.of(context).primaryColor.withAlpha(0),
|
||||
constraints:
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ import 'sticker.dart';
|
|||
|
||||
class MessageContent extends StatelessWidget {
|
||||
final Event event;
|
||||
final Color textColor;
|
||||
final void Function(Event) onInfoTab;
|
||||
final Color? textColor;
|
||||
final void Function(Event)? onInfoTab;
|
||||
|
||||
const MessageContent(this.event, {this.onInfoTab, Key key, this.textColor})
|
||||
const MessageContent(this.event, {this.onInfoTab, Key? key, this.textColor})
|
||||
: super(key: key);
|
||||
|
||||
void _verifyOrRequestKey(BuildContext context) async {
|
||||
|
|
@ -33,15 +33,15 @@ class MessageContent extends StatelessWidget {
|
|||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(
|
||||
event.type == EventTypes.Encrypted
|
||||
? L10n.of(context).needPantalaimonWarning
|
||||
? L10n.of(context)!.needPantalaimonWarning
|
||||
: event.getLocalizedBody(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
),
|
||||
)));
|
||||
return;
|
||||
}
|
||||
final client = Matrix.of(context).client;
|
||||
if (client.isUnknownSession && client.encryption.crossSigning.enabled) {
|
||||
if (client.isUnknownSession && client.encryption!.crossSigning.enabled) {
|
||||
await BootstrapDialog(
|
||||
client: Matrix.of(context).client,
|
||||
).show(context);
|
||||
|
|
@ -55,7 +55,7 @@ class MessageContent extends StatelessWidget {
|
|||
);
|
||||
if (success.error == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(L10n.of(context).requestToReadOlderMessages)));
|
||||
content: Text(L10n.of(context)!.requestToReadOlderMessages)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -83,17 +83,17 @@ class MessageContent extends StatelessWidget {
|
|||
if (PlatformInfos.isMobile) {
|
||||
return AudioPlayerWidget(
|
||||
event,
|
||||
color: textColor,
|
||||
color: textColor!,
|
||||
);
|
||||
}
|
||||
return MessageDownloadContent(event, textColor);
|
||||
return MessageDownloadContent(event, textColor!);
|
||||
case MessageTypes.Video:
|
||||
if (PlatformInfos.isMobile || PlatformInfos.isWeb) {
|
||||
return EventVideoPlayer(event);
|
||||
}
|
||||
return MessageDownloadContent(event, textColor);
|
||||
return MessageDownloadContent(event, textColor!);
|
||||
case MessageTypes.File:
|
||||
return MessageDownloadContent(event, textColor);
|
||||
return MessageDownloadContent(event, textColor!);
|
||||
|
||||
case MessageTypes.Text:
|
||||
case MessageTypes.Notice:
|
||||
|
|
@ -115,7 +115,7 @@ class MessageContent extends StatelessWidget {
|
|||
fontSize: bigEmotes ? fontSize * 3 : fontSize,
|
||||
),
|
||||
linkStyle: TextStyle(
|
||||
color: textColor.withAlpha(150),
|
||||
color: textColor!.withAlpha(150),
|
||||
fontSize: bigEmotes ? fontSize * 3 : fontSize,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
|
|
@ -131,14 +131,12 @@ class MessageContent extends StatelessWidget {
|
|||
textColor: buttonTextColor,
|
||||
onPressed: () => _verifyOrRequestKey(context),
|
||||
icon: const Icon(Icons.lock_outline),
|
||||
label: L10n.of(context).encrypted,
|
||||
label: L10n.of(context)!.encrypted,
|
||||
);
|
||||
case MessageTypes.Location:
|
||||
final geoUri =
|
||||
Uri.tryParse(event.content.tryGet<String>('geo_uri'));
|
||||
if (geoUri != null &&
|
||||
geoUri.scheme == 'geo' &&
|
||||
geoUri.path != null) {
|
||||
Uri.tryParse(event.content.tryGet<String>('geo_uri')!);
|
||||
if (geoUri != null && geoUri.scheme == 'geo') {
|
||||
final latlong = geoUri.path
|
||||
.split(';')
|
||||
.first
|
||||
|
|
@ -152,8 +150,8 @@ class MessageContent extends StatelessWidget {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
MapBubble(
|
||||
latitude: latlong.first,
|
||||
longitude: latlong.last,
|
||||
latitude: latlong.first!,
|
||||
longitude: latlong.last!,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
OutlinedButton.icon(
|
||||
|
|
@ -161,7 +159,7 @@ class MessageContent extends StatelessWidget {
|
|||
onPressed:
|
||||
UrlLauncher(context, geoUri.toString()).launchUrl,
|
||||
label: Text(
|
||||
L10n.of(context).openInMaps,
|
||||
L10n.of(context)!.openInMaps,
|
||||
style: TextStyle(color: textColor),
|
||||
),
|
||||
),
|
||||
|
|
@ -177,24 +175,24 @@ class MessageContent extends StatelessWidget {
|
|||
return _ButtonContent(
|
||||
onPressed: () => launch(event.body),
|
||||
icon: const Icon(Icons.phone_outlined, color: Colors.green),
|
||||
label: L10n.of(context).videoCall,
|
||||
label: L10n.of(context)!.videoCall,
|
||||
textColor: buttonTextColor,
|
||||
);
|
||||
}
|
||||
if (event.redacted) {
|
||||
return _ButtonContent(
|
||||
label: L10n.of(context)
|
||||
label: L10n.of(context)!
|
||||
.redactedAnEvent(event.sender.calcDisplayname()),
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
textColor: buttonTextColor,
|
||||
onPressed: () => onInfoTab(event),
|
||||
onPressed: () => onInfoTab!(event),
|
||||
);
|
||||
}
|
||||
final bigEmotes = event.onlyEmotes &&
|
||||
event.numberEmotes > 0 &&
|
||||
event.numberEmotes <= 10;
|
||||
return LinkText(
|
||||
text: event.getLocalizedBody(MatrixLocals(L10n.of(context)),
|
||||
text: event.getLocalizedBody(MatrixLocals(L10n.of(context)!),
|
||||
hideReply: true),
|
||||
textStyle: TextStyle(
|
||||
color: textColor,
|
||||
|
|
@ -202,24 +200,22 @@ class MessageContent extends StatelessWidget {
|
|||
decoration: event.redacted ? TextDecoration.lineThrough : null,
|
||||
),
|
||||
linkStyle: TextStyle(
|
||||
color: textColor.withAlpha(150),
|
||||
color: textColor!.withAlpha(150),
|
||||
fontSize: bigEmotes ? fontSize * 3 : fontSize,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
onLinkTap: (url) => UrlLauncher(context, url).launchUrl(),
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return _ButtonContent(
|
||||
label: L10n.of(context)
|
||||
label: L10n.of(context)!
|
||||
.userSentUnknownEvent(event.sender.calcDisplayname(), event.type),
|
||||
icon: const Icon(Icons.info_outlined),
|
||||
textColor: buttonTextColor,
|
||||
onPressed: () => onInfoTab(event),
|
||||
onPressed: () => onInfoTab!(event),
|
||||
);
|
||||
}
|
||||
return Container(); // else flutter analyze complains
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -227,14 +223,14 @@ class _ButtonContent extends StatelessWidget {
|
|||
final void Function() onPressed;
|
||||
final String label;
|
||||
final Icon icon;
|
||||
final Color textColor;
|
||||
final Color? textColor;
|
||||
|
||||
const _ButtonContent({
|
||||
@required this.label,
|
||||
@required this.icon,
|
||||
@required this.textColor,
|
||||
@required this.onPressed,
|
||||
Key key,
|
||||
required this.label,
|
||||
required this.icon,
|
||||
required this.textColor,
|
||||
required this.onPressed,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
//@dart=2.12
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:collection/collection.dart' show IterableExtension;
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
|
|
@ -14,14 +15,14 @@ class MessageReactions extends StatelessWidget {
|
|||
final Event event;
|
||||
final Timeline timeline;
|
||||
|
||||
const MessageReactions(this.event, this.timeline, {Key key})
|
||||
const MessageReactions(this.event, this.timeline, {Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final allReactionEvents =
|
||||
event.aggregatedEvents(timeline, RelationshipTypes.reaction);
|
||||
final reactionMap = <String, _ReactionEntry>{};
|
||||
final reactionMap = <String?, _ReactionEntry>{};
|
||||
final client = Matrix.of(context).client;
|
||||
|
||||
for (final e in allReactionEvents) {
|
||||
|
|
@ -35,9 +36,9 @@ class MessageReactions extends StatelessWidget {
|
|||
reactors: [],
|
||||
);
|
||||
}
|
||||
reactionMap[key].count++;
|
||||
reactionMap[key].reactors.add(e.sender);
|
||||
reactionMap[key].reacted |= e.senderId == e.room.client.userID;
|
||||
reactionMap[key]!.count++;
|
||||
reactionMap[key]!.reactors!.add(e.sender);
|
||||
reactionMap[key]!.reacted |= e.senderId == e.room.client.userID;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,11 +53,9 @@ class MessageReactions extends StatelessWidget {
|
|||
reacted: r.reacted,
|
||||
onTap: () {
|
||||
if (r.reacted) {
|
||||
final evt = allReactionEvents.firstWhere(
|
||||
(e) =>
|
||||
e.senderId == e.room.client.userID &&
|
||||
e.content['m.relates_to']['key'] == r.key,
|
||||
orElse: () => null);
|
||||
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,
|
||||
|
|
@ -67,7 +66,7 @@ class MessageReactions extends StatelessWidget {
|
|||
showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () =>
|
||||
event.room.sendReaction(event.eventId, r.key));
|
||||
event.room.sendReaction(event.eventId, r.key!));
|
||||
}
|
||||
},
|
||||
onLongPress: () async => await _AdaptableReactorsDialog(
|
||||
|
|
@ -91,11 +90,11 @@ class MessageReactions extends StatelessWidget {
|
|||
}
|
||||
|
||||
class _Reaction extends StatelessWidget {
|
||||
final String reactionKey;
|
||||
final int count;
|
||||
final bool reacted;
|
||||
final void Function() onTap;
|
||||
final void Function() onLongPress;
|
||||
final String? reactionKey;
|
||||
final int? count;
|
||||
final bool? reacted;
|
||||
final void Function()? onTap;
|
||||
final void Function()? onLongPress;
|
||||
|
||||
const _Reaction({
|
||||
this.reactionKey,
|
||||
|
|
@ -113,11 +112,11 @@ class _Reaction extends StatelessWidget {
|
|||
final color = Theme.of(context).scaffoldBackgroundColor;
|
||||
final fontSize = DefaultTextStyle.of(context).style.fontSize;
|
||||
Widget content;
|
||||
if (reactionKey.startsWith('mxc://')) {
|
||||
final src = Uri.parse(reactionKey)?.getThumbnail(
|
||||
if (reactionKey!.startsWith('mxc://')) {
|
||||
final src = Uri.parse(reactionKey!).getThumbnail(
|
||||
Matrix.of(context).client,
|
||||
width: 9999,
|
||||
height: fontSize * MediaQuery.of(context).devicePixelRatio,
|
||||
height: fontSize! * MediaQuery.of(context).devicePixelRatio,
|
||||
method: ThumbnailMethod.scale,
|
||||
);
|
||||
content = Row(
|
||||
|
|
@ -136,7 +135,7 @@ class _Reaction extends StatelessWidget {
|
|||
],
|
||||
);
|
||||
} else {
|
||||
var renderKey = Characters(reactionKey);
|
||||
var renderKey = Characters(reactionKey!);
|
||||
if (renderKey.length > 10) {
|
||||
renderKey = renderKey.getRange(0, 9) + Characters('…');
|
||||
}
|
||||
|
|
@ -147,13 +146,13 @@ class _Reaction extends StatelessWidget {
|
|||
));
|
||||
}
|
||||
return InkWell(
|
||||
onTap: () => onTap != null ? onTap() : null,
|
||||
onLongPress: () => onLongPress != null ? onLongPress() : null,
|
||||
onTap: () => onTap != null ? onTap!() : null,
|
||||
onLongPress: () => onLongPress != null ? onLongPress!() : null,
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
border: reacted
|
||||
border: reacted!
|
||||
? Border.all(
|
||||
width: 1,
|
||||
color: Theme.of(context).primaryColor,
|
||||
|
|
@ -169,25 +168,30 @@ class _Reaction extends StatelessWidget {
|
|||
}
|
||||
|
||||
class _ReactionEntry {
|
||||
String key;
|
||||
String? key;
|
||||
int count;
|
||||
bool reacted;
|
||||
List<User> reactors;
|
||||
List<User>? reactors;
|
||||
|
||||
_ReactionEntry({this.key, this.count, this.reacted, this.reactors});
|
||||
_ReactionEntry({
|
||||
this.key,
|
||||
required this.count,
|
||||
required this.reacted,
|
||||
this.reactors,
|
||||
});
|
||||
}
|
||||
|
||||
class _AdaptableReactorsDialog extends StatelessWidget {
|
||||
final Client client;
|
||||
final _ReactionEntry reactionEntry;
|
||||
final Client? client;
|
||||
final _ReactionEntry? reactionEntry;
|
||||
|
||||
const _AdaptableReactorsDialog({
|
||||
Key key,
|
||||
Key? key,
|
||||
this.client,
|
||||
this.reactionEntry,
|
||||
}) : super(key: key);
|
||||
|
||||
Future<bool> show(BuildContext context) => PlatformInfos.isCupertinoStyle
|
||||
Future<bool?> show(BuildContext context) => PlatformInfos.isCupertinoStyle
|
||||
? showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (context) => this,
|
||||
|
|
@ -209,20 +213,20 @@ class _AdaptableReactorsDialog extends StatelessWidget {
|
|||
runSpacing: 4.0,
|
||||
alignment: WrapAlignment.center,
|
||||
children: <Widget>[
|
||||
for (var reactor in reactionEntry.reactors)
|
||||
for (var reactor in reactionEntry!.reactors!)
|
||||
Chip(
|
||||
avatar: Avatar(
|
||||
mxContent: reactor.avatarUrl,
|
||||
name: reactor.displayName,
|
||||
client: client,
|
||||
),
|
||||
label: Text(reactor.displayName),
|
||||
label: Text(reactor.displayName!),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final title = Center(child: Text(reactionEntry.key));
|
||||
final title = Center(child: Text(reactionEntry!.key!));
|
||||
|
||||
return PlatformInfos.isCupertinoStyle
|
||||
? CupertinoAlertDialog(
|
||||
|
|
|
|||
|
|
@ -10,21 +10,23 @@ import 'html_message.dart';
|
|||
class ReplyContent extends StatelessWidget {
|
||||
final Event replyEvent;
|
||||
final bool lightText;
|
||||
final Timeline timeline;
|
||||
final Timeline? timeline;
|
||||
|
||||
const ReplyContent(this.replyEvent,
|
||||
{this.lightText = false, Key key, this.timeline})
|
||||
: super(key: key);
|
||||
const ReplyContent(
|
||||
this.replyEvent, {
|
||||
this.lightText = false,
|
||||
Key? key,
|
||||
this.timeline,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget replyBody;
|
||||
final displayEvent = replyEvent != null && timeline != null
|
||||
? replyEvent.getDisplayEvent(timeline)
|
||||
: replyEvent;
|
||||
final timeline = this.timeline;
|
||||
final displayEvent =
|
||||
timeline != null ? replyEvent.getDisplayEvent(timeline) : replyEvent;
|
||||
final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor;
|
||||
if (displayEvent != null &&
|
||||
AppConfig.renderHtml &&
|
||||
if (AppConfig.renderHtml &&
|
||||
[EventTypes.Message, EventTypes.Encrypted]
|
||||
.contains(displayEvent.type) &&
|
||||
[MessageTypes.Text, MessageTypes.Notice, MessageTypes.Emote]
|
||||
|
|
@ -32,16 +34,16 @@ class ReplyContent extends StatelessWidget {
|
|||
!displayEvent.redacted &&
|
||||
displayEvent.content['format'] == 'org.matrix.custom.html' &&
|
||||
displayEvent.content['formatted_body'] is String) {
|
||||
String html = displayEvent.content['formatted_body'];
|
||||
String? html = displayEvent.content['formatted_body'];
|
||||
if (displayEvent.messageType == MessageTypes.Emote) {
|
||||
html = '* $html';
|
||||
}
|
||||
replyBody = HtmlMessage(
|
||||
html: html,
|
||||
html: html!,
|
||||
defaultTextStyle: TextStyle(
|
||||
color: lightText
|
||||
? Colors.white
|
||||
: Theme.of(context).textTheme.bodyText2.color,
|
||||
: Theme.of(context).textTheme.bodyText2!.color,
|
||||
fontSize: fontSize,
|
||||
),
|
||||
maxLines: 1,
|
||||
|
|
@ -50,18 +52,17 @@ class ReplyContent extends StatelessWidget {
|
|||
);
|
||||
} else {
|
||||
replyBody = Text(
|
||||
displayEvent?.getLocalizedBody(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
withSenderNamePrefix: false,
|
||||
hideReply: true,
|
||||
) ??
|
||||
'',
|
||||
displayEvent.getLocalizedBody(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: false,
|
||||
hideReply: true,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: lightText
|
||||
? Colors.white
|
||||
: Theme.of(context).textTheme.bodyText2.color,
|
||||
: Theme.of(context).textTheme.bodyText2!.color,
|
||||
fontSize: fontSize,
|
||||
),
|
||||
);
|
||||
|
|
@ -81,7 +82,7 @@ class ReplyContent extends StatelessWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
(displayEvent?.sender?.calcDisplayname() ?? '') + ':',
|
||||
displayEvent.sender.calcDisplayname() + ':',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
|
|
|
|||
|
|
@ -9,16 +9,16 @@ import '../../../config/app_config.dart';
|
|||
class StateMessage extends StatelessWidget {
|
||||
final Event event;
|
||||
final void Function(String) unfold;
|
||||
const StateMessage(this.event, {@required this.unfold, Key key})
|
||||
const StateMessage(this.event, {required this.unfold, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (event.unsigned['im.fluffychat.collapsed_state_event'] == true) {
|
||||
if (event.unsigned!['im.fluffychat.collapsed_state_event'] == true) {
|
||||
return Container();
|
||||
}
|
||||
final int counter =
|
||||
event.unsigned['im.fluffychat.collapsed_state_event_count'] ?? 0;
|
||||
event.unsigned!['im.fluffychat.collapsed_state_event_count'] ?? 0;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
|
|
@ -40,18 +40,18 @@ class StateMessage extends StatelessWidget {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
event.getLocalizedBody(MatrixLocals(L10n.of(context))),
|
||||
event.getLocalizedBody(MatrixLocals(L10n.of(context)!)),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14 * AppConfig.fontSizeFactor,
|
||||
color: Theme.of(context).textTheme.bodyText2.color,
|
||||
color: Theme.of(context).textTheme.bodyText2!.color,
|
||||
decoration:
|
||||
event.redacted ? TextDecoration.lineThrough : null,
|
||||
),
|
||||
),
|
||||
if (counter != 0)
|
||||
Text(
|
||||
L10n.of(context).moreEvents(counter),
|
||||
L10n.of(context)!.moreEvents(counter),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14 * AppConfig.fontSizeFactor,
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ import 'image_bubble.dart';
|
|||
class Sticker extends StatefulWidget {
|
||||
final Event event;
|
||||
|
||||
const Sticker(this.event, {Key key}) : super(key: key);
|
||||
const Sticker(this.event, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_StickerState createState() => _StickerState();
|
||||
}
|
||||
|
||||
class _StickerState extends State<Sticker> {
|
||||
bool animated;
|
||||
bool? animated;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -31,7 +31,7 @@ class _StickerState extends State<Sticker> {
|
|||
showOkAlertDialog(
|
||||
context: context,
|
||||
message: widget.event.body,
|
||||
okLabel: L10n.of(context).ok,
|
||||
okLabel: L10n.of(context)!.ok,
|
||||
);
|
||||
},
|
||||
animated: animated ?? AppConfig.autoplayImages,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ class VerificationRequestContent extends StatelessWidget {
|
|||
final Event event;
|
||||
final Timeline timeline;
|
||||
|
||||
const VerificationRequestContent({this.event, this.timeline, Key key})
|
||||
const VerificationRequestContent(
|
||||
{required this.event, required this.timeline, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -50,10 +51,10 @@ class VerificationRequestContent extends StatelessWidget {
|
|||
Text(canceled
|
||||
? 'Error ${cancel.first.content.tryGet<String>('code')}: ${cancel.first.content.tryGet<String>('reason')}'
|
||||
: (fullyDone
|
||||
? L10n.of(context).verifySuccess
|
||||
? L10n.of(context)!.verifySuccess
|
||||
: (started
|
||||
? L10n.of(context).loadingPleaseWait
|
||||
: L10n.of(context).newVerificationRequest)))
|
||||
? L10n.of(context)!.loadingPleaseWait
|
||||
: L10n.of(context)!.newVerificationRequest)))
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
//@dart=2.12
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
|
|
|||
|
|
@ -15,19 +15,19 @@ import 'command_hints.dart';
|
|||
|
||||
class InputBar extends StatelessWidget {
|
||||
final Room room;
|
||||
final int minLines;
|
||||
final int maxLines;
|
||||
final TextInputType keyboardType;
|
||||
final TextInputAction textInputAction;
|
||||
final ValueChanged<String> onSubmitted;
|
||||
final FocusNode focusNode;
|
||||
final TextEditingController controller;
|
||||
final InputDecoration decoration;
|
||||
final ValueChanged<String> onChanged;
|
||||
final bool autofocus;
|
||||
final int? minLines;
|
||||
final int? maxLines;
|
||||
final TextInputType? keyboardType;
|
||||
final TextInputAction? textInputAction;
|
||||
final ValueChanged<String>? onSubmitted;
|
||||
final FocusNode? focusNode;
|
||||
final TextEditingController? controller;
|
||||
final InputDecoration? decoration;
|
||||
final ValueChanged<String>? onChanged;
|
||||
final bool? autofocus;
|
||||
|
||||
const InputBar({
|
||||
this.room,
|
||||
required this.room,
|
||||
this.minLines,
|
||||
this.maxLines,
|
||||
this.keyboardType,
|
||||
|
|
@ -38,22 +38,23 @@ class InputBar extends StatelessWidget {
|
|||
this.onChanged,
|
||||
this.autofocus,
|
||||
this.textInputAction,
|
||||
Key key,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
List<Map<String, String>> getSuggestions(String text) {
|
||||
if (controller.selection.baseOffset != controller.selection.extentOffset ||
|
||||
controller.selection.baseOffset < 0) {
|
||||
List<Map<String, String?>> getSuggestions(String text) {
|
||||
if (controller!.selection.baseOffset !=
|
||||
controller!.selection.extentOffset ||
|
||||
controller!.selection.baseOffset < 0) {
|
||||
return []; // no entries if there is selected text
|
||||
}
|
||||
final searchText =
|
||||
controller.text.substring(0, controller.selection.baseOffset);
|
||||
final ret = <Map<String, String>>[];
|
||||
controller!.text.substring(0, controller!.selection.baseOffset);
|
||||
final List<Map<String, String?>> ret = <Map<String, String>>[];
|
||||
const maxResults = 30;
|
||||
|
||||
final commandMatch = RegExp(r'^\/([\w]*)$').firstMatch(searchText);
|
||||
if (commandMatch != null) {
|
||||
final commandSearch = commandMatch[1].toLowerCase();
|
||||
final commandSearch = commandMatch[1]!.toLowerCase();
|
||||
for (final command in room.client.commands.keys) {
|
||||
if (command.contains(commandSearch)) {
|
||||
ret.add({
|
||||
|
|
@ -69,7 +70,7 @@ class InputBar extends StatelessWidget {
|
|||
RegExp(r'(?:\s|^):(?:([-\w]+)~)?([-\w]+)$').firstMatch(searchText);
|
||||
if (emojiMatch != null) {
|
||||
final packSearch = emojiMatch[1];
|
||||
final emoteSearch = emojiMatch[2].toLowerCase();
|
||||
final emoteSearch = emojiMatch[2]!.toLowerCase();
|
||||
final emotePacks = room.getImagePacks(ImagePackUsage.emoticon);
|
||||
if (packSearch == null || packSearch.isEmpty) {
|
||||
for (final pack in emotePacks.entries) {
|
||||
|
|
@ -93,16 +94,16 @@ class InputBar extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
} else if (emotePacks[packSearch] != null) {
|
||||
for (final emote in emotePacks[packSearch].images.entries) {
|
||||
for (final emote in emotePacks[packSearch]!.images.entries) {
|
||||
if (emote.key.toLowerCase().contains(emoteSearch)) {
|
||||
ret.add({
|
||||
'type': 'emote',
|
||||
'name': emote.key,
|
||||
'pack': packSearch,
|
||||
'pack_avatar_url':
|
||||
emotePacks[packSearch].pack.avatarUrl?.toString(),
|
||||
emotePacks[packSearch]!.pack.avatarUrl?.toString(),
|
||||
'pack_display_name':
|
||||
emotePacks[packSearch].pack.displayName ?? packSearch,
|
||||
emotePacks[packSearch]!.pack.displayName ?? packSearch,
|
||||
'mxc': emote.value.url.toString(),
|
||||
});
|
||||
}
|
||||
|
|
@ -114,11 +115,11 @@ class InputBar extends StatelessWidget {
|
|||
}
|
||||
final userMatch = RegExp(r'(?:\s|^)@([-\w]+)$').firstMatch(searchText);
|
||||
if (userMatch != null) {
|
||||
final userSearch = userMatch[1].toLowerCase();
|
||||
final userSearch = userMatch[1]!.toLowerCase();
|
||||
for (final user in room.getParticipants()) {
|
||||
if ((user.displayName != null &&
|
||||
(user.displayName.toLowerCase().contains(userSearch) ||
|
||||
slugify(user.displayName.toLowerCase())
|
||||
(user.displayName!.toLowerCase().contains(userSearch) ||
|
||||
slugify(user.displayName!.toLowerCase())
|
||||
.contains(userSearch))) ||
|
||||
user.id.split(':')[0].toLowerCase().contains(userSearch)) {
|
||||
ret.add({
|
||||
|
|
@ -136,7 +137,7 @@ class InputBar extends StatelessWidget {
|
|||
}
|
||||
final roomMatch = RegExp(r'(?:\s|^)#([-\w]+)$').firstMatch(searchText);
|
||||
if (roomMatch != null) {
|
||||
final roomSearch = roomMatch[1].toLowerCase();
|
||||
final roomSearch = roomMatch[1]!.toLowerCase();
|
||||
for (final r in room.client.rooms) {
|
||||
if (r.getState(EventTypes.RoomTombstone) != null) {
|
||||
continue; // we don't care about tombstoned rooms
|
||||
|
|
@ -155,12 +156,10 @@ class InputBar extends StatelessWidget {
|
|||
.split(':')[0]
|
||||
.toLowerCase()
|
||||
.contains(roomSearch))))) ||
|
||||
(r.name != null && r.name.toLowerCase().contains(roomSearch))) {
|
||||
(r.name.toLowerCase().contains(roomSearch))) {
|
||||
ret.add({
|
||||
'type': 'room',
|
||||
'mxid': (r.canonicalAlias != null && r.canonicalAlias.isNotEmpty)
|
||||
? r.canonicalAlias
|
||||
: r.id,
|
||||
'mxid': (r.canonicalAlias.isNotEmpty) ? r.canonicalAlias : r.id,
|
||||
'displayname': r.displayname,
|
||||
'avatar_url': r.avatar?.toString(),
|
||||
});
|
||||
|
|
@ -175,14 +174,14 @@ class InputBar extends StatelessWidget {
|
|||
|
||||
Widget buildSuggestion(
|
||||
BuildContext context,
|
||||
Map<String, String> suggestion,
|
||||
Client client,
|
||||
Map<String, String?> suggestion,
|
||||
Client? client,
|
||||
) {
|
||||
const size = 30.0;
|
||||
const padding = EdgeInsets.all(4.0);
|
||||
if (suggestion['type'] == 'command') {
|
||||
final command = suggestion['name'];
|
||||
final hint = commandHint(L10n.of(context), command);
|
||||
final command = suggestion['name']!;
|
||||
final hint = commandHint(L10n.of(context)!, command);
|
||||
return Tooltip(
|
||||
message: hint,
|
||||
waitDuration: const Duration(days: 1), // don't show on hover
|
||||
|
|
@ -206,7 +205,7 @@ class InputBar extends StatelessWidget {
|
|||
}
|
||||
if (suggestion['type'] == 'emote') {
|
||||
final ratio = MediaQuery.of(context).devicePixelRatio;
|
||||
final url = Uri.parse(suggestion['mxc'] ?? '')?.getThumbnail(
|
||||
final url = Uri.parse(suggestion['mxc'] ?? '').getThumbnail(
|
||||
room.client,
|
||||
width: size * ratio,
|
||||
height: size * ratio,
|
||||
|
|
@ -224,7 +223,7 @@ class InputBar extends StatelessWidget {
|
|||
height: size,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(suggestion['name']),
|
||||
Text(suggestion['name']!),
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
|
|
@ -239,7 +238,7 @@ class InputBar extends StatelessWidget {
|
|||
size: size * 0.9,
|
||||
client: client,
|
||||
)
|
||||
: Text(suggestion['pack_display_name']),
|
||||
: Text(suggestion['pack_display_name']!),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -262,7 +261,7 @@ class InputBar extends StatelessWidget {
|
|||
client: client,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(suggestion['displayname'] ?? suggestion['mxid']),
|
||||
Text(suggestion['displayname'] ?? suggestion['mxid']!),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
@ -270,16 +269,16 @@ class InputBar extends StatelessWidget {
|
|||
return Container();
|
||||
}
|
||||
|
||||
void insertSuggestion(_, Map<String, String> suggestion) {
|
||||
void insertSuggestion(_, Map<String, String?> suggestion) {
|
||||
final replaceText =
|
||||
controller.text.substring(0, controller.selection.baseOffset);
|
||||
controller!.text.substring(0, controller!.selection.baseOffset);
|
||||
var startText = '';
|
||||
final afterText = replaceText == controller.text
|
||||
final afterText = replaceText == controller!.text
|
||||
? ''
|
||||
: controller.text.substring(controller.selection.baseOffset + 1);
|
||||
: controller!.text.substring(controller!.selection.baseOffset + 1);
|
||||
var insertText = '';
|
||||
if (suggestion['type'] == 'command') {
|
||||
insertText = suggestion['name'] + ' ';
|
||||
insertText = suggestion['name']! + ' ';
|
||||
startText = replaceText.replaceAllMapped(
|
||||
RegExp(r'^(\/[\w]*)$'),
|
||||
(Match m) => '/' + insertText,
|
||||
|
|
@ -304,29 +303,29 @@ class InputBar extends StatelessWidget {
|
|||
break;
|
||||
}
|
||||
}
|
||||
insertText = ':${isUnique ? '' : insertPack + '~'}$insertEmote: ';
|
||||
insertText = ':${isUnique ? '' : insertPack! + '~'}$insertEmote: ';
|
||||
startText = replaceText.replaceAllMapped(
|
||||
RegExp(r'(\s|^)(:(?:[-\w]+~)?[-\w]+)$'),
|
||||
(Match m) => '${m[1]}$insertText',
|
||||
);
|
||||
}
|
||||
if (suggestion['type'] == 'user') {
|
||||
insertText = suggestion['mention'] + ' ';
|
||||
insertText = suggestion['mention']! + ' ';
|
||||
startText = replaceText.replaceAllMapped(
|
||||
RegExp(r'(\s|^)(@[-\w]+)$'),
|
||||
(Match m) => '${m[1]}$insertText',
|
||||
);
|
||||
}
|
||||
if (suggestion['type'] == 'room') {
|
||||
insertText = suggestion['mxid'] + ' ';
|
||||
insertText = suggestion['mxid']! + ' ';
|
||||
startText = replaceText.replaceAllMapped(
|
||||
RegExp(r'(\s|^)(#[-\w]+)$'),
|
||||
(Match m) => '${m[1]}$insertText',
|
||||
);
|
||||
}
|
||||
if (insertText.isNotEmpty && startText.isNotEmpty) {
|
||||
controller.text = startText + afterText;
|
||||
controller.selection = TextSelection(
|
||||
controller!.text = startText + afterText;
|
||||
controller!.selection = TextSelection(
|
||||
baseOffset: startText.length,
|
||||
extentOffset: startText.length,
|
||||
);
|
||||
|
|
@ -351,13 +350,13 @@ class InputBar extends StatelessWidget {
|
|||
? {}
|
||||
: {
|
||||
NewLineIntent: CallbackAction(onInvoke: (i) {
|
||||
final val = controller.value;
|
||||
final val = controller!.value;
|
||||
final selection = val.selection.start;
|
||||
final messageWithoutNewLine =
|
||||
controller.text.substring(0, val.selection.start) +
|
||||
controller!.text.substring(0, val.selection.start) +
|
||||
'\n' +
|
||||
controller.text.substring(val.selection.end);
|
||||
controller.value = TextEditingValue(
|
||||
controller!.text.substring(val.selection.end);
|
||||
controller!.value = TextEditingValue(
|
||||
text: messageWithoutNewLine,
|
||||
selection: TextSelection.fromPosition(
|
||||
TextPosition(offset: selection + 1),
|
||||
|
|
@ -366,11 +365,11 @@ class InputBar extends StatelessWidget {
|
|||
return null;
|
||||
}),
|
||||
SubmitLineIntent: CallbackAction(onInvoke: (i) {
|
||||
onSubmitted(controller.text);
|
||||
onSubmitted!(controller!.text);
|
||||
return null;
|
||||
}),
|
||||
},
|
||||
child: TypeAheadField<Map<String, String>>(
|
||||
child: TypeAheadField<Map<String, String?>>(
|
||||
direction: AxisDirection.up,
|
||||
hideOnEmpty: true,
|
||||
hideOnLoading: true,
|
||||
|
|
@ -381,31 +380,31 @@ class InputBar extends StatelessWidget {
|
|||
textFieldConfiguration: TextFieldConfiguration(
|
||||
minLines: minLines,
|
||||
maxLines: maxLines,
|
||||
keyboardType: keyboardType,
|
||||
keyboardType: keyboardType!,
|
||||
textInputAction: textInputAction,
|
||||
autofocus: autofocus,
|
||||
autofocus: autofocus!,
|
||||
onSubmitted: (text) {
|
||||
// fix for library for now
|
||||
// it sets the types for the callback incorrectly
|
||||
onSubmitted(text);
|
||||
onSubmitted!(text);
|
||||
},
|
||||
//focusNode: focusNode,
|
||||
controller: controller,
|
||||
decoration: decoration,
|
||||
decoration: decoration!,
|
||||
focusNode: focusNode,
|
||||
onChanged: (text) {
|
||||
// fix for the library for now
|
||||
// it sets the types for the callback incorrectly
|
||||
onChanged(text);
|
||||
onChanged!(text);
|
||||
},
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
),
|
||||
suggestionsCallback: getSuggestions,
|
||||
itemBuilder: (c, s) =>
|
||||
buildSuggestion(c, s, Matrix.of(context).client),
|
||||
onSuggestionSelected: (Map<String, String> suggestion) =>
|
||||
onSuggestionSelected: (Map<String, String?> suggestion) =>
|
||||
insertSuggestion(context, suggestion),
|
||||
errorBuilder: (BuildContext context, Object error) => Container(),
|
||||
errorBuilder: (BuildContext context, Object? error) => Container(),
|
||||
loadingBuilder: (BuildContext context) =>
|
||||
Container(), // fix loading briefly flickering a dark box
|
||||
noItemsFoundBuilder: (BuildContext context) =>
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ import 'package:fluffychat/pages/chat/chat.dart';
|
|||
|
||||
class ReactionsPicker extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
const ReactionsPicker(this.controller, {Key key}) : super(key: key);
|
||||
const ReactionsPicker(this.controller, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (controller.showEmojiPicker) return Container();
|
||||
final display = controller.editEvent == null &&
|
||||
controller.replyEvent == null &&
|
||||
controller.room.canSendDefaultMessages &&
|
||||
controller.room!.canSendDefaultMessages &&
|
||||
controller.selectedEvents.isNotEmpty;
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
|
|
@ -28,8 +28,9 @@ class ReactionsPicker extends StatelessWidget {
|
|||
}
|
||||
final emojis = List<String>.from(AppEmojis.emojis);
|
||||
final allReactionEvents = controller.selectedEvents.first
|
||||
.aggregatedEvents(controller.timeline, RelationshipTypes.reaction)
|
||||
?.where((event) =>
|
||||
.aggregatedEvents(
|
||||
controller.timeline!, RelationshipTypes.reaction)
|
||||
.where((event) =>
|
||||
event.senderId == event.room.client.userID &&
|
||||
event.type == 'm.reaction');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
//@dart=2.12
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import 'events/reply_content.dart';
|
|||
|
||||
class ReplyDisplay extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
const ReplyDisplay(this.controller, {Key key}) : super(key: key);
|
||||
const ReplyDisplay(this.controller, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -23,16 +23,16 @@ class ReplyDisplay extends StatelessWidget {
|
|||
child: Row(
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
tooltip: L10n.of(context).close,
|
||||
tooltip: L10n.of(context)!.close,
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: controller.cancelReplyEventAction,
|
||||
),
|
||||
Expanded(
|
||||
child: controller.replyEvent != null
|
||||
? ReplyContent(controller.replyEvent,
|
||||
timeline: controller.timeline)
|
||||
? ReplyContent(controller.replyEvent!,
|
||||
timeline: controller.timeline!)
|
||||
: _EditContent(controller.editEvent
|
||||
?.getDisplayEvent(controller.timeline)),
|
||||
?.getDisplayEvent(controller.timeline!)),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -42,7 +42,7 @@ class ReplyDisplay extends StatelessWidget {
|
|||
}
|
||||
|
||||
class _EditContent extends StatelessWidget {
|
||||
final Event event;
|
||||
final Event? event;
|
||||
|
||||
const _EditContent(this.event);
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ class _EditContent extends StatelessWidget {
|
|||
Container(width: 15.0),
|
||||
Text(
|
||||
event?.getLocalizedBody(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: false,
|
||||
hideReply: true,
|
||||
) ??
|
||||
|
|
@ -68,7 +68,7 @@ class _EditContent extends StatelessWidget {
|
|||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodyText2.color,
|
||||
color: Theme.of(context).textTheme.bodyText2!.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ import 'package:fluffychat/widgets/matrix.dart';
|
|||
|
||||
class SeenByRow extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
const SeenByRow(this.controller, {Key key}) : super(key: key);
|
||||
const SeenByRow(this.controller, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final seenByUsers = controller.room.getSeenByUsers(
|
||||
controller.timeline,
|
||||
final seenByUsers = controller.room!.getSeenByUsers(
|
||||
controller.timeline!,
|
||||
controller.filteredEvents,
|
||||
controller.unfolded,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
//@dart=2.12
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ class SendLocationDialog extends StatefulWidget {
|
|||
final Room room;
|
||||
|
||||
const SendLocationDialog({
|
||||
this.room,
|
||||
Key key,
|
||||
required this.room,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -27,8 +27,8 @@ class _SendLocationDialogState extends State<SendLocationDialog> {
|
|||
bool disabled = false;
|
||||
bool denied = false;
|
||||
bool isSending = false;
|
||||
Position position;
|
||||
Error error;
|
||||
Position? position;
|
||||
Object? error;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -75,9 +75,9 @@ class _SendLocationDialogState extends State<SendLocationDialog> {
|
|||
void sendAction() async {
|
||||
setState(() => isSending = true);
|
||||
final body =
|
||||
'https://www.openstreetmap.org/?mlat=${position.latitude}&mlon=${position.longitude}#map=16/${position.latitude}/${position.longitude}';
|
||||
'https://www.openstreetmap.org/?mlat=${position!.latitude}&mlon=${position!.longitude}#map=16/${position!.latitude}/${position!.longitude}';
|
||||
final uri =
|
||||
'geo:${position.latitude},${position.longitude};u=${position.accuracy}';
|
||||
'geo:${position!.latitude},${position!.longitude};u=${position!.accuracy}';
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => widget.room.sendLocation(body, uri),
|
||||
|
|
@ -90,16 +90,16 @@ class _SendLocationDialogState extends State<SendLocationDialog> {
|
|||
Widget contentWidget;
|
||||
if (position != null) {
|
||||
contentWidget = MapBubble(
|
||||
latitude: position.latitude,
|
||||
longitude: position.longitude,
|
||||
latitude: position!.latitude,
|
||||
longitude: position!.longitude,
|
||||
);
|
||||
} else if (disabled) {
|
||||
contentWidget = Text(L10n.of(context).locationDisabledNotice);
|
||||
contentWidget = Text(L10n.of(context)!.locationDisabledNotice);
|
||||
} else if (denied) {
|
||||
contentWidget = Text(L10n.of(context).locationPermissionDeniedNotice);
|
||||
contentWidget = Text(L10n.of(context)!.locationPermissionDeniedNotice);
|
||||
} else if (error != null) {
|
||||
contentWidget =
|
||||
Text(L10n.of(context).errorObtainingLocation(error.toString()));
|
||||
Text(L10n.of(context)!.errorObtainingLocation(error.toString()));
|
||||
} else {
|
||||
contentWidget = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
|
@ -107,38 +107,38 @@ class _SendLocationDialogState extends State<SendLocationDialog> {
|
|||
children: [
|
||||
const CupertinoActivityIndicator(),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context).obtainingLocation),
|
||||
Text(L10n.of(context)!.obtainingLocation),
|
||||
],
|
||||
);
|
||||
}
|
||||
if (PlatformInfos.isCupertinoStyle) {
|
||||
return CupertinoAlertDialog(
|
||||
title: Text(L10n.of(context).shareLocation),
|
||||
title: Text(L10n.of(context)!.shareLocation),
|
||||
content: contentWidget,
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
onPressed: Navigator.of(context, rootNavigator: false).pop,
|
||||
child: Text(L10n.of(context).cancel),
|
||||
child: Text(L10n.of(context)!.cancel),
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
onPressed: isSending ? null : sendAction,
|
||||
child: Text(L10n.of(context).send),
|
||||
child: Text(L10n.of(context)!.send),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return AlertDialog(
|
||||
title: Text(L10n.of(context).shareLocation),
|
||||
title: Text(L10n.of(context)!.shareLocation),
|
||||
content: contentWidget,
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Navigator.of(context, rootNavigator: false).pop,
|
||||
child: Text(L10n.of(context).cancel),
|
||||
child: Text(L10n.of(context)!.cancel),
|
||||
),
|
||||
if (position != null)
|
||||
TextButton(
|
||||
onPressed: isSending ? null : sendAction,
|
||||
child: Text(L10n.of(context).send),
|
||||
child: Text(L10n.of(context)!.send),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ import 'events/image_bubble.dart';
|
|||
class StickerPickerDialog extends StatefulWidget {
|
||||
final Room room;
|
||||
|
||||
const StickerPickerDialog({this.room, Key key}) : super(key: key);
|
||||
const StickerPickerDialog({required this.room, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
StickerPickerDialogState createState() => StickerPickerDialogState();
|
||||
}
|
||||
|
||||
class StickerPickerDialogState extends State<StickerPickerDialog> {
|
||||
String searchFilter;
|
||||
String? searchFilter;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -26,14 +26,14 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
|
|||
|
||||
// ignore: prefer_function_declarations_over_variables
|
||||
final _packBuilder = (BuildContext context, int packIndex) {
|
||||
final pack = stickerPacks[packSlugs[packIndex]];
|
||||
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.key.toLowerCase().contains(searchFilter!.toLowerCase()) ||
|
||||
(e.value.body
|
||||
?.toLowerCase()
|
||||
?.contains(searchFilter.toLowerCase()) ??
|
||||
.contains(searchFilter!.toLowerCase()) ??
|
||||
false)));
|
||||
}
|
||||
final imageKeys =
|
||||
|
|
@ -62,7 +62,7 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
|
|||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (BuildContext context, int imageIndex) {
|
||||
final image = pack.images[imageKeys[imageIndex]];
|
||||
final image = pack.images[imageKeys[imageIndex]]!;
|
||||
final fakeEvent = Event.fromJson(<String, dynamic>{
|
||||
'type': EventTypes.Sticker,
|
||||
'content': <String, dynamic>{
|
||||
|
|
@ -116,7 +116,7 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
|
|||
),
|
||||
title: DefaultAppBarSearchField(
|
||||
autofocus: false,
|
||||
hintText: L10n.of(context).search,
|
||||
hintText: L10n.of(context)!.search,
|
||||
suffix: const Icon(Icons.search_outlined),
|
||||
onChanged: (s) => setState(() => searchFilter = s),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ import 'chat.dart';
|
|||
|
||||
class TombstoneDisplay extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
const TombstoneDisplay(this.controller, {Key key}) : super(key: key);
|
||||
const TombstoneDisplay(this.controller, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (controller.room.getState(EventTypes.RoomTombstone) == null) {
|
||||
if (controller.room!.getState(EventTypes.RoomTombstone) == null) {
|
||||
return Container();
|
||||
}
|
||||
return SizedBox(
|
||||
|
|
@ -26,14 +26,14 @@ class TombstoneDisplay extends StatelessWidget {
|
|||
child: const Icon(Icons.upgrade_outlined),
|
||||
),
|
||||
title: Text(
|
||||
controller.room
|
||||
.getState(EventTypes.RoomTombstone)
|
||||
controller.room!
|
||||
.getState(EventTypes.RoomTombstone)!
|
||||
.parsedTombstoneContent
|
||||
.body,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
subtitle: Text(L10n.of(context).goToTheNewRoom),
|
||||
subtitle: Text(L10n.of(context)!.goToTheNewRoom),
|
||||
onTap: controller.goToNewRoomAction,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import 'package:fluffychat/widgets/matrix.dart';
|
|||
|
||||
class TypingIndicators extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
const TypingIndicators(this.controller, {Key key}) : super(key: key);
|
||||
const TypingIndicators(this.controller, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final typingUsers = controller.room.typingUsers
|
||||
final typingUsers = controller.room!.typingUsers
|
||||
..removeWhere((u) => u.stateKey == Matrix.of(context).client.userID);
|
||||
const topPadding = 20.0;
|
||||
const bottomPadding = 4.0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue