mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-11-04 14:27:23 +01:00 
			
		
		
		
	design: Hide unimportant state events instead of folding
This commit is contained in:
		
							parent
							
								
									b4bf23cd34
								
							
						
					
					
						commit
						c365469dc5
					
				@ -2949,5 +2949,6 @@
 | 
			
		||||
    "placeholders": {
 | 
			
		||||
      "number": {}
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  },
 | 
			
		||||
  "hideUnimportantStateEvents": "Hide unimportant state events"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -37,6 +37,7 @@ abstract class AppConfig {
 | 
			
		||||
  static bool renderHtml = true;
 | 
			
		||||
  static bool hideRedactedEvents = false;
 | 
			
		||||
  static bool hideUnknownEvents = true;
 | 
			
		||||
  static bool hideUnimportantStateEvents = true;
 | 
			
		||||
  static bool showDirectChatsInSpaces = true;
 | 
			
		||||
  static bool separateChatTypes = false;
 | 
			
		||||
  static bool autoplayImages = true;
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,8 @@ abstract class SettingKeys {
 | 
			
		||||
  static const String renderHtml = 'chat.fluffy.renderHtml';
 | 
			
		||||
  static const String hideRedactedEvents = 'chat.fluffy.hideRedactedEvents';
 | 
			
		||||
  static const String hideUnknownEvents = 'chat.fluffy.hideUnknownEvents';
 | 
			
		||||
  static const String hideUnimportantStateEvents =
 | 
			
		||||
      'chat.fluffy.hideUnimportantStateEvents';
 | 
			
		||||
  static const String showDirectChatsInSpaces =
 | 
			
		||||
      'chat.fluffy.showDirectChatsInSpaces';
 | 
			
		||||
  static const String separateChatTypes = 'chat.fluffy.separateChatTypes';
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,6 @@ import 'package:fluffychat/utils/platform_infos.dart';
 | 
			
		||||
import 'package:fluffychat/widgets/matrix.dart';
 | 
			
		||||
import '../../utils/account_bundles.dart';
 | 
			
		||||
import '../../utils/localized_exception_extension.dart';
 | 
			
		||||
import '../../utils/matrix_sdk_extensions.dart/filtered_timeline_extension.dart';
 | 
			
		||||
import '../../utils/matrix_sdk_extensions.dart/matrix_file_extension.dart';
 | 
			
		||||
import 'send_file_dialog.dart';
 | 
			
		||||
import 'send_location_dialog.dart';
 | 
			
		||||
@ -111,8 +110,6 @@ class ChatController extends State<Chat> {
 | 
			
		||||
 | 
			
		||||
  List<Event> selectedEvents = [];
 | 
			
		||||
 | 
			
		||||
  late List<Event> filteredEvents;
 | 
			
		||||
 | 
			
		||||
  final Set<String> unfolded = {};
 | 
			
		||||
 | 
			
		||||
  Event? replyEvent;
 | 
			
		||||
@ -184,22 +181,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
 | 
			
		||||
  void updateView() {
 | 
			
		||||
    if (!mounted) return;
 | 
			
		||||
    setState(
 | 
			
		||||
      () {
 | 
			
		||||
        filteredEvents = timeline!.getFilteredEvents(unfolded: unfolded);
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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);
 | 
			
		||||
    });
 | 
			
		||||
    setState(() {});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<bool> getTimeline() async {
 | 
			
		||||
@ -225,7 +207,6 @@ class ChatController extends State<Chat> {
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    filteredEvents = timeline!.getFilteredEvents(unfolded: unfolded);
 | 
			
		||||
    timeline!.requestKeys(onlineKeyBackupOnly: false);
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
@ -656,7 +637,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void scrollToEventId(String eventId) async {
 | 
			
		||||
    var eventIndex = filteredEvents.indexWhere((e) => e.eventId == eventId);
 | 
			
		||||
    var eventIndex = timeline!.events.indexWhere((e) => e.eventId == eventId);
 | 
			
		||||
    if (eventIndex == -1) {
 | 
			
		||||
      // event id not found...maybe we can fetch it?
 | 
			
		||||
      // the try...finally is here to start and close the loading dialog reliably
 | 
			
		||||
@ -693,7 +674,7 @@ class ChatController extends State<Chat> {
 | 
			
		||||
                rethrow;
 | 
			
		||||
              }
 | 
			
		||||
              eventIndex =
 | 
			
		||||
                  filteredEvents.indexWhere((e) => e.eventId == eventId);
 | 
			
		||||
                  timeline!.events.indexWhere((e) => e.eventId == eventId);
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ import 'package:fluffychat/pages/chat/events/message.dart';
 | 
			
		||||
import 'package:fluffychat/pages/chat/seen_by_row.dart';
 | 
			
		||||
import 'package:fluffychat/pages/chat/typing_indicators.dart';
 | 
			
		||||
import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart';
 | 
			
		||||
import 'package:fluffychat/utils/matrix_sdk_extensions.dart/filtered_timeline_extension.dart';
 | 
			
		||||
import 'package:fluffychat/utils/platform_infos.dart';
 | 
			
		||||
 | 
			
		||||
class ChatEventList extends StatelessWidget {
 | 
			
		||||
@ -26,9 +27,10 @@ class ChatEventList extends StatelessWidget {
 | 
			
		||||
    // create a map of eventId --> index to greatly improve performance of
 | 
			
		||||
    // ListView's findChildIndexCallback
 | 
			
		||||
    final thisEventsKeyMap = <String, int>{};
 | 
			
		||||
    for (var i = 0; i < controller.filteredEvents.length; i++) {
 | 
			
		||||
      thisEventsKeyMap[controller.filteredEvents[i].eventId] = i;
 | 
			
		||||
    for (var i = 0; i < controller.timeline!.events.length; i++) {
 | 
			
		||||
      thisEventsKeyMap[controller.timeline!.events[i].eventId] = i;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return ListView.custom(
 | 
			
		||||
      padding: EdgeInsets.only(
 | 
			
		||||
        top: 16,
 | 
			
		||||
@ -55,7 +57,7 @@ class ChatEventList extends StatelessWidget {
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // Request history button or progress indicator:
 | 
			
		||||
          if (i == controller.filteredEvents.length + 1) {
 | 
			
		||||
          if (i == controller.timeline!.events.length + 1) {
 | 
			
		||||
            if (controller.timeline!.isRequestingHistory) {
 | 
			
		||||
              return const Center(
 | 
			
		||||
                child: CircularProgressIndicator.adaptive(strokeWidth: 2),
 | 
			
		||||
@ -76,37 +78,40 @@ class ChatEventList extends StatelessWidget {
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // The message at this index:
 | 
			
		||||
          final event = controller.timeline!.events[i - 1];
 | 
			
		||||
 | 
			
		||||
          return AutoScrollTag(
 | 
			
		||||
            key: ValueKey(controller.filteredEvents[i - 1].eventId),
 | 
			
		||||
            key: ValueKey(event.eventId),
 | 
			
		||||
            index: i - 1,
 | 
			
		||||
            controller: controller.scrollController,
 | 
			
		||||
            child: Message(controller.filteredEvents[i - 1],
 | 
			
		||||
                onSwipe: (direction) => controller.replyAction(
 | 
			
		||||
                    replyTo: controller.filteredEvents[i - 1]),
 | 
			
		||||
                onInfoTab: controller.showEventInfo,
 | 
			
		||||
                onAvatarTab: (Event event) => showModalBottomSheet(
 | 
			
		||||
                      context: context,
 | 
			
		||||
                      builder: (c) => UserBottomSheet(
 | 
			
		||||
                        user: event.senderFromMemoryOrFallback,
 | 
			
		||||
                        outerContext: context,
 | 
			
		||||
                        onMention: () => controller.sendController.text +=
 | 
			
		||||
                            '${event.senderFromMemoryOrFallback.mention} ',
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                unfold: controller.unfold,
 | 
			
		||||
                onSelect: controller.onSelectMessage,
 | 
			
		||||
                scrollToEventId: (String eventId) =>
 | 
			
		||||
                    controller.scrollToEventId(eventId),
 | 
			
		||||
                longPressSelect: controller.selectedEvents.isEmpty,
 | 
			
		||||
                selected: controller.selectedEvents.any((e) =>
 | 
			
		||||
                    e.eventId == controller.filteredEvents[i - 1].eventId),
 | 
			
		||||
                timeline: controller.timeline!,
 | 
			
		||||
                nextEvent: i < controller.filteredEvents.length
 | 
			
		||||
                    ? controller.filteredEvents[i]
 | 
			
		||||
                    : null),
 | 
			
		||||
            child: event.isVisibleInGui
 | 
			
		||||
                ? Message(event,
 | 
			
		||||
                    onSwipe: (direction) =>
 | 
			
		||||
                        controller.replyAction(replyTo: event),
 | 
			
		||||
                    onInfoTab: controller.showEventInfo,
 | 
			
		||||
                    onAvatarTab: (Event event) => showModalBottomSheet(
 | 
			
		||||
                          context: context,
 | 
			
		||||
                          builder: (c) => UserBottomSheet(
 | 
			
		||||
                            user: event.senderFromMemoryOrFallback,
 | 
			
		||||
                            outerContext: context,
 | 
			
		||||
                            onMention: () => controller.sendController.text +=
 | 
			
		||||
                                '${event.senderFromMemoryOrFallback.mention} ',
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                    onSelect: controller.onSelectMessage,
 | 
			
		||||
                    scrollToEventId: (String eventId) =>
 | 
			
		||||
                        controller.scrollToEventId(eventId),
 | 
			
		||||
                    longPressSelect: controller.selectedEvents.isEmpty,
 | 
			
		||||
                    selected: controller.selectedEvents
 | 
			
		||||
                        .any((e) => e.eventId == event.eventId),
 | 
			
		||||
                    timeline: controller.timeline!,
 | 
			
		||||
                    nextEvent: i < controller.timeline!.events.length
 | 
			
		||||
                        ? controller.timeline!.events[i]
 | 
			
		||||
                        : null)
 | 
			
		||||
                : Container(),
 | 
			
		||||
          );
 | 
			
		||||
        },
 | 
			
		||||
        childCount: controller.filteredEvents.length + 2,
 | 
			
		||||
        childCount: controller.timeline!.events.length + 2,
 | 
			
		||||
        findChildIndexCallback: (key) =>
 | 
			
		||||
            controller.findChildIndexCallback(key, thisEventsKeyMap),
 | 
			
		||||
      ),
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,6 @@ class Message extends StatelessWidget {
 | 
			
		||||
  final void Function(Event)? onAvatarTab;
 | 
			
		||||
  final void Function(Event)? onInfoTab;
 | 
			
		||||
  final void Function(String)? scrollToEventId;
 | 
			
		||||
  final void Function(String) unfold;
 | 
			
		||||
  final void Function(SwipeDirection) onSwipe;
 | 
			
		||||
  final bool longPressSelect;
 | 
			
		||||
  final bool selected;
 | 
			
		||||
@ -36,7 +35,6 @@ class Message extends StatelessWidget {
 | 
			
		||||
      this.onAvatarTab,
 | 
			
		||||
      this.scrollToEventId,
 | 
			
		||||
      required this.onSwipe,
 | 
			
		||||
      required this.unfold,
 | 
			
		||||
      this.selected = false,
 | 
			
		||||
      required this.timeline,
 | 
			
		||||
      Key? key})
 | 
			
		||||
@ -57,7 +55,7 @@ class Message extends StatelessWidget {
 | 
			
		||||
      if (event.type.startsWith('m.call.')) {
 | 
			
		||||
        return Container();
 | 
			
		||||
      }
 | 
			
		||||
      return StateMessage(event, unfold: unfold);
 | 
			
		||||
      return StateMessage(event);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (event.type == EventTypes.Message &&
 | 
			
		||||
 | 
			
		||||
@ -8,9 +8,7 @@ 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})
 | 
			
		||||
      : super(key: key);
 | 
			
		||||
  const StateMessage(this.event, {Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -25,48 +23,43 @@ class StateMessage extends StatelessWidget {
 | 
			
		||||
        vertical: 4.0,
 | 
			
		||||
      ),
 | 
			
		||||
      child: Center(
 | 
			
		||||
        child: InkWell(
 | 
			
		||||
          onTap: counter != 0 ? () => unfold(event.eventId) : null,
 | 
			
		||||
          borderRadius: BorderRadius.circular(AppConfig.borderRadius),
 | 
			
		||||
          child: Container(
 | 
			
		||||
            padding: const EdgeInsets.all(8),
 | 
			
		||||
            decoration: BoxDecoration(
 | 
			
		||||
              color: Theme.of(context).brightness == Brightness.light
 | 
			
		||||
                  ? Colors.white
 | 
			
		||||
                  : Colors.grey.shade900,
 | 
			
		||||
              borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
 | 
			
		||||
            ),
 | 
			
		||||
            child: Column(
 | 
			
		||||
              mainAxisSize: MainAxisSize.min,
 | 
			
		||||
              children: [
 | 
			
		||||
                FutureBuilder<String>(
 | 
			
		||||
                    future: event
 | 
			
		||||
                        .calcLocalizedBody(MatrixLocals(L10n.of(context)!)),
 | 
			
		||||
                    builder: (context, snapshot) {
 | 
			
		||||
                      return Text(
 | 
			
		||||
                        snapshot.data ??
 | 
			
		||||
                            event.calcLocalizedBodyFallback(
 | 
			
		||||
                                MatrixLocals(L10n.of(context)!)),
 | 
			
		||||
                        textAlign: TextAlign.center,
 | 
			
		||||
                        style: TextStyle(
 | 
			
		||||
                          fontSize: 14 * AppConfig.fontSizeFactor,
 | 
			
		||||
                          color: Theme.of(context).textTheme.bodyText2!.color,
 | 
			
		||||
                          decoration: event.redacted
 | 
			
		||||
                              ? TextDecoration.lineThrough
 | 
			
		||||
                              : null,
 | 
			
		||||
                        ),
 | 
			
		||||
                      );
 | 
			
		||||
                    }),
 | 
			
		||||
                if (counter != 0)
 | 
			
		||||
                  Text(
 | 
			
		||||
                    L10n.of(context)!.moreEvents(counter),
 | 
			
		||||
                    style: TextStyle(
 | 
			
		||||
                      fontWeight: FontWeight.bold,
 | 
			
		||||
                      fontSize: 14 * AppConfig.fontSizeFactor,
 | 
			
		||||
                    ),
 | 
			
		||||
        child: Container(
 | 
			
		||||
          padding: const EdgeInsets.all(8),
 | 
			
		||||
          decoration: BoxDecoration(
 | 
			
		||||
            color: Theme.of(context).brightness == Brightness.light
 | 
			
		||||
                ? Colors.white
 | 
			
		||||
                : Colors.grey.shade900,
 | 
			
		||||
            borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
 | 
			
		||||
          ),
 | 
			
		||||
          child: Column(
 | 
			
		||||
            mainAxisSize: MainAxisSize.min,
 | 
			
		||||
            children: [
 | 
			
		||||
              FutureBuilder<String>(
 | 
			
		||||
                  future:
 | 
			
		||||
                      event.calcLocalizedBody(MatrixLocals(L10n.of(context)!)),
 | 
			
		||||
                  builder: (context, snapshot) {
 | 
			
		||||
                    return Text(
 | 
			
		||||
                      snapshot.data ??
 | 
			
		||||
                          event.calcLocalizedBodyFallback(
 | 
			
		||||
                              MatrixLocals(L10n.of(context)!)),
 | 
			
		||||
                      textAlign: TextAlign.center,
 | 
			
		||||
                      style: TextStyle(
 | 
			
		||||
                        fontSize: 14 * AppConfig.fontSizeFactor,
 | 
			
		||||
                        color: Theme.of(context).textTheme.bodyText2!.color,
 | 
			
		||||
                        decoration:
 | 
			
		||||
                            event.redacted ? TextDecoration.lineThrough : null,
 | 
			
		||||
                      ),
 | 
			
		||||
                    );
 | 
			
		||||
                  }),
 | 
			
		||||
              if (counter != 0)
 | 
			
		||||
                Text(
 | 
			
		||||
                  L10n.of(context)!.moreEvents(counter),
 | 
			
		||||
                  style: TextStyle(
 | 
			
		||||
                    fontWeight: FontWeight.bold,
 | 
			
		||||
                    fontSize: 14 * AppConfig.fontSizeFactor,
 | 
			
		||||
                  ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
 | 
			
		||||
@ -12,11 +12,7 @@ class SeenByRow extends StatelessWidget {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final seenByUsers = controller.room!.getSeenByUsers(
 | 
			
		||||
      controller.timeline!,
 | 
			
		||||
      controller.filteredEvents,
 | 
			
		||||
      controller.unfolded,
 | 
			
		||||
    );
 | 
			
		||||
    final seenByUsers = controller.room!.getSeenByUsers(controller.timeline!);
 | 
			
		||||
    const maxAvatars = 7;
 | 
			
		||||
    return Container(
 | 
			
		||||
      width: double.infinity,
 | 
			
		||||
@ -28,8 +24,8 @@ class SeenByRow extends StatelessWidget {
 | 
			
		||||
        duration: seenByUsers.isEmpty
 | 
			
		||||
            ? const Duration(milliseconds: 0)
 | 
			
		||||
            : const Duration(milliseconds: 300),
 | 
			
		||||
        alignment: controller.filteredEvents.isNotEmpty &&
 | 
			
		||||
                controller.filteredEvents.first.senderId ==
 | 
			
		||||
        alignment: controller.timeline!.events.isNotEmpty &&
 | 
			
		||||
                controller.timeline!.events.first.senderId ==
 | 
			
		||||
                    Matrix.of(context).client.userID
 | 
			
		||||
            ? Alignment.topRight
 | 
			
		||||
            : Alignment.topLeft,
 | 
			
		||||
 | 
			
		||||
@ -26,8 +26,8 @@ class TypingIndicators extends StatelessWidget {
 | 
			
		||||
        height: typingUsers.isEmpty ? 0 : Avatar.defaultSize + bottomPadding,
 | 
			
		||||
        duration: const Duration(milliseconds: 300),
 | 
			
		||||
        curve: Curves.bounceInOut,
 | 
			
		||||
        alignment: controller.filteredEvents.isNotEmpty &&
 | 
			
		||||
                controller.filteredEvents.first.senderId ==
 | 
			
		||||
        alignment: controller.timeline!.events.isNotEmpty &&
 | 
			
		||||
                controller.timeline!.events.first.senderId ==
 | 
			
		||||
                    Matrix.of(context).client.userID
 | 
			
		||||
            ? Alignment.topRight
 | 
			
		||||
            : Alignment.topLeft,
 | 
			
		||||
 | 
			
		||||
@ -45,6 +45,12 @@ class SettingsChatView extends StatelessWidget {
 | 
			
		||||
                storeKey: SettingKeys.hideUnknownEvents,
 | 
			
		||||
                defaultValue: AppConfig.hideUnknownEvents,
 | 
			
		||||
              ),
 | 
			
		||||
              SettingsSwitchListTile.adaptive(
 | 
			
		||||
                title: L10n.of(context)!.hideUnimportantStateEvents,
 | 
			
		||||
                onChanged: (b) => AppConfig.hideUnimportantStateEvents = b,
 | 
			
		||||
                storeKey: SettingKeys.hideUnimportantStateEvents,
 | 
			
		||||
                defaultValue: AppConfig.hideUnimportantStateEvents,
 | 
			
		||||
              ),
 | 
			
		||||
              if (PlatformInfos.isMobile)
 | 
			
		||||
                SettingsSwitchListTile.adaptive(
 | 
			
		||||
                  title: L10n.of(context)!.autoplayImages,
 | 
			
		||||
 | 
			
		||||
@ -110,8 +110,6 @@ class StoryPageController extends State<StoryPage> {
 | 
			
		||||
    if (timeline == null || currentEvent == null) return [];
 | 
			
		||||
    return Matrix.of(context).client.getRoomById(roomId)?.getSeenByUsers(
 | 
			
		||||
              timeline,
 | 
			
		||||
              events,
 | 
			
		||||
              {},
 | 
			
		||||
              eventId: currentEvent.eventId,
 | 
			
		||||
            ) ??
 | 
			
		||||
        [];
 | 
			
		||||
 | 
			
		||||
@ -2,50 +2,39 @@ import 'package:matrix/matrix.dart';
 | 
			
		||||
 | 
			
		||||
import '../../config/app_config.dart';
 | 
			
		||||
 | 
			
		||||
extension FilteredTimelineExtension on Timeline {
 | 
			
		||||
  List<Event> getFilteredEvents({Set<String> unfolded = const {}}) {
 | 
			
		||||
    final filteredEvents = events
 | 
			
		||||
        .where((e) =>
 | 
			
		||||
            // always filter out edit and reaction relationships
 | 
			
		||||
            !{RelationshipTypes.edit, RelationshipTypes.reaction}
 | 
			
		||||
                .contains(e.relationshipType) &&
 | 
			
		||||
            // always filter out m.key.* events
 | 
			
		||||
            !e.type.startsWith('m.key.verification.') &&
 | 
			
		||||
            // event types to hide: redaction and reaction events
 | 
			
		||||
            // if a reaction has been redacted we also want it to be hidden in the timeline
 | 
			
		||||
            !{EventTypes.Reaction, EventTypes.Redaction}.contains(e.type) &&
 | 
			
		||||
            // if we enabled to hide all redacted events, don't show those
 | 
			
		||||
            (!AppConfig.hideRedactedEvents || !e.redacted) &&
 | 
			
		||||
            // if we enabled to hide all unknown events, don't show those
 | 
			
		||||
            (!AppConfig.hideUnknownEvents || e.isEventTypeKnown) &&
 | 
			
		||||
            // remove state events that we don't want to render
 | 
			
		||||
            (e.isState || !AppConfig.hideAllStateEvents))
 | 
			
		||||
        .toList();
 | 
			
		||||
 | 
			
		||||
    // Fold state events
 | 
			
		||||
    var counter = 0;
 | 
			
		||||
    for (var i = filteredEvents.length - 1; i >= 0; i--) {
 | 
			
		||||
      if (!filteredEvents[i].isState) continue;
 | 
			
		||||
      if (i > 0 &&
 | 
			
		||||
          filteredEvents[i - 1].isState &&
 | 
			
		||||
          !unfolded.contains(filteredEvents[i - 1].eventId)) {
 | 
			
		||||
        counter++;
 | 
			
		||||
        filteredEvents[i].unsigned ??= {};
 | 
			
		||||
        filteredEvents[i].unsigned!['im.fluffychat.collapsed_state_event'] =
 | 
			
		||||
            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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extension IsStateExtension on Event {
 | 
			
		||||
  bool get isVisibleInGui =>
 | 
			
		||||
      // always filter out edit and reaction relationships
 | 
			
		||||
      !{RelationshipTypes.edit, RelationshipTypes.reaction}
 | 
			
		||||
          .contains(relationshipType) &&
 | 
			
		||||
      // always filter out m.key.* events
 | 
			
		||||
      !type.startsWith('m.key.verification.') &&
 | 
			
		||||
      // event types to hide: redaction and reaction events
 | 
			
		||||
      // if a reaction has been redacted we also want it to be hidden in the timeline
 | 
			
		||||
      !{EventTypes.Reaction, EventTypes.Redaction}.contains(type) &&
 | 
			
		||||
      // if we enabled to hide all redacted events, don't show those
 | 
			
		||||
      (!AppConfig.hideRedactedEvents || !redacted) &&
 | 
			
		||||
      // if we enabled to hide all unknown events, don't show those
 | 
			
		||||
      (!AppConfig.hideUnknownEvents || isEventTypeKnown) &&
 | 
			
		||||
      // remove state events that we don't want to render
 | 
			
		||||
      (isState || !AppConfig.hideAllStateEvents) &&
 | 
			
		||||
      // hide unimportant state events
 | 
			
		||||
      (!AppConfig.hideUnimportantStateEvents ||
 | 
			
		||||
          !isState ||
 | 
			
		||||
          importantStateEvents.contains(type)) &&
 | 
			
		||||
      // hide member events in public rooms
 | 
			
		||||
      (!AppConfig.hideUnimportantStateEvents ||
 | 
			
		||||
          type != EventTypes.RoomMember ||
 | 
			
		||||
          room.joinRules != JoinRules.public);
 | 
			
		||||
 | 
			
		||||
  static const Set<String> importantStateEvents = {
 | 
			
		||||
    EventTypes.Encryption,
 | 
			
		||||
    EventTypes.RoomCreate,
 | 
			
		||||
    EventTypes.RoomMember,
 | 
			
		||||
    EventTypes.RoomTombstone,
 | 
			
		||||
    EventTypes.CallInvite,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  bool get isState => !{
 | 
			
		||||
        EventTypes.Message,
 | 
			
		||||
        EventTypes.Sticker,
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,6 @@ import 'package:matrix/matrix.dart';
 | 
			
		||||
 | 
			
		||||
import '../config/app_config.dart';
 | 
			
		||||
import 'date_time_extension.dart';
 | 
			
		||||
import 'matrix_sdk_extensions.dart/filtered_timeline_extension.dart';
 | 
			
		||||
 | 
			
		||||
extension RoomStatusExtension on Room {
 | 
			
		||||
  CachedPresence? get directChatPresence =>
 | 
			
		||||
@ -65,14 +64,9 @@ extension RoomStatusExtension on Room {
 | 
			
		||||
    return typingText;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  List<User> getSeenByUsers(
 | 
			
		||||
      Timeline timeline, List<Event> filteredEvents, Set<String> unfolded,
 | 
			
		||||
      {String? eventId}) {
 | 
			
		||||
  List<User> getSeenByUsers(Timeline timeline, {String? eventId}) {
 | 
			
		||||
    if (timeline.events.isEmpty) return [];
 | 
			
		||||
 | 
			
		||||
    final filteredEvents = timeline.getFilteredEvents(unfolded: unfolded);
 | 
			
		||||
    if (filteredEvents.isEmpty) return [];
 | 
			
		||||
    eventId ??= filteredEvents.first.eventId;
 | 
			
		||||
    eventId ??= timeline.events.first.eventId;
 | 
			
		||||
 | 
			
		||||
    final lastReceipts = <User>{};
 | 
			
		||||
    // now we iterate the timeline events until we hit the first rendered event
 | 
			
		||||
@ -83,7 +77,7 @@ extension RoomStatusExtension on Room {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    lastReceipts.removeWhere((user) =>
 | 
			
		||||
        user.id == client.userID || user.id == filteredEvents.first.senderId);
 | 
			
		||||
        user.id == client.userID || user.id == timeline.events.first.senderId);
 | 
			
		||||
    return lastReceipts.toList();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user