Merge pull request #2311 from krille-chan/krille/implement-threads
feat: Implement threads
This commit is contained in:
commit
01636cae8f
7 changed files with 166 additions and 21 deletions
|
|
@ -3431,7 +3431,7 @@
|
||||||
"addAnswerOption": "Add answer option",
|
"addAnswerOption": "Add answer option",
|
||||||
"allowMultipleAnswers": "Allow multiple answers",
|
"allowMultipleAnswers": "Allow multiple answers",
|
||||||
"pollHasBeenEnded": "Poll has been ended",
|
"pollHasBeenEnded": "Poll has been ended",
|
||||||
"countVotes": "{count} votes",
|
"countVotes": "{count, plural, =1{One vote} other{{count} votes}}",
|
||||||
"@countVotes": {
|
"@countVotes": {
|
||||||
"type": "int",
|
"type": "int",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
|
|
@ -3440,5 +3440,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"answersWillBeVisibleWhenPollHasEnded": "Answers will be visible when poll has ended"
|
"answersWillBeVisibleWhenPollHasEnded": "Answers will be visible when poll has ended",
|
||||||
|
"replyInThread": "Reply in thread",
|
||||||
|
"countReplies": "{count, plural, =1{One reply} other{{count} replies}}",
|
||||||
|
"@countReplies": {
|
||||||
|
"type": "int",
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"thread": "Thread",
|
||||||
|
"backToMainChat": "Back to main chat"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,8 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
|
|
||||||
Timeline? timeline;
|
Timeline? timeline;
|
||||||
|
|
||||||
|
String? activeThreadId;
|
||||||
|
|
||||||
late final String readMarkerEventId;
|
late final String readMarkerEventId;
|
||||||
|
|
||||||
String get roomId => widget.room.id;
|
String get roomId => widget.room.id;
|
||||||
|
|
@ -130,6 +132,8 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
files: details.files,
|
files: details.files,
|
||||||
room: room,
|
room: room,
|
||||||
outerContext: context,
|
outerContext: context,
|
||||||
|
threadRootEventId: activeThreadId,
|
||||||
|
threadLastEventId: threadLastEventId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -167,6 +171,25 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
|
|
||||||
bool showEmojiPicker = false;
|
bool showEmojiPicker = false;
|
||||||
|
|
||||||
|
String? get threadLastEventId {
|
||||||
|
final threadId = activeThreadId;
|
||||||
|
if (threadId == null) return null;
|
||||||
|
return timeline?.events
|
||||||
|
.filterByVisibleInGui(threadId: threadId)
|
||||||
|
.firstOrNull
|
||||||
|
?.eventId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void enterThread(String eventId) => setState(() {
|
||||||
|
activeThreadId = eventId;
|
||||||
|
selectedEvents.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
void closeThread() => setState(() {
|
||||||
|
activeThreadId = null;
|
||||||
|
selectedEvents.clear();
|
||||||
|
});
|
||||||
|
|
||||||
void recreateChat() async {
|
void recreateChat() async {
|
||||||
final room = this.room;
|
final room = this.room;
|
||||||
final userId = room.directChatMatrixID;
|
final userId = room.directChatMatrixID;
|
||||||
|
|
@ -267,6 +290,8 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
files: files,
|
files: files,
|
||||||
room: room,
|
room: room,
|
||||||
outerContext: context,
|
outerContext: context,
|
||||||
|
threadRootEventId: activeThreadId,
|
||||||
|
threadLastEventId: threadLastEventId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -340,7 +365,9 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
final Set<String> expandedEventIds = {};
|
final Set<String> expandedEventIds = {};
|
||||||
|
|
||||||
void expandEventsFrom(Event event, bool expand) {
|
void expandEventsFrom(Event event, bool expand) {
|
||||||
final events = timeline!.events.filterByVisibleInGui();
|
final events = timeline!.events.filterByVisibleInGui(
|
||||||
|
threadId: activeThreadId,
|
||||||
|
);
|
||||||
final start = events.indexOf(event);
|
final start = events.indexOf(event);
|
||||||
setState(() {
|
setState(() {
|
||||||
for (var i = start; i < events.length; i++) {
|
for (var i = start; i < events.length; i++) {
|
||||||
|
|
@ -367,6 +394,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
: timeline!.events
|
: timeline!.events
|
||||||
.filterByVisibleInGui(
|
.filterByVisibleInGui(
|
||||||
exceptionEventId: readMarkerEventId,
|
exceptionEventId: readMarkerEventId,
|
||||||
|
threadId: activeThreadId,
|
||||||
)
|
)
|
||||||
.indexWhere((e) => e.eventId == readMarkerEventId);
|
.indexWhere((e) => e.eventId == readMarkerEventId);
|
||||||
|
|
||||||
|
|
@ -377,6 +405,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
readMarkerEventIndex = timeline!.events
|
readMarkerEventIndex = timeline!.events
|
||||||
.filterByVisibleInGui(
|
.filterByVisibleInGui(
|
||||||
exceptionEventId: readMarkerEventId,
|
exceptionEventId: readMarkerEventId,
|
||||||
|
threadId: activeThreadId,
|
||||||
)
|
)
|
||||||
.indexWhere((e) => e.eventId == readMarkerEventId);
|
.indexWhere((e) => e.eventId == readMarkerEventId);
|
||||||
}
|
}
|
||||||
|
|
@ -571,6 +600,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
inReplyTo: replyEvent,
|
inReplyTo: replyEvent,
|
||||||
editEventId: editEvent?.eventId,
|
editEventId: editEvent?.eventId,
|
||||||
parseCommands: parseCommands,
|
parseCommands: parseCommands,
|
||||||
|
threadRootEventId: activeThreadId,
|
||||||
);
|
);
|
||||||
sendController.value = TextEditingValue(
|
sendController.value = TextEditingValue(
|
||||||
text: pendingText,
|
text: pendingText,
|
||||||
|
|
@ -599,6 +629,8 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
files: files,
|
files: files,
|
||||||
room: room,
|
room: room,
|
||||||
outerContext: context,
|
outerContext: context,
|
||||||
|
threadRootEventId: activeThreadId,
|
||||||
|
threadLastEventId: threadLastEventId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -611,6 +643,8 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
files: [XFile.fromData(image)],
|
files: [XFile.fromData(image)],
|
||||||
room: room,
|
room: room,
|
||||||
outerContext: context,
|
outerContext: context,
|
||||||
|
threadRootEventId: activeThreadId,
|
||||||
|
threadLastEventId: threadLastEventId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -627,6 +661,8 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
files: [file],
|
files: [file],
|
||||||
room: room,
|
room: room,
|
||||||
outerContext: context,
|
outerContext: context,
|
||||||
|
threadRootEventId: activeThreadId,
|
||||||
|
threadLastEventId: threadLastEventId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -646,6 +682,8 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
files: [file],
|
files: [file],
|
||||||
room: room,
|
room: room,
|
||||||
outerContext: context,
|
outerContext: context,
|
||||||
|
threadRootEventId: activeThreadId,
|
||||||
|
threadLastEventId: threadLastEventId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -677,6 +715,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
room.sendFileEvent(
|
room.sendFileEvent(
|
||||||
file,
|
file,
|
||||||
inReplyTo: replyEvent,
|
inReplyTo: replyEvent,
|
||||||
|
threadRootEventId: activeThreadId,
|
||||||
extraContent: {
|
extraContent: {
|
||||||
'info': {
|
'info': {
|
||||||
...file.info,
|
...file.info,
|
||||||
|
|
@ -966,6 +1005,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
: timeline!.events
|
: timeline!.events
|
||||||
.filterByVisibleInGui(
|
.filterByVisibleInGui(
|
||||||
exceptionEventId: eventId,
|
exceptionEventId: eventId,
|
||||||
|
threadId: activeThreadId,
|
||||||
)
|
)
|
||||||
.indexOf(foundEvent);
|
.indexOf(foundEvent);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,9 @@ 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();
|
final events = timeline.events.filterByVisibleInGui(
|
||||||
|
threadId: controller.activeThreadId,
|
||||||
|
);
|
||||||
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
|
||||||
|
|
@ -95,7 +97,8 @@ class ChatEventList extends StatelessWidget {
|
||||||
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (timeline.canRequestHistory) {
|
if (timeline.canRequestHistory &&
|
||||||
|
controller.activeThreadId == null) {
|
||||||
return Builder(
|
return Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
WidgetsBinding.instance
|
WidgetsBinding.instance
|
||||||
|
|
@ -165,6 +168,9 @@ class ChatEventList extends StatelessWidget {
|
||||||
scrollController: controller.scrollController,
|
scrollController: controller.scrollController,
|
||||||
colors: colors,
|
colors: colors,
|
||||||
isCollapsed: isCollapsed,
|
isCollapsed: isCollapsed,
|
||||||
|
enterThread: controller.activeThreadId == null
|
||||||
|
? controller.enterThread
|
||||||
|
: null,
|
||||||
onExpand: canExpand
|
onExpand: canExpand
|
||||||
? () => controller.expandEventsFrom(
|
? () => controller.expandEventsFrom(
|
||||||
event,
|
event,
|
||||||
|
|
|
||||||
|
|
@ -43,11 +43,15 @@ class ChatView extends StatelessWidget {
|
||||||
tooltip: L10n.of(context).edit,
|
tooltip: L10n.of(context).edit,
|
||||||
onPressed: controller.editSelectedEventAction,
|
onPressed: controller.editSelectedEventAction,
|
||||||
),
|
),
|
||||||
IconButton(
|
if (controller.selectedEvents.length == 1 &&
|
||||||
icon: const Icon(Icons.copy_outlined),
|
controller.activeThreadId == null &&
|
||||||
tooltip: L10n.of(context).copy,
|
controller.room.canSendDefaultMessages)
|
||||||
onPressed: controller.copyEventsAction,
|
IconButton(
|
||||||
),
|
icon: const Icon(Icons.message_outlined),
|
||||||
|
tooltip: L10n.of(context).replyInThread,
|
||||||
|
onPressed: () => controller
|
||||||
|
.enterThread(controller.selectedEvents.single.eventId),
|
||||||
|
),
|
||||||
if (controller.canPinSelectedEvents)
|
if (controller.canPinSelectedEvents)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.push_pin_outlined),
|
icon: const Icon(Icons.push_pin_outlined),
|
||||||
|
|
@ -75,6 +79,18 @@ class ChatView extends StatelessWidget {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
itemBuilder: (context) => [
|
itemBuilder: (context) => [
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: controller.copyEventsAction,
|
||||||
|
value: null,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.copy_outlined),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(L10n.of(context).copy),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
if (controller.canSaveSelectedEvent)
|
if (controller.canSaveSelectedEvent)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: () => controller.saveSelectedEvent(context),
|
onTap: () => controller.saveSelectedEvent(context),
|
||||||
|
|
@ -157,6 +173,8 @@ class ChatView extends StatelessWidget {
|
||||||
controller.clearSelectedEvents();
|
controller.clearSelectedEvents();
|
||||||
} else if (controller.showEmojiPicker) {
|
} else if (controller.showEmojiPicker) {
|
||||||
controller.emojiPickerAction();
|
controller.emojiPickerAction();
|
||||||
|
} else if (controller.activeThreadId != null) {
|
||||||
|
controller.closeThread();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: StreamBuilder(
|
child: StreamBuilder(
|
||||||
|
|
@ -167,6 +185,10 @@ class ChatView extends StatelessWidget {
|
||||||
future: controller.loadTimelineFuture,
|
future: controller.loadTimelineFuture,
|
||||||
builder: (BuildContext context, snapshot) {
|
builder: (BuildContext context, snapshot) {
|
||||||
var appbarBottomHeight = 0.0;
|
var appbarBottomHeight = 0.0;
|
||||||
|
final activeThreadId = controller.activeThreadId;
|
||||||
|
if (activeThreadId != null) {
|
||||||
|
appbarBottomHeight += ChatAppBarListTile.fixedHeight;
|
||||||
|
}
|
||||||
if (controller.room.pinnedEventIds.isNotEmpty) {
|
if (controller.room.pinnedEventIds.isNotEmpty) {
|
||||||
appbarBottomHeight += ChatAppBarListTile.fixedHeight;
|
appbarBottomHeight += ChatAppBarListTile.fixedHeight;
|
||||||
}
|
}
|
||||||
|
|
@ -181,7 +203,9 @@ class ChatView extends StatelessWidget {
|
||||||
: theme.colorScheme.onTertiaryContainer,
|
: theme.colorScheme.onTertiaryContainer,
|
||||||
),
|
),
|
||||||
backgroundColor: controller.selectedEvents.isEmpty
|
backgroundColor: controller.selectedEvents.isEmpty
|
||||||
? null
|
? controller.activeThreadId != null
|
||||||
|
? theme.colorScheme.secondaryContainer
|
||||||
|
: null
|
||||||
: theme.colorScheme.tertiaryContainer,
|
: theme.colorScheme.tertiaryContainer,
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
leading: controller.selectMode
|
leading: controller.selectMode
|
||||||
|
|
@ -213,6 +237,17 @@ class ChatView extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
if (activeThreadId != null)
|
||||||
|
SizedBox(
|
||||||
|
height: ChatAppBarListTile.fixedHeight,
|
||||||
|
child: Center(
|
||||||
|
child: TextButton.icon(
|
||||||
|
onPressed: controller.closeThread,
|
||||||
|
label: Text(L10n.of(context).backToMainChat),
|
||||||
|
icon: const Icon(Icons.message),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
PinnedEvents(controller),
|
PinnedEvents(controller),
|
||||||
if (scrollUpBannerEventId != null)
|
if (scrollUpBannerEventId != null)
|
||||||
ChatAppBarListTile(
|
ChatAppBarListTile(
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import 'package:fluffychat/pages/chat/events/room_creation_state_event.dart';
|
||||||
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
|
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
|
||||||
import 'package:fluffychat/utils/date_time_extension.dart';
|
import 'package:fluffychat/utils/date_time_extension.dart';
|
||||||
import 'package:fluffychat/utils/file_description.dart';
|
import 'package:fluffychat/utils/file_description.dart';
|
||||||
|
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||||
import 'package:fluffychat/utils/string_color.dart';
|
import 'package:fluffychat/utils/string_color.dart';
|
||||||
import 'package:fluffychat/widgets/avatar.dart';
|
import 'package:fluffychat/widgets/avatar.dart';
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
import 'package:fluffychat/widgets/matrix.dart';
|
||||||
|
|
@ -35,6 +36,7 @@ class Message extends StatelessWidget {
|
||||||
final void Function() onSwipe;
|
final void Function() onSwipe;
|
||||||
final void Function() onMention;
|
final void Function() onMention;
|
||||||
final void Function() onEdit;
|
final void Function() onEdit;
|
||||||
|
final void Function(String eventId)? enterThread;
|
||||||
final bool longPressSelect;
|
final bool longPressSelect;
|
||||||
final bool selected;
|
final bool selected;
|
||||||
final bool singleSelected;
|
final bool singleSelected;
|
||||||
|
|
@ -70,6 +72,7 @@ class Message extends StatelessWidget {
|
||||||
required this.scrollController,
|
required this.scrollController,
|
||||||
required this.colors,
|
required this.colors,
|
||||||
this.onExpand,
|
this.onExpand,
|
||||||
|
required this.enterThread,
|
||||||
this.isCollapsed = false,
|
this.isCollapsed = false,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
@ -194,9 +197,14 @@ class Message extends StatelessWidget {
|
||||||
final showReceiptsRow =
|
final showReceiptsRow =
|
||||||
event.hasAggregatedEvents(timeline, RelationshipTypes.reaction);
|
event.hasAggregatedEvents(timeline, RelationshipTypes.reaction);
|
||||||
|
|
||||||
|
final threadChildren =
|
||||||
|
event.aggregatedEvents(timeline, RelationshipTypes.thread);
|
||||||
|
|
||||||
final showReactionPicker =
|
final showReactionPicker =
|
||||||
singleSelected && event.room.canSendDefaultMessages;
|
singleSelected && event.room.canSendDefaultMessages;
|
||||||
|
|
||||||
|
final enterThread = this.enterThread;
|
||||||
|
|
||||||
return Center(
|
return Center(
|
||||||
child: Swipeable(
|
child: Swipeable(
|
||||||
key: ValueKey(event.eventId),
|
key: ValueKey(event.eventId),
|
||||||
|
|
@ -490,15 +498,10 @@ class Message extends StatelessWidget {
|
||||||
CrossAxisAlignment
|
CrossAxisAlignment
|
||||||
.start,
|
.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
if ({
|
if (RelationshipTypes
|
||||||
RelationshipTypes
|
.reply ==
|
||||||
.reply,
|
event
|
||||||
RelationshipTypes
|
.relationshipType)
|
||||||
.thread,
|
|
||||||
}.contains(
|
|
||||||
event
|
|
||||||
.relationshipType,
|
|
||||||
))
|
|
||||||
FutureBuilder<Event?>(
|
FutureBuilder<Event?>(
|
||||||
future: event
|
future: event
|
||||||
.getReplyEvent(
|
.getReplyEvent(
|
||||||
|
|
@ -867,6 +870,38 @@ class Message extends StatelessWidget {
|
||||||
child: MessageReactions(event, timeline),
|
child: MessageReactions(event, timeline),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (enterThread != null)
|
||||||
|
AnimatedSize(
|
||||||
|
duration: FluffyThemes.animationDuration,
|
||||||
|
curve: FluffyThemes.animationCurve,
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: threadChildren.isEmpty
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 2.0, bottom: 4.0),
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 400,
|
||||||
|
),
|
||||||
|
child: TextButton.icon(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
backgroundColor:
|
||||||
|
theme.colorScheme.surfaceContainerHighest,
|
||||||
|
),
|
||||||
|
onPressed: () => enterThread(event.eventId),
|
||||||
|
icon: const Icon(Icons.message),
|
||||||
|
label: Text(
|
||||||
|
'${L10n.of(context).countReplies(threadChildren.length)} | ${threadChildren.last.calcLocalizedBodyFallback(
|
||||||
|
MatrixLocals(L10n.of(context)),
|
||||||
|
withSenderNamePrefix: true,
|
||||||
|
)}',
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
if (displayReadMarker)
|
if (displayReadMarker)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,14 @@ class SendFileDialog extends StatefulWidget {
|
||||||
final Room room;
|
final Room room;
|
||||||
final List<XFile> files;
|
final List<XFile> files;
|
||||||
final BuildContext outerContext;
|
final BuildContext outerContext;
|
||||||
|
final String? threadLastEventId, threadRootEventId;
|
||||||
|
|
||||||
const SendFileDialog({
|
const SendFileDialog({
|
||||||
required this.room,
|
required this.room,
|
||||||
required this.files,
|
required this.files,
|
||||||
required this.outerContext,
|
required this.outerContext,
|
||||||
|
required this.threadLastEventId,
|
||||||
|
required this.threadRootEventId,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -108,6 +111,8 @@ class SendFileDialogState extends State<SendFileDialog> {
|
||||||
thumbnail: thumbnail,
|
thumbnail: thumbnail,
|
||||||
shrinkImageMaxDimension: compress ? 1600 : null,
|
shrinkImageMaxDimension: compress ? 1600 : null,
|
||||||
extraContent: label.isEmpty ? null : {'body': label},
|
extraContent: label.isEmpty ? null : {'body': label},
|
||||||
|
threadRootEventId: widget.threadRootEventId,
|
||||||
|
threadLastEventId: widget.threadLastEventId,
|
||||||
);
|
);
|
||||||
} on MatrixException catch (e) {
|
} on MatrixException catch (e) {
|
||||||
final retryAfterMs = e.retryAfterMs;
|
final retryAfterMs = e.retryAfterMs;
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,21 @@ import 'package:fluffychat/config/setting_keys.dart';
|
||||||
extension VisibleInGuiExtension on List<Event> {
|
extension VisibleInGuiExtension on List<Event> {
|
||||||
List<Event> filterByVisibleInGui({
|
List<Event> filterByVisibleInGui({
|
||||||
String? exceptionEventId,
|
String? exceptionEventId,
|
||||||
|
String? threadId,
|
||||||
}) =>
|
}) =>
|
||||||
where(
|
where(
|
||||||
(event) => event.isVisibleInGui || event.eventId == exceptionEventId,
|
(event) {
|
||||||
|
if (threadId != null) {
|
||||||
|
if ((event.relationshipType != RelationshipTypes.thread ||
|
||||||
|
event.relationshipEventId != threadId) &&
|
||||||
|
event.eventId != threadId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (event.relationshipType == RelationshipTypes.thread) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return event.isVisibleInGui || event.eventId == exceptionEventId;
|
||||||
|
},
|
||||||
).toList();
|
).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue