refactor: Not nullable room in ChatPage

This commit is contained in:
Krille 2023-03-25 15:06:12 +01:00
parent 2f6799470c
commit 2acf49a12b
No known key found for this signature in database
10 changed files with 102 additions and 95 deletions

View File

@ -72,7 +72,7 @@ class AppRoutes {
),
VWidget(
path: ':roomid',
widget: const Chat(),
widget: const ChatPage(),
stackedRoutes: [
VWidget(
path: 'encryption',
@ -100,7 +100,7 @@ class AppRoutes {
stackedRoutes: [
VWidget(
path: ':roomid',
widget: const Chat(),
widget: const ChatPage(),
buildTransition: _dynamicTransition,
),
],
@ -174,14 +174,14 @@ class AppRoutes {
VNester(
path: ':roomid',
widgetBuilder: (child) => SideViewLayout(
mainView: const Chat(),
mainView: const ChatPage(),
sideView: child,
),
buildTransition: _fadeTransition,
nestedRoutes: [
VWidget(
path: '',
widget: const Chat(),
widget: const ChatPage(),
buildTransition: _fadeTransition,
),
VWidget(
@ -245,7 +245,7 @@ class AppRoutes {
),
VWidget(
path: ':roomid',
widget: const Chat(),
widget: const ChatPage(),
buildTransition: _dynamicTransition,
),
],

View File

@ -35,17 +35,48 @@ import 'send_file_dialog.dart';
import 'send_location_dialog.dart';
import 'sticker_picker_dialog.dart';
class Chat extends StatefulWidget {
class ChatPage extends StatelessWidget {
final Widget? sideView;
const Chat({Key? key, this.sideView}) : super(key: key);
const ChatPage({Key? key, this.sideView}) : super(key: key);
@override
Widget build(BuildContext context) {
final roomId = context.vRouter.pathParameters['roomid'];
final room =
roomId == null ? null : Matrix.of(context).client.getRoomById(roomId);
if (room == null) {
return Scaffold(
appBar: AppBar(title: Text(L10n.of(context)!.oopsSomethingWentWrong)),
body: Center(
child: Padding(
padding: const EdgeInsets.all(16),
child:
Text(L10n.of(context)!.youAreNoLongerParticipatingInThisChat),
),
),
);
}
return ChatPageWithRoom(sideView: sideView, room: room);
}
}
class ChatPageWithRoom extends StatefulWidget {
final Widget? sideView;
final Room room;
const ChatPageWithRoom({
Key? key,
required this.sideView,
required this.room,
}) : super(key: key);
@override
ChatController createState() => ChatController();
}
class ChatController extends State<Chat> {
Room? room;
class ChatController extends State<ChatPageWithRoom> {
Room get room => widget.room;
late Client sendingClient;
@ -53,7 +84,7 @@ class ChatController extends State<Chat> {
String? readMarkerEventId;
String? get roomId => context.vRouter.pathParameters['roomid'];
String get roomId => widget.room.id;
final AutoScrollController scrollController = AutoScrollController();
@ -95,7 +126,7 @@ class ChatController extends State<Chat> {
useRootNavigator: false,
builder: (c) => SendFileDialog(
files: matrixFiles,
room: room!,
room: room,
),
);
}
@ -134,13 +165,13 @@ class ChatController extends State<Chat> {
bool get lastReadEventVisible =>
timeline == null ||
room!.fullyRead.isEmpty ||
timeline!.events.any((event) => event.eventId == room!.fullyRead);
room.fullyRead.isEmpty ||
timeline!.events.any((event) => event.eventId == room.fullyRead);
void recreateChat() async {
final room = this.room;
final userId = room?.directChatMatrixID;
if (room == null || userId == null) {
final userId = room.directChatMatrixID;
if (userId == null) {
throw Exception(
'Try to recreate a room with is not a DM room. This should not be possible from the UI!',
);
@ -162,12 +193,6 @@ class ChatController extends State<Chat> {
}
void leaveChat() async {
final room = this.room;
if (room == null) {
throw Exception(
'Leave room button clicked while room is null. This should not be possible from the UI!',
);
}
final success = await showFutureLoadingDialog(
context: context,
future: room.leave,
@ -262,12 +287,12 @@ class ChatController extends State<Chat> {
if (timeline == null) {
await Matrix.of(context).client.roomsLoading;
await Matrix.of(context).client.accountDataLoading;
timeline = await room!.getTimeline(
timeline = await room.getTimeline(
onUpdate: updateView,
eventContextId: eventContextId,
);
if (timeline!.events.isNotEmpty) {
if (room!.markedUnread) room!.markUnread(false);
if (room.markedUnread) room.markUnread(false);
setReadMarker();
}
@ -293,8 +318,8 @@ class ChatController extends State<Chat> {
void setReadMarker({String? eventId}) {
if (_setReadMarkerFuture != null) return;
if (lastReadEventVisible &&
!room!.hasNewMessages &&
room!.notificationCount == 0) {
!room.hasNewMessages &&
room.notificationCount == 0) {
return;
}
if (!Matrix.of(context).webHasFocus) return;
@ -312,7 +337,7 @@ class ChatController extends State<Chat> {
_setReadMarkerFuture = timeline.setReadMarker(eventId).then((_) {
_setReadMarkerFuture = null;
});
room!.client.updateIosBadge();
room.client.updateIosBadge();
}
@override
@ -331,7 +356,7 @@ class ChatController extends State<Chat> {
// 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
@ -351,7 +376,7 @@ class ChatController extends State<Chat> {
final commandMatch = RegExp(r'^\/(\w+)').firstMatch(sendController.text);
if (commandMatch != null &&
!room!.client.commands.keys.contains(commandMatch[1]!.toLowerCase())) {
!room.client.commands.keys.contains(commandMatch[1]!.toLowerCase())) {
final l10n = L10n.of(context)!;
final dialogResult = await showOkCancelAlertDialog(
context: context,
@ -366,7 +391,7 @@ class ChatController extends State<Chat> {
}
// ignore: unawaited_futures
room!.sendTextEvent(
room.sendTextEvent(
sendController.text,
inReplyTo: replyEvent,
editEventId: editEvent?.eventId,
@ -403,7 +428,7 @@ class ChatController extends State<Chat> {
).detectFileType,
)
.toList(),
room: room!,
room: room,
),
);
}
@ -428,7 +453,7 @@ class ChatController extends State<Chat> {
).detectFileType,
)
.toList(),
room: room!,
room: room,
),
);
}
@ -449,7 +474,7 @@ class ChatController extends State<Chat> {
name: file.path,
)
],
room: room!,
room: room,
),
);
}
@ -470,7 +495,7 @@ class ChatController extends State<Chat> {
name: file.path,
)
],
room: room!,
room: room,
),
);
}
@ -478,7 +503,7 @@ class ChatController extends State<Chat> {
void sendStickerAction() async {
final sticker = await showAdaptiveBottomSheet<ImagePackImageContent>(
context: context,
builder: (c) => StickerPickerDialog(room: room!),
builder: (c) => StickerPickerDialog(room: room),
);
if (sticker == null) return;
final eventContent = <String, dynamic>{
@ -487,7 +512,7 @@ class ChatController extends State<Chat> {
'url': sticker.url.toString(),
};
// send the sticker
await room!.sendEvent(
await room.sendEvent(
eventContent,
type: EventTypes.Sticker,
);
@ -521,7 +546,7 @@ class ChatController extends State<Chat> {
bytes: audioFile.readAsBytesSync(),
name: audioFile.path,
);
await room!.sendFileEvent(
await room.sendFileEvent(
file,
inReplyTo: replyEvent,
extraContent: {
@ -571,7 +596,7 @@ class ChatController extends State<Chat> {
await showDialog(
context: context,
useRootNavigator: false,
builder: (c) => SendLocationDialog(room: room!),
builder: (c) => SendLocationDialog(room: room),
);
}
@ -677,7 +702,7 @@ class ChatController extends State<Chat> {
if (client == null) {
return;
}
final room = client.getRoomById(roomId!)!;
final room = client.getRoomById(roomId)!;
await Event.fromJson(event.toJson(), room).redactEvent();
}
} else {
@ -694,7 +719,7 @@ class ChatController extends State<Chat> {
List<Client?> get currentRoomBundle {
final clients = Matrix.of(context).currentBundle!;
clients.removeWhere((c) => c!.getRoomById(roomId!) == null);
clients.removeWhere((c) => c!.getRoomById(roomId) == null);
return clients;
}
@ -808,7 +833,7 @@ class ChatController extends State<Chat> {
void forgetRoom() async {
final result = await showFutureLoadingDialog(
context: context,
future: room!.forget,
future: room.forget,
);
if (result.error != null) return;
VRouter.of(context).to('/archive');
@ -857,7 +882,7 @@ class ChatController extends State<Chat> {
final events = List<Event>.from(selectedEvents);
setState(() => selectedEvents.clear());
for (final event in events) {
await room!.sendReaction(
await room.sendReaction(
event.eventId,
emoji!,
);
@ -904,7 +929,7 @@ class ChatController extends State<Chat> {
useRootNavigator: false,
context: context,
title: L10n.of(context)!.goToTheNewRoom,
message: room!
message: room
.getState(EventTypes.RoomTombstone)!
.parsedTombstoneContent
.body,
@ -915,8 +940,8 @@ class ChatController extends State<Chat> {
}
final result = await showFutureLoadingDialog(
context: context,
future: () => room!.client.joinRoom(
room!
future: () => room.client.joinRoom(
room
.getState(EventTypes.RoomTombstone)!
.parsedTombstoneContent
.replacementRoom,
@ -924,7 +949,7 @@ class ChatController extends State<Chat> {
);
await showFutureLoadingDialog(
context: context,
future: room!.leave,
future: room.leave,
);
if (result.error == null) {
VRouter.of(context).toSegments(['rooms', result.result!]);
@ -1001,18 +1026,16 @@ class ChatController extends State<Chat> {
cancelLabel: L10n.of(context)!.cancel,
);
if (response == OkCancelResult.ok) {
final events = room!.pinnedEventIds
final events = room.pinnedEventIds
..removeWhere((oldEvent) => oldEvent == eventId);
showFutureLoadingDialog(
context: context,
future: () => room!.setPinnedEvents(events),
future: () => room.setPinnedEvents(events),
);
}
}
void pinEvent() {
final room = this.room;
if (room == null) return;
final pinnedEventIds = room.pinnedEventIds;
final selectedEventIds = selectedEvents.map((e) => e.eventId).toSet();
final unpin = selectedEventIds.length == 1 &&
@ -1057,7 +1080,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;
@ -1065,14 +1088,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);
}
bool get isArchived =>
{Membership.leave, Membership.ban}.contains(room?.membership);
{Membership.leave, Membership.ban}.contains(room.membership);
void showEventInfo([Event? event]) =>
(event ?? selectedEvents.single).showInfoDialog(context);
@ -1120,7 +1142,7 @@ class ChatController extends State<Chat> {
if (success.result != null) {
final voipPlugin = Matrix.of(context).voipPlugin;
try {
await voipPlugin!.voip.inviteToCall(room!.id, callType);
await voipPlugin!.voip.inviteToCall(room.id, callType);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.toLocalizedString(context))),

View File

@ -16,9 +16,6 @@ class ChatAppBarTitle extends StatelessWidget {
@override
Widget build(BuildContext context) {
final room = controller.room;
if (room == null) {
return const SizedBox.shrink();
}
if (controller.selectedEvents.isNotEmpty) {
return Text(controller.selectedEvents.length.toString());
}

View File

@ -146,7 +146,7 @@ class ChatInputRow extends StatelessWidget {
contentPadding: const EdgeInsets.all(0),
),
),
if (controller.room!
if (controller.room
.getImagePacks(ImagePackUsage.sticker)
.isNotEmpty)
PopupMenuItem<String>(
@ -227,7 +227,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,

View File

@ -125,37 +125,26 @@ class ChatView extends StatelessWidget {
} else {
return [
if (Matrix.of(context).voipPlugin != null &&
controller.room!.isDirectChat)
controller.room.isDirectChat)
IconButton(
onPressed: controller.onPhoneButtonTap,
icon: const Icon(Icons.call_outlined),
tooltip: L10n.of(context)!.placeCall,
),
EncryptionButton(controller.room!),
ChatSettingsPopupMenu(controller.room!, !controller.room!.isDirectChat),
EncryptionButton(controller.room),
ChatSettingsPopupMenu(controller.room, !controller.room.isDirectChat),
];
}
}
@override
Widget build(BuildContext context) {
controller.room = controller.sendingClient.getRoomById(controller.roomId!);
controller.readMarkerEventId ??= controller.room!.fullyRead;
if (controller.room == null) {
return Scaffold(
appBar: AppBar(
title: Text(L10n.of(context)!.oopsSomethingWentWrong),
),
body: Center(
child: Text(L10n.of(context)!.youAreNoLongerParticipatingInThisChat),
),
);
}
controller.readMarkerEventId ??= controller.room.fullyRead;
if (controller.room!.membership == Membership.invite) {
if (controller.room.membership == Membership.invite) {
showFutureLoadingDialog(
context: context,
future: () => controller.room!.join(),
future: () => controller.room.join(),
);
}
final bottomSheetPadding = FluffyThemes.isColumnMode(context) ? 16.0 : 8.0;
@ -175,7 +164,7 @@ class ChatView extends StatelessWidget {
onTapDown: (_) => controller.setReadMarker(),
behavior: HitTestBehavior.opaque,
child: StreamBuilder(
stream: controller.room!.onUpdate.stream
stream: controller.room.onUpdate.stream
.rateLimit(const Duration(seconds: 1)),
builder: (context, snapshot) => FutureBuilder(
future: controller.getTimeline(),
@ -196,7 +185,7 @@ class ChatView extends StatelessWidget {
color: Theme.of(context).colorScheme.primary,
)
: UnreadRoomsBadge(
filter: (r) => r.id != controller.roomId!,
filter: (r) => r.id != controller.roomId,
badgePosition: BadgePosition.topEnd(end: 8, top: 4),
child: const Center(child: BackButton()),
),
@ -269,8 +258,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,
@ -295,7 +284,7 @@ class ChatView extends StatelessWidget {
Brightness.light
? Colors.white
: Colors.black,
child: controller.room?.isAbandonedDMRoom ==
child: controller.room.isAbandonedDMRoom ==
true
? Row(
mainAxisAlignment:
@ -359,14 +348,14 @@ class ChatView extends StatelessWidget {
child: FloatingActionButton.extended(
icon: const Icon(Icons.arrow_upward_outlined),
onPressed: () => controller
.scrollToEventId(controller.room!.fullyRead),
.scrollToEventId(controller.room.fullyRead),
label: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(L10n.of(context)!.jumpToLastReadMessage),
IconButton(
onPressed: () => controller.setReadMarker(
eventId: controller.room!.fullyRead,
eventId: controller.room.fullyRead,
),
icon: const Icon(Icons.close),
),

View File

@ -46,14 +46,14 @@ class PinnedEvents extends StatelessWidget {
@override
Widget build(BuildContext context) {
final pinnedEventIds = controller.room!.pinnedEventIds;
final pinnedEventIds = controller.room.pinnedEventIds;
if (pinnedEventIds.isEmpty) {
return const SizedBox.shrink();
}
final completers = pinnedEventIds.map<Completer<Event?>>((e) {
final completer = Completer<Event?>();
controller.room!
controller.room
.getEventById(e)
.then((value) => completer.complete(value));
return completer;
@ -86,11 +86,10 @@ class PinnedEvents extends StatelessWidget {
color: Theme.of(context).colorScheme.onSurfaceVariant,
icon: const Icon(Icons.push_pin),
tooltip: L10n.of(context)!.unpin,
onPressed: controller.room
?.canSendEvent(EventTypes.RoomPinnedEvents) ??
false
? () => controller.unpinEvent(event.eventId)
: null,
onPressed:
controller.room.canSendEvent(EventTypes.RoomPinnedEvents)
? () => controller.unpinEvent(event.eventId)
: null,
),
Expanded(
child: Padding(

View File

@ -18,7 +18,7 @@ class ReactionsPicker extends StatelessWidget {
if (controller.showEmojiPicker) return const SizedBox.shrink();
final display = controller.editEvent == null &&
controller.replyEvent == null &&
controller.room!.canSendDefaultMessages &&
controller.room.canSendDefaultMessages &&
controller.selectedEvents.isNotEmpty;
return AnimatedContainer(
duration: FluffyThemes.animationDuration,

View File

@ -12,7 +12,7 @@ class SeenByRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
final seenByUsers = controller.room!.getSeenByUsers(controller.timeline!);
final seenByUsers = controller.room.getSeenByUsers(controller.timeline!);
const maxAvatars = 7;
return Container(
width: double.infinity,

View File

@ -11,7 +11,7 @@ class TombstoneDisplay extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (controller.room!.getState(EventTypes.RoomTombstone) == null) {
if (controller.room.getState(EventTypes.RoomTombstone) == null) {
return const SizedBox.shrink();
}
return SizedBox(
@ -26,7 +26,7 @@ class TombstoneDisplay extends StatelessWidget {
child: const Icon(Icons.upgrade_outlined),
),
title: Text(
controller.room!
controller.room
.getState(EventTypes.RoomTombstone)!
.parsedTombstoneContent
.body,

View File

@ -12,7 +12,7 @@ class TypingIndicators extends StatelessWidget {
@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;