feat: Collapse all state events by default
This commit is contained in:
parent
3d0a3ee226
commit
103cb8328d
9 changed files with 121 additions and 79 deletions
|
|
@ -53,7 +53,6 @@ abstract class AppConfig {
|
|||
static bool renderHtml = true;
|
||||
static bool hideRedactedEvents = false;
|
||||
static bool hideUnknownEvents = true;
|
||||
static bool hideUnimportantStateEvents = true;
|
||||
static bool separateChatTypes = false;
|
||||
static bool autoplayImages = true;
|
||||
static bool sendTypingNotifications = true;
|
||||
|
|
@ -64,7 +63,6 @@ abstract class AppConfig {
|
|||
static bool displayNavigationRail = false;
|
||||
static bool experimentalVoip = false;
|
||||
static const bool hideTypingUsernames = false;
|
||||
static const bool hideAllStateEvents = false;
|
||||
static const String inviteLinkPrefix = 'https://matrix.to/#/';
|
||||
static const String deepLinkPrefix = 'im.fluffychat://chat/';
|
||||
static const String schemePrefix = 'matrix:';
|
||||
|
|
|
|||
|
|
@ -3240,5 +3240,6 @@
|
|||
"commandHint_logout": "Logout your current device",
|
||||
"commandHint_logoutall": "Logout all active devices",
|
||||
"displayNavigationRail": "Show navigation rail on mobile",
|
||||
"customReaction": "Custom reaction"
|
||||
"customReaction": "Custom reaction",
|
||||
"moreEvents": "More events"
|
||||
}
|
||||
|
|
@ -339,6 +339,24 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
}
|
||||
|
||||
final Set<String> expandedEventIds = {};
|
||||
|
||||
void expandEventsFrom(Event event, bool expand) {
|
||||
final events = timeline!.events.filterByVisibleInGui();
|
||||
final start = events.indexOf(event);
|
||||
setState(() {
|
||||
for (var i = start; i < events.length; i++) {
|
||||
final event = events[i];
|
||||
if (!event.isCollapsedState) return;
|
||||
if (expand) {
|
||||
expandedEventIds.add(event.eventId);
|
||||
} else {
|
||||
expandedEventIds.remove(event.eventId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _tryLoadTimeline() async {
|
||||
final initialEventId = widget.eventId;
|
||||
loadTimelineFuture = _getTimeline();
|
||||
|
|
|
|||
|
|
@ -119,6 +119,17 @@ class ChatEventList extends StatelessWidget {
|
|||
timeline.events.length > animateInEventIndex &&
|
||||
event == timeline.events[animateInEventIndex];
|
||||
|
||||
final nextEvent = i + 1 < events.length ? events[i + 1] : null;
|
||||
final previousEvent = i > 0 ? events[i - 1] : null;
|
||||
|
||||
// Collapsed state event
|
||||
final canExpand = event.isCollapsedState &&
|
||||
nextEvent?.isCollapsedState == true &&
|
||||
previousEvent?.isCollapsedState != true;
|
||||
final isCollapsed = event.isCollapsedState &&
|
||||
previousEvent?.isCollapsedState == true &&
|
||||
!controller.expandedEventIds.contains(event.eventId);
|
||||
|
||||
return AutoScrollTag(
|
||||
key: ValueKey(event.eventId),
|
||||
index: i,
|
||||
|
|
@ -148,11 +159,18 @@ class ChatEventList extends StatelessWidget {
|
|||
timeline: timeline,
|
||||
displayReadMarker:
|
||||
i > 0 && controller.readMarkerEventId == event.eventId,
|
||||
nextEvent: i + 1 < events.length ? events[i + 1] : null,
|
||||
previousEvent: i > 0 ? events[i - 1] : null,
|
||||
nextEvent: nextEvent,
|
||||
previousEvent: previousEvent,
|
||||
wallpaperMode: hasWallpaper,
|
||||
scrollController: controller.scrollController,
|
||||
colors: colors,
|
||||
isCollapsed: isCollapsed,
|
||||
onExpand: canExpand
|
||||
? () => controller.expandEventsFrom(
|
||||
event,
|
||||
!controller.expandedEventIds.contains(event.eventId),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ class Message extends StatelessWidget {
|
|||
final bool wallpaperMode;
|
||||
final ScrollController scrollController;
|
||||
final List<Color> colors;
|
||||
final void Function()? onExpand;
|
||||
final bool isCollapsed;
|
||||
|
||||
const Message(
|
||||
this.event, {
|
||||
|
|
@ -66,6 +68,8 @@ class Message extends StatelessWidget {
|
|||
required this.onMention,
|
||||
required this.scrollController,
|
||||
required this.colors,
|
||||
this.onExpand,
|
||||
this.isCollapsed = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -85,7 +89,7 @@ class Message extends StatelessWidget {
|
|||
if (event.type == EventTypes.RoomCreate) {
|
||||
return RoomCreationStateEvent(event: event);
|
||||
}
|
||||
return StateMessage(event);
|
||||
return StateMessage(event, onExpand: onExpand, isCollapsed: isCollapsed);
|
||||
}
|
||||
|
||||
if (event.type == EventTypes.Message &&
|
||||
|
|
|
|||
|
|
@ -1,46 +1,84 @@
|
|||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import '../../../config/app_config.dart';
|
||||
|
||||
class StateMessage extends StatelessWidget {
|
||||
final Event event;
|
||||
const StateMessage(this.event, {super.key});
|
||||
final void Function()? onExpand;
|
||||
final bool isCollapsed;
|
||||
const StateMessage(
|
||||
this.event, {
|
||||
this.onExpand,
|
||||
this.isCollapsed = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Material(
|
||||
color: theme.colorScheme.surface.withAlpha(128),
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 3),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
||||
child: Text(
|
||||
event.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 12 * AppConfig.fontSizeFactor,
|
||||
decoration:
|
||||
event.redacted ? TextDecoration.lineThrough : null,
|
||||
return AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
child: isCollapsed
|
||||
? const SizedBox.shrink()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Material(
|
||||
color: theme.colorScheme.surface.withAlpha(128),
|
||||
borderRadius:
|
||||
BorderRadius.circular(AppConfig.borderRadius / 3),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
vertical: 4.0,
|
||||
),
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: event.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
),
|
||||
),
|
||||
if (onExpand != null) ...[
|
||||
const TextSpan(
|
||||
text: ' + ',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
TextSpan(
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.primary,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = onExpand,
|
||||
text: L10n.of(context).moreEvents,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12 * AppConfig.fontSizeFactor,
|
||||
decoration: event.redacted
|
||||
? TextDecoration.lineThrough
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,13 +38,6 @@ class SettingsChatView extends StatelessWidget {
|
|||
storeKey: SettingKeys.renderHtml,
|
||||
defaultValue: AppConfig.renderHtml,
|
||||
),
|
||||
SettingsSwitchListTile.adaptive(
|
||||
title: L10n.of(context).hideMemberChangesInPublicChats,
|
||||
subtitle: L10n.of(context).hideMemberChangesInPublicChatsBody,
|
||||
onChanged: (b) => AppConfig.hideUnimportantStateEvents = b,
|
||||
storeKey: SettingKeys.hideUnimportantStateEvents,
|
||||
defaultValue: AppConfig.hideUnimportantStateEvents,
|
||||
),
|
||||
SettingsSwitchListTile.adaptive(
|
||||
title: L10n.of(context).hideRedactedMessages,
|
||||
subtitle: L10n.of(context).hideRedactedMessagesBody,
|
||||
|
|
|
|||
|
|
@ -3,33 +3,9 @@ import 'package:matrix/matrix.dart';
|
|||
import '../../config/app_config.dart';
|
||||
|
||||
extension VisibleInGuiExtension on List<Event> {
|
||||
List<Event> filterByVisibleInGui({String? exceptionEventId}) {
|
||||
final visibleEvents =
|
||||
where((e) => e.isVisibleInGui || e.eventId == exceptionEventId)
|
||||
.toList();
|
||||
|
||||
// Hide creation state events:
|
||||
if (visibleEvents.isNotEmpty &&
|
||||
visibleEvents.last.type == EventTypes.RoomCreate) {
|
||||
var i = visibleEvents.length - 2;
|
||||
while (i > 0) {
|
||||
final event = visibleEvents[i];
|
||||
if (!event.isState) break;
|
||||
if (event.type == EventTypes.Encryption) {
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
if (event.type == EventTypes.RoomMember &&
|
||||
event.roomMemberChangeType == RoomMemberChangeType.acceptInvite) {
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
visibleEvents.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return visibleEvents;
|
||||
}
|
||||
List<Event> filterByVisibleInGui({String? exceptionEventId}) => where(
|
||||
(event) => event.isVisibleInGui || event.eventId == exceptionEventId,
|
||||
).toList();
|
||||
}
|
||||
|
||||
extension IsStateExtension on Event {
|
||||
|
|
@ -45,19 +21,19 @@ extension IsStateExtension on Event {
|
|||
// 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 simple join/leave member events in public rooms
|
||||
(!AppConfig.hideUnimportantStateEvents ||
|
||||
type != EventTypes.RoomMember ||
|
||||
room.joinRules != JoinRules.public ||
|
||||
content.tryGet<String>('membership') == 'ban' ||
|
||||
stateKey != senderId);
|
||||
(!AppConfig.hideUnknownEvents || isEventTypeKnown);
|
||||
|
||||
bool get isState => !{
|
||||
EventTypes.Message,
|
||||
EventTypes.Sticker,
|
||||
EventTypes.Encrypted,
|
||||
}.contains(type);
|
||||
|
||||
bool get isCollapsedState => !{
|
||||
EventTypes.Message,
|
||||
EventTypes.Sticker,
|
||||
EventTypes.Encrypted,
|
||||
EventTypes.RoomCreate,
|
||||
EventTypes.RoomTombstone,
|
||||
}.contains(type);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -411,10 +411,6 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
|||
store.getBool(SettingKeys.hideUnknownEvents) ??
|
||||
AppConfig.hideUnknownEvents;
|
||||
|
||||
AppConfig.hideUnimportantStateEvents =
|
||||
store.getBool(SettingKeys.hideUnimportantStateEvents) ??
|
||||
AppConfig.hideUnimportantStateEvents;
|
||||
|
||||
AppConfig.separateChatTypes =
|
||||
store.getBool(SettingKeys.separateChatTypes) ??
|
||||
AppConfig.separateChatTypes;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue