add /room/:room/:thread route
edit chat.dart to support threads edit SendFileDialog and SendPollDialog to support threads
This commit is contained in:
parent
74c05be615
commit
251ca9893d
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:extera_next/pages/thread/thread.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
@ -148,6 +149,21 @@ abstract class AppRoutes {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
redirect: loggedOutRedirect,
|
redirect: loggedOutRedirect,
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: ':threadroot',
|
||||||
|
pageBuilder: (context, state) => defaultPageBuilder(
|
||||||
|
context,
|
||||||
|
state,
|
||||||
|
ThreadPage(
|
||||||
|
roomId: state.pathParameters['roomid']!,
|
||||||
|
threadRootEventId:
|
||||||
|
state.pathParameters['threadroot']!,
|
||||||
|
eventId: state.uri.queryParameters['event'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
redirect: loggedOutRedirect,
|
redirect: loggedOutRedirect,
|
||||||
|
|
@ -352,6 +368,18 @@ abstract class AppRoutes {
|
||||||
},
|
},
|
||||||
redirect: loggedOutRedirect,
|
redirect: loggedOutRedirect,
|
||||||
routes: [
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: ':threadroot',
|
||||||
|
pageBuilder: (context, state) => defaultPageBuilder(
|
||||||
|
context,
|
||||||
|
state,
|
||||||
|
ThreadPage(
|
||||||
|
roomId: state.pathParameters['roomid']!,
|
||||||
|
threadRootEventId: state.pathParameters['threadroot']!,
|
||||||
|
eventId: state.uri.queryParameters['event'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'search',
|
path: 'search',
|
||||||
pageBuilder: (context, state) => defaultPageBuilder(
|
pageBuilder: (context, state) => defaultPageBuilder(
|
||||||
|
|
|
||||||
|
|
@ -85,12 +85,14 @@ class ChatPage extends StatelessWidget {
|
||||||
|
|
||||||
class ChatPageWithRoom extends StatefulWidget {
|
class ChatPageWithRoom extends StatefulWidget {
|
||||||
final Room room;
|
final Room room;
|
||||||
|
final Thread? thread;
|
||||||
final List<ShareItem>? shareItems;
|
final List<ShareItem>? shareItems;
|
||||||
final String? eventId;
|
final String? eventId;
|
||||||
|
|
||||||
const ChatPageWithRoom({
|
const ChatPageWithRoom({
|
||||||
super.key,
|
super.key,
|
||||||
required this.room,
|
required this.room,
|
||||||
|
this.thread,
|
||||||
this.shareItems,
|
this.shareItems,
|
||||||
this.eventId,
|
this.eventId,
|
||||||
});
|
});
|
||||||
|
|
@ -102,14 +104,18 @@ class ChatPageWithRoom extends StatefulWidget {
|
||||||
class ChatController extends State<ChatPageWithRoom>
|
class ChatController extends State<ChatPageWithRoom>
|
||||||
with WidgetsBindingObserver {
|
with WidgetsBindingObserver {
|
||||||
Room get room => sendingClient.getRoomById(roomId) ?? widget.room;
|
Room get room => sendingClient.getRoomById(roomId) ?? widget.room;
|
||||||
|
Thread? get thread =>
|
||||||
|
sendingClient.getRoomById(roomId)?.threads[threadRootEventId] ??
|
||||||
|
widget.room.threads[threadRootEventId];
|
||||||
|
|
||||||
late Client sendingClient;
|
late Client sendingClient;
|
||||||
|
|
||||||
RoomTimeline? timeline;
|
Timeline? timeline;
|
||||||
|
|
||||||
late final String readMarkerEventId;
|
late final String readMarkerEventId;
|
||||||
|
|
||||||
String get roomId => widget.room.id;
|
String get roomId => widget.room.id;
|
||||||
|
String? get threadRootEventId => widget.thread?.rootEvent.eventId;
|
||||||
|
|
||||||
final AutoScrollController scrollController = AutoScrollController();
|
final AutoScrollController scrollController = AutoScrollController();
|
||||||
|
|
||||||
|
|
@ -134,6 +140,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
files: details.files,
|
files: details.files,
|
||||||
room: room,
|
room: room,
|
||||||
|
thread: thread,
|
||||||
replyEvent: replyEvent,
|
replyEvent: replyEvent,
|
||||||
outerContext: context,
|
outerContext: context,
|
||||||
),
|
),
|
||||||
|
|
@ -274,6 +281,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
files: files,
|
files: files,
|
||||||
room: room,
|
room: room,
|
||||||
|
thread: thread,
|
||||||
outerContext: context,
|
outerContext: context,
|
||||||
replyEvent: replyEvent,
|
replyEvent: replyEvent,
|
||||||
),
|
),
|
||||||
|
|
@ -324,6 +332,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
void _tryLoadTimeline() async {
|
void _tryLoadTimeline() async {
|
||||||
final initialEventId = widget.eventId;
|
final initialEventId = widget.eventId;
|
||||||
loadTimelineFuture = _getTimeline();
|
loadTimelineFuture = _getTimeline();
|
||||||
|
Logs().v("Trying to load timeline...");
|
||||||
try {
|
try {
|
||||||
await loadTimelineFuture;
|
await loadTimelineFuture;
|
||||||
if (initialEventId != null) scrollToEventId(initialEventId);
|
if (initialEventId != null) scrollToEventId(initialEventId);
|
||||||
|
|
@ -387,15 +396,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
animateInEventIndex = i;
|
animateInEventIndex = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _getTimeline({
|
Future<void> _loadRoomTimeline({String? eventContextId}) async {
|
||||||
String? eventContextId,
|
|
||||||
}) async {
|
|
||||||
await Matrix.of(context).client.roomsLoading;
|
|
||||||
await Matrix.of(context).client.accountDataLoading;
|
|
||||||
if (eventContextId != null &&
|
|
||||||
(!eventContextId.isValidMatrixId || eventContextId.sigil != '\$')) {
|
|
||||||
eventContextId = null;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
timeline?.cancelSubscriptions();
|
timeline?.cancelSubscriptions();
|
||||||
timeline = await room.getTimeline(
|
timeline = await room.getTimeline(
|
||||||
|
|
@ -415,6 +416,56 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
_showScrollUpMaterialBanner(eventContextId!);
|
_showScrollUpMaterialBanner(eventContextId!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadThreadTimeline({String? eventContextId}) async {
|
||||||
|
if (thread == null) {
|
||||||
|
throw Exception(
|
||||||
|
"_loadThreadTimeline should not be called, thread == null",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
timeline?.cancelSubscriptions();
|
||||||
|
timeline = await thread!.getTimeline(
|
||||||
|
onUpdate: updateView,
|
||||||
|
eventContextId: eventContextId,
|
||||||
|
onInsert: onInsert,
|
||||||
|
);
|
||||||
|
if (timeline is ThreadTimeline) {
|
||||||
|
(timeline as ThreadTimeline).getThreadEvents();
|
||||||
|
}
|
||||||
|
Logs().v("Thread timeline loaded");
|
||||||
|
} catch (e, s) {
|
||||||
|
Logs().w(
|
||||||
|
'Unable to load timeline on event ID $eventContextId (in thread)',
|
||||||
|
e,
|
||||||
|
s);
|
||||||
|
if (!mounted) return;
|
||||||
|
timeline = await thread!.getTimeline(
|
||||||
|
onUpdate: updateView,
|
||||||
|
onInsert: onInsert,
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (e is TimeoutException || e is IOException) {
|
||||||
|
_showScrollUpMaterialBanner(eventContextId!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _getTimeline({
|
||||||
|
String? eventContextId,
|
||||||
|
}) async {
|
||||||
|
await Matrix.of(context).client.roomsLoading;
|
||||||
|
await Matrix.of(context).client.accountDataLoading;
|
||||||
|
if (eventContextId != null &&
|
||||||
|
(!eventContextId.isValidMatrixId || eventContextId.sigil != '\$')) {
|
||||||
|
eventContextId = null;
|
||||||
|
}
|
||||||
|
if (thread == null) {
|
||||||
|
await _loadRoomTimeline(eventContextId: eventContextId);
|
||||||
|
} else {
|
||||||
|
await _loadThreadTimeline(eventContextId: eventContextId);
|
||||||
|
}
|
||||||
timeline!.requestKeys(onlineKeyBackupOnly: false);
|
timeline!.requestKeys(onlineKeyBackupOnly: false);
|
||||||
if (room.markedUnread) room.markUnread(false);
|
if (room.markedUnread) room.markUnread(false);
|
||||||
|
|
||||||
|
|
@ -471,9 +522,13 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
.then((_) {
|
.then((_) {
|
||||||
_setReadMarkerFuture = null;
|
_setReadMarkerFuture = null;
|
||||||
});
|
});
|
||||||
if (eventId == null || eventId == timeline.room.lastEvent?.eventId) {
|
|
||||||
Matrix.of(context).backgroundPush?.cancelNotification(roomId);
|
if (timeline is RoomTimeline) {
|
||||||
|
if (eventId == null || eventId == timeline.room.lastEvent?.eventId) {
|
||||||
|
Matrix.of(context).backgroundPush?.cancelNotification(roomId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// TODO same for Threads
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -540,12 +595,13 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: unawaited_futures
|
// ignore: unawaited_futures
|
||||||
room.sendTextEvent(
|
room.sendTextEvent(sendController.text,
|
||||||
sendController.text,
|
inReplyTo: replyEvent,
|
||||||
inReplyTo: replyEvent,
|
editEventId: editEvent?.eventId,
|
||||||
editEventId: editEvent?.eventId,
|
parseCommands: parseCommands,
|
||||||
parseCommands: parseCommands,
|
threadRootEventId: thread?.rootEvent.eventId,
|
||||||
);
|
threadLastEventId:
|
||||||
|
thread?.lastEvent?.eventId ?? thread?.rootEvent.eventId);
|
||||||
sendController.value = TextEditingValue(
|
sendController.value = TextEditingValue(
|
||||||
text: pendingText,
|
text: pendingText,
|
||||||
selection: const TextSelection.collapsed(offset: 0),
|
selection: const TextSelection.collapsed(offset: 0),
|
||||||
|
|
@ -562,8 +618,13 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
|
|
||||||
void sendPollAction() async {
|
void sendPollAction() async {
|
||||||
await showAdaptiveDialog(
|
await showAdaptiveDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (c) => SendPollDialog(room: room, outerContext: context));
|
builder: (c) => SendPollDialog(
|
||||||
|
room: room,
|
||||||
|
thread: thread,
|
||||||
|
outerContext: context,
|
||||||
|
),
|
||||||
|
);
|
||||||
replyEvent = null;
|
replyEvent = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -582,6 +643,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
files: files,
|
files: files,
|
||||||
room: room,
|
room: room,
|
||||||
|
thread: thread,
|
||||||
outerContext: context,
|
outerContext: context,
|
||||||
replyEvent: replyEvent,
|
replyEvent: replyEvent,
|
||||||
),
|
),
|
||||||
|
|
@ -596,6 +658,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
files: [XFile.fromData(image)],
|
files: [XFile.fromData(image)],
|
||||||
room: room,
|
room: room,
|
||||||
|
thread: thread,
|
||||||
outerContext: context,
|
outerContext: context,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -612,6 +675,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
files: [file],
|
files: [file],
|
||||||
room: room,
|
room: room,
|
||||||
|
thread: thread,
|
||||||
outerContext: context,
|
outerContext: context,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -631,6 +695,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
files: [file],
|
files: [file],
|
||||||
room: room,
|
room: room,
|
||||||
|
thread: thread,
|
||||||
outerContext: context,
|
outerContext: context,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ class ChatEventList extends StatelessWidget {
|
||||||
|
|
||||||
final horizontalPadding = FluffyThemes.isColumnMode(context) ? 8.0 : 0.0;
|
final horizontalPadding = FluffyThemes.isColumnMode(context) ? 8.0 : 0.0;
|
||||||
|
|
||||||
final events = timeline.events.filterByVisibleInGui().filterByThreaded(false);
|
final events = timeline.events.filterByVisibleInGui().filterByThreaded(controller.thread != null);
|
||||||
final animateInEventIndex = controller.animateInEventIndex;
|
final animateInEventIndex = controller.animateInEventIndex;
|
||||||
|
|
||||||
// create a map of eventId --> index to greatly improve performance of
|
// create a map of eventId --> index to greatly improve performance of
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,12 @@ import 'dart:ui' as ui;
|
||||||
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
|
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
|
||||||
import 'package:extera_next/utils/adaptive_bottom_sheet.dart';
|
import 'package:extera_next/utils/adaptive_bottom_sheet.dart';
|
||||||
import 'package:extera_next/utils/poll_events.dart';
|
import 'package:extera_next/utils/poll_events.dart';
|
||||||
|
import 'package:extera_next/widgets/mxc_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:extera_next/generated/l10n/l10n.dart';
|
import 'package:extera_next/generated/l10n/l10n.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
import 'package:swipe_to_action/swipe_to_action.dart';
|
import 'package:swipe_to_action/swipe_to_action.dart';
|
||||||
|
|
||||||
|
|
@ -690,11 +692,58 @@ class Message extends StatelessWidget {
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
thread != null
|
||||||
thread == null
|
? Align(
|
||||||
? 'No thread'
|
alignment: ownMessage
|
||||||
: 'Has thread, last event: ${thread!.lastEvent != null ? thread!.lastEvent!.eventId : 'None'}',
|
? Alignment.bottomRight
|
||||||
),
|
: Alignment.bottomLeft,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: InkWell(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.chat_bubble_outline,
|
||||||
|
color: Colors.grey[200],
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
thread!.lastEvent != null
|
||||||
|
? FutureBuilder<User?>(
|
||||||
|
future: thread!.lastEvent!
|
||||||
|
.fetchSenderUser(),
|
||||||
|
builder:
|
||||||
|
(context, snapshot) {
|
||||||
|
final user = snapshot
|
||||||
|
.data ??
|
||||||
|
event
|
||||||
|
.senderFromMemoryOrFallback;
|
||||||
|
|
||||||
|
return Avatar(
|
||||||
|
mxContent:
|
||||||
|
user.avatarUrl,
|
||||||
|
name: user
|
||||||
|
.calcDisplayname(),
|
||||||
|
size: 24,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
thread!.lastEvent != null
|
||||||
|
? Text(
|
||||||
|
thread!.lastEvent!.text)
|
||||||
|
: const Text('Thread'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () => context.go(
|
||||||
|
'/rooms/${event.roomId}/${event.eventId}',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,14 @@ import 'package:html_unescape/html_unescape.dart';
|
||||||
|
|
||||||
class SendFileDialog extends StatefulWidget {
|
class SendFileDialog extends StatefulWidget {
|
||||||
final Room room;
|
final Room room;
|
||||||
|
final Thread? thread;
|
||||||
final List<XFile> files;
|
final List<XFile> files;
|
||||||
final BuildContext outerContext;
|
final BuildContext outerContext;
|
||||||
final Event? replyEvent;
|
final Event? replyEvent;
|
||||||
|
|
||||||
const SendFileDialog({
|
const SendFileDialog({
|
||||||
required this.room,
|
required this.room,
|
||||||
|
required this.thread,
|
||||||
required this.files,
|
required this.files,
|
||||||
required this.outerContext,
|
required this.outerContext,
|
||||||
this.replyEvent,
|
this.replyEvent,
|
||||||
|
|
@ -151,6 +153,8 @@ class SendFileDialogState extends State<SendFileDialog> {
|
||||||
thumbnail: thumbnail,
|
thumbnail: thumbnail,
|
||||||
shrinkImageMaxDimension: compress ? 1600 : null,
|
shrinkImageMaxDimension: compress ? 1600 : null,
|
||||||
extraContent: extraContent,
|
extraContent: extraContent,
|
||||||
|
threadLastEventId: widget.thread?.lastEvent?.eventId ?? widget.thread?.rootEvent.eventId,
|
||||||
|
threadRootEventId: widget.thread?.rootEvent.eventId
|
||||||
);
|
);
|
||||||
} on MatrixException catch (e) {
|
} on MatrixException catch (e) {
|
||||||
final retryAfterMs = e.retryAfterMs;
|
final retryAfterMs = e.retryAfterMs;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:extera_next/generated/l10n/l10n.dart';
|
import 'package:extera_next/generated/l10n/l10n.dart';
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
@ -8,9 +7,11 @@ class SendPollDialog extends StatefulWidget {
|
||||||
final Room room;
|
final Room room;
|
||||||
final BuildContext outerContext;
|
final BuildContext outerContext;
|
||||||
final Event? replyEvent;
|
final Event? replyEvent;
|
||||||
|
final Thread? thread;
|
||||||
|
|
||||||
const SendPollDialog({
|
const SendPollDialog({
|
||||||
required this.room,
|
required this.room,
|
||||||
|
required this.thread,
|
||||||
required this.outerContext,
|
required this.outerContext,
|
||||||
this.replyEvent,
|
this.replyEvent,
|
||||||
super.key,
|
super.key,
|
||||||
|
|
@ -74,11 +75,13 @@ class SendPollDialogState extends State<SendPollDialog> {
|
||||||
'm.text': question,
|
'm.text': question,
|
||||||
},
|
},
|
||||||
'answers': answers
|
'answers': answers
|
||||||
.map((answer) => {
|
.map(
|
||||||
'id': const Uuid().v4(),
|
(answer) => {
|
||||||
'org.matrix.msc1767.text': answer,
|
'id': const Uuid().v4(),
|
||||||
'm.text': answer,
|
'org.matrix.msc1767.text': answer,
|
||||||
})
|
'm.text': answer,
|
||||||
|
},
|
||||||
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
'max_selections': _maxSelections,
|
'max_selections': _maxSelections,
|
||||||
'kind': _kind,
|
'kind': _kind,
|
||||||
|
|
@ -86,7 +89,13 @@ class SendPollDialogState extends State<SendPollDialog> {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await widget.room.sendEvent(pollContent, type: 'org.matrix.msc3381.poll.start');
|
await widget.room.sendEvent(
|
||||||
|
pollContent,
|
||||||
|
type: 'org.matrix.msc3381.poll.start',
|
||||||
|
threadLastEventId: widget.thread?.lastEvent?.eventId ??
|
||||||
|
widget.thread?.rootEvent.eventId,
|
||||||
|
threadRootEventId: widget.thread?.rootEvent.eventId,
|
||||||
|
);
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -154,7 +163,7 @@ class SendPollDialogState extends State<SendPollDialog> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
DropdownButtonFormField<int>(
|
DropdownButtonFormField<int>(
|
||||||
value: _maxSelections,
|
initialValue: _maxSelections,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: L10n.of(context).maxSelections,
|
labelText: L10n.of(context).maxSelections,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
|
|
@ -170,7 +179,7 @@ class SendPollDialogState extends State<SendPollDialog> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
DropdownButtonFormField<String>(
|
DropdownButtonFormField<String>(
|
||||||
value: _kind,
|
initialValue: _kind,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: L10n.of(context).pollType,
|
labelText: L10n.of(context).pollType,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
|
|
@ -202,4 +211,4 @@ class SendPollDialogState extends State<SendPollDialog> {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import 'package:extera_next/generated/l10n/l10n.dart';
|
||||||
|
import 'package:extera_next/pages/chat/chat.dart';
|
||||||
|
import 'package:extera_next/widgets/matrix.dart';
|
||||||
|
import 'package:extera_next/widgets/share_scaffold_dialog.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ThreadPage extends StatelessWidget {
|
||||||
|
final String roomId;
|
||||||
|
final List<ShareItem>? shareItems;
|
||||||
|
final String? threadRootEventId;
|
||||||
|
final String? eventId;
|
||||||
|
|
||||||
|
const ThreadPage({
|
||||||
|
super.key,
|
||||||
|
required this.roomId,
|
||||||
|
required this.threadRootEventId,
|
||||||
|
this.eventId,
|
||||||
|
this.shareItems,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final client = Matrix.of(context).client;
|
||||||
|
final room = 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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final thread = room.threads[threadRootEventId];
|
||||||
|
|
||||||
|
return ChatPageWithRoom(
|
||||||
|
key: Key('chat_page_${roomId}_${threadRootEventId}_$eventId'),
|
||||||
|
room: room,
|
||||||
|
thread: thread,
|
||||||
|
shareItems: shareItems,
|
||||||
|
eventId: eventId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2171,7 +2171,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.4"
|
version: "3.1.4"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: uuid
|
name: uuid
|
||||||
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,7 @@ dependencies:
|
||||||
wakelock_plus: ^1.2.2
|
wakelock_plus: ^1.2.2
|
||||||
webrtc_interface: ^1.0.13
|
webrtc_interface: ^1.0.13
|
||||||
dio: ^5.9.0
|
dio: ^5.9.0
|
||||||
|
uuid: ^4.5.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_lints: ^3.0.0
|
flutter_lints: ^3.0.0
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue