Merge branch 'krille/new-state-collapsing' into 'main'

change: Remove previous collapse solution

See merge request famedly/fluffychat!390
This commit is contained in:
Krille Fear 2021-02-25 10:24:52 +00:00
commit 73cff455c0
6 changed files with 124 additions and 65 deletions

View File

@ -16,9 +16,10 @@ import 'state_message.dart';
class Message extends StatelessWidget { class Message extends StatelessWidget {
final Event event; final Event event;
final Event nextEvent; final Event nextEvent;
final Function(Event) onSelect; final void Function(Event) onSelect;
final Function(Event) onAvatarTab; final void Function(Event) onAvatarTab;
final Function(String) scrollToEventId; final void Function(String) scrollToEventId;
final void Function(String) unfold;
final bool longPressSelect; final bool longPressSelect;
final bool selected; final bool selected;
final Timeline timeline; final Timeline timeline;
@ -29,6 +30,7 @@ class Message extends StatelessWidget {
this.onSelect, this.onSelect,
this.onAvatarTab, this.onAvatarTab,
this.scrollToEventId, this.scrollToEventId,
@required this.unfold,
this.selected, this.selected,
this.timeline}); this.timeline});
@ -38,15 +40,9 @@ class Message extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (event.type == EventTypes.RoomCreate) {
return InkWell(
onTap: () => onSelect(event),
child: StateMessage(event),
);
}
if (![EventTypes.Message, EventTypes.Sticker, EventTypes.Encrypted] if (![EventTypes.Message, EventTypes.Sticker, EventTypes.Encrypted]
.contains(event.type)) { .contains(event.type)) {
return StateMessage(event); return StateMessage(event, unfold: unfold);
} }
var client = Matrix.of(context).client; var client = Matrix.of(context).client;

View File

@ -7,11 +7,19 @@ import '../../app_config.dart';
class StateMessage extends StatelessWidget { class StateMessage extends StatelessWidget {
final Event event; final Event event;
const StateMessage(this.event); final void Function(String) unfold;
const StateMessage(this.event, {@required this.unfold});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( if (event.unsigned['im.fluffychat.collapsed_state_event'] == true) {
return Container();
}
final int counter =
event.unsigned['im.fluffychat.collapsed_state_event_count'] ?? 0;
return InkWell(
onTap: counter != 0 ? () => unfold(event.eventId) : null,
child: Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: 8.0, left: 8.0,
right: 8.0, right: 8.0,
@ -24,14 +32,30 @@ class StateMessage extends StatelessWidget {
color: Theme.of(context).secondaryHeaderColor, color: Theme.of(context).secondaryHeaderColor,
borderRadius: BorderRadius.circular(7), borderRadius: BorderRadius.circular(7),
), ),
child: Text( child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
event.getLocalizedBody(MatrixLocals(L10n.of(context))), event.getLocalizedBody(MatrixLocals(L10n.of(context))),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: Theme.of(context).textTheme.bodyText1.fontSize * fontSize: Theme.of(context).textTheme.bodyText1.fontSize *
AppConfig.fontSizeFactor, AppConfig.fontSizeFactor,
color: Theme.of(context).textTheme.bodyText2.color, color: Theme.of(context).textTheme.bodyText2.color,
decoration: event.redacted ? TextDecoration.lineThrough : null, decoration:
event.redacted ? TextDecoration.lineThrough : null,
),
),
if (counter != 0)
Text(
counter == 1
? L10n.of(context).oneMoreEvent
: L10n.of(context).xMoreEvents(counter.toString()),
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
],
), ),
), ),
), ),

View File

@ -513,6 +513,18 @@
"type": "text", "type": "text",
"placeholders": {} "placeholders": {}
}, },
"oneMoreEvent": "1 more event",
"@oneMoreEvent": {
"type": "text",
"placeholders": {}
},
"xMoreEvents": "{count} more events",
"@xMoreEvents": {
"type": "text",
"placeholders": {
"count": {}
}
},
"createdTheChat": "{username} created the chat", "createdTheChat": "{username} created the chat",
"@createdTheChat": { "@createdTheChat": {
"type": "text", "type": "text",

View File

@ -3,7 +3,7 @@ import 'package:famedlysdk/famedlysdk.dart';
import '../app_config.dart'; import '../app_config.dart';
extension FilteredTimelineExtension on Timeline { extension FilteredTimelineExtension on Timeline {
List<Event> getFilteredEvents({bool collapseRoomCreate = true}) { List<Event> getFilteredEvents({Set<String> unfolded = const {}}) {
final filteredEvents = events final filteredEvents = events
.where((e) => .where((e) =>
// always filter out edit and reaction relationships // always filter out edit and reaction relationships
@ -19,23 +19,35 @@ extension FilteredTimelineExtension on Timeline {
// if we enabled to hide all unknown events, don't show those // if we enabled to hide all unknown events, don't show those
(!AppConfig.hideUnknownEvents || e.isEventTypeKnown) && (!AppConfig.hideUnknownEvents || e.isEventTypeKnown) &&
// remove state events that we don't want to render // remove state events that we don't want to render
(!{EventTypes.Message, EventTypes.Sticker, EventTypes.Encrypted} (e.isState || !AppConfig.hideAllStateEvents))
.contains(e.type) ||
!AppConfig.hideAllStateEvents))
.toList(); .toList();
// Hide state events from the room creater right after the room created event // Fold state events
if (collapseRoomCreate && var counter = 0;
filteredEvents[filteredEvents.length - 1].type == for (var i = filteredEvents.length - 1; i >= 0; i--) {
EventTypes.RoomCreate) { if (!filteredEvents[i].isState) continue;
while (filteredEvents.length >= 3 && if (i > 0 &&
filteredEvents[filteredEvents.length - 2].senderId == filteredEvents[i - 1].isState &&
filteredEvents[filteredEvents.length - 1].senderId && !unfolded.contains(filteredEvents[i - 1].eventId)) {
![EventTypes.Message, EventTypes.Sticker, EventTypes.Encrypted] counter++;
.contains(filteredEvents[filteredEvents.length - 2].type)) { filteredEvents[i].unsigned['im.fluffychat.collapsed_state_event'] =
filteredEvents.removeAt(filteredEvents.length - 2); true;
} else {
filteredEvents[i].unsigned['im.fluffychat.collapsed_state_event'] =
false;
filteredEvents[i]
.unsigned['im.fluffychat.collapsed_state_event_count'] = counter;
counter = 0;
} }
} }
return filteredEvents; return filteredEvents;
} }
} }
extension IsStateExtension on Event {
bool get isState => !{
EventTypes.Message,
EventTypes.Sticker,
EventTypes.Encrypted
}.contains(type);
}

View File

@ -63,11 +63,14 @@ extension RoomStatusExtension on Room {
} }
String getLocalizedSeenByText( String getLocalizedSeenByText(
BuildContext context, Timeline timeline, List<Event> filteredEvents) { BuildContext context,
Timeline timeline,
List<Event> filteredEvents,
Set<String> unfolded,
) {
var seenByText = ''; var seenByText = '';
if (timeline.events.isNotEmpty) { if (timeline.events.isNotEmpty) {
final filteredEvents = final filteredEvents = timeline.getFilteredEvents(unfolded: unfolded);
timeline.getFilteredEvents(collapseRoomCreate: false);
final lastReceipts = <User>{}; final lastReceipts = <User>{};
// now we iterate the timeline events until we hit the first rendered event // now we iterate the timeline events until we hit the first rendered event
for (final event in timeline.events) { for (final event in timeline.events) {

View File

@ -72,7 +72,7 @@ class _ChatState extends State<Chat> {
List<Event> filteredEvents; List<Event> filteredEvents;
bool _collapseRoomCreate = true; final Set<String> _unfolded = {};
Event replyEvent; Event replyEvent;
@ -146,12 +146,22 @@ class _ChatState extends State<Chat> {
if (!mounted) return; if (!mounted) return;
setState( setState(
() { () {
filteredEvents = filteredEvents = timeline.getFilteredEvents(unfolded: _unfolded);
timeline.getFilteredEvents(collapseRoomCreate: _collapseRoomCreate);
}, },
); );
} }
void _unfold(String eventId) {
var i = filteredEvents.indexWhere((e) => e.eventId == eventId);
setState(() {
while (i < filteredEvents.length - 1 && filteredEvents[i].isState) {
_unfolded.add(filteredEvents[i].eventId);
i++;
}
filteredEvents = timeline.getFilteredEvents(unfolded: _unfolded);
});
}
Future<bool> getTimeline(BuildContext context) async { Future<bool> getTimeline(BuildContext context) async {
if (timeline == null) { if (timeline == null) {
timeline = await room.getTimeline(onUpdate: updateView); timeline = await room.getTimeline(onUpdate: updateView);
@ -712,9 +722,7 @@ class _ChatState extends State<Chat> {
thisEventsKeyMap[filteredEvents[i].eventId] = i; thisEventsKeyMap[filteredEvents[i].eventId] = i;
} }
return ListView.custom( final horizontalPadding = max(
padding: EdgeInsets.symmetric(
horizontal: max(
0, 0,
(MediaQuery.of(context).size.width - (MediaQuery.of(context).size.width -
FluffyThemes.columnWidth * FluffyThemes.columnWidth *
@ -724,7 +732,14 @@ class _ChatState extends State<Chat> {
null null
? 4.5 ? 4.5
: 3.5)) / : 3.5)) /
2), 2)
.toDouble();
return ListView.custom(
padding: EdgeInsets.only(
top: 16,
left: horizontalPadding,
right: horizontalPadding,
), ),
reverse: true, reverse: true,
controller: _scrollController, controller: _scrollController,
@ -761,7 +776,9 @@ class _ChatState extends State<Chat> {
room.getLocalizedSeenByText( room.getLocalizedSeenByText(
context, context,
timeline, timeline,
filteredEvents); filteredEvents,
_unfolded,
);
return AnimatedContainer( return AnimatedContainer(
height: seenByText.isEmpty ? 0 : 24, height: seenByText.isEmpty ? 0 : 24,
duration: seenByText.isEmpty duration: seenByText.isEmpty
@ -830,13 +847,8 @@ class _ChatState extends State<Chat> {
'${event.senderId} ', '${event.senderId} ',
), ),
), ),
unfold: _unfold,
onSelect: (Event event) { onSelect: (Event event) {
if (event.type ==
EventTypes.RoomCreate) {
return setState(() =>
_collapseRoomCreate =
false);
}
if (!event.redacted) { if (!event.redacted) {
if (selectedEvents if (selectedEvents
.contains(event)) { .contains(event)) {