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 renderHtml = true;
|
||||||
static bool hideRedactedEvents = false;
|
static bool hideRedactedEvents = false;
|
||||||
static bool hideUnknownEvents = true;
|
static bool hideUnknownEvents = true;
|
||||||
static bool hideUnimportantStateEvents = true;
|
|
||||||
static bool separateChatTypes = false;
|
static bool separateChatTypes = false;
|
||||||
static bool autoplayImages = true;
|
static bool autoplayImages = true;
|
||||||
static bool sendTypingNotifications = true;
|
static bool sendTypingNotifications = true;
|
||||||
|
|
@ -64,7 +63,6 @@ abstract class AppConfig {
|
||||||
static bool displayNavigationRail = false;
|
static bool displayNavigationRail = false;
|
||||||
static bool experimentalVoip = false;
|
static bool experimentalVoip = false;
|
||||||
static const bool hideTypingUsernames = false;
|
static const bool hideTypingUsernames = false;
|
||||||
static const bool hideAllStateEvents = false;
|
|
||||||
static const String inviteLinkPrefix = 'https://matrix.to/#/';
|
static const String inviteLinkPrefix = 'https://matrix.to/#/';
|
||||||
static const String deepLinkPrefix = 'im.fluffychat://chat/';
|
static const String deepLinkPrefix = 'im.fluffychat://chat/';
|
||||||
static const String schemePrefix = 'matrix:';
|
static const String schemePrefix = 'matrix:';
|
||||||
|
|
|
||||||
|
|
@ -3240,5 +3240,6 @@
|
||||||
"commandHint_logout": "Logout your current device",
|
"commandHint_logout": "Logout your current device",
|
||||||
"commandHint_logoutall": "Logout all active devices",
|
"commandHint_logoutall": "Logout all active devices",
|
||||||
"displayNavigationRail": "Show navigation rail on mobile",
|
"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 {
|
void _tryLoadTimeline() async {
|
||||||
final initialEventId = widget.eventId;
|
final initialEventId = widget.eventId;
|
||||||
loadTimelineFuture = _getTimeline();
|
loadTimelineFuture = _getTimeline();
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,17 @@ class ChatEventList extends StatelessWidget {
|
||||||
timeline.events.length > animateInEventIndex &&
|
timeline.events.length > animateInEventIndex &&
|
||||||
event == timeline.events[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(
|
return AutoScrollTag(
|
||||||
key: ValueKey(event.eventId),
|
key: ValueKey(event.eventId),
|
||||||
index: i,
|
index: i,
|
||||||
|
|
@ -148,11 +159,18 @@ class ChatEventList extends StatelessWidget {
|
||||||
timeline: timeline,
|
timeline: timeline,
|
||||||
displayReadMarker:
|
displayReadMarker:
|
||||||
i > 0 && controller.readMarkerEventId == event.eventId,
|
i > 0 && controller.readMarkerEventId == event.eventId,
|
||||||
nextEvent: i + 1 < events.length ? events[i + 1] : null,
|
nextEvent: nextEvent,
|
||||||
previousEvent: i > 0 ? events[i - 1] : null,
|
previousEvent: previousEvent,
|
||||||
wallpaperMode: hasWallpaper,
|
wallpaperMode: hasWallpaper,
|
||||||
scrollController: controller.scrollController,
|
scrollController: controller.scrollController,
|
||||||
colors: colors,
|
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 bool wallpaperMode;
|
||||||
final ScrollController scrollController;
|
final ScrollController scrollController;
|
||||||
final List<Color> colors;
|
final List<Color> colors;
|
||||||
|
final void Function()? onExpand;
|
||||||
|
final bool isCollapsed;
|
||||||
|
|
||||||
const Message(
|
const Message(
|
||||||
this.event, {
|
this.event, {
|
||||||
|
|
@ -66,6 +68,8 @@ class Message extends StatelessWidget {
|
||||||
required this.onMention,
|
required this.onMention,
|
||||||
required this.scrollController,
|
required this.scrollController,
|
||||||
required this.colors,
|
required this.colors,
|
||||||
|
this.onExpand,
|
||||||
|
this.isCollapsed = false,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -85,7 +89,7 @@ class Message extends StatelessWidget {
|
||||||
if (event.type == EventTypes.RoomCreate) {
|
if (event.type == EventTypes.RoomCreate) {
|
||||||
return RoomCreationStateEvent(event: event);
|
return RoomCreationStateEvent(event: event);
|
||||||
}
|
}
|
||||||
return StateMessage(event);
|
return StateMessage(event, onExpand: onExpand, isCollapsed: isCollapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type == EventTypes.Message &&
|
if (event.type == EventTypes.Message &&
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,84 @@
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
|
import 'package:fluffychat/config/themes.dart';
|
||||||
import 'package:fluffychat/l10n/l10n.dart';
|
import 'package:fluffychat/l10n/l10n.dart';
|
||||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||||
import '../../../config/app_config.dart';
|
import '../../../config/app_config.dart';
|
||||||
|
|
||||||
class StateMessage extends StatelessWidget {
|
class StateMessage extends StatelessWidget {
|
||||||
final Event event;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return Padding(
|
return AnimatedSize(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
duration: FluffyThemes.animationDuration,
|
||||||
child: Center(
|
curve: FluffyThemes.animationCurve,
|
||||||
child: Padding(
|
child: isCollapsed
|
||||||
padding: const EdgeInsets.all(4),
|
? const SizedBox.shrink()
|
||||||
child: Material(
|
: Padding(
|
||||||
color: theme.colorScheme.surface.withAlpha(128),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 3),
|
child: Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding:
|
padding: const EdgeInsets.all(4),
|
||||||
const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
child: Material(
|
||||||
child: Text(
|
color: theme.colorScheme.surface.withAlpha(128),
|
||||||
event.calcLocalizedBodyFallback(
|
borderRadius:
|
||||||
MatrixLocals(L10n.of(context)),
|
BorderRadius.circular(AppConfig.borderRadius / 3),
|
||||||
),
|
child: Padding(
|
||||||
textAlign: TextAlign.center,
|
padding: const EdgeInsets.symmetric(
|
||||||
maxLines: 2,
|
horizontal: 8.0,
|
||||||
overflow: TextOverflow.ellipsis,
|
vertical: 4.0,
|
||||||
style: TextStyle(
|
),
|
||||||
fontSize: 12 * AppConfig.fontSizeFactor,
|
child: Text.rich(
|
||||||
decoration:
|
TextSpan(
|
||||||
event.redacted ? TextDecoration.lineThrough : null,
|
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,
|
storeKey: SettingKeys.renderHtml,
|
||||||
defaultValue: AppConfig.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(
|
SettingsSwitchListTile.adaptive(
|
||||||
title: L10n.of(context).hideRedactedMessages,
|
title: L10n.of(context).hideRedactedMessages,
|
||||||
subtitle: L10n.of(context).hideRedactedMessagesBody,
|
subtitle: L10n.of(context).hideRedactedMessagesBody,
|
||||||
|
|
|
||||||
|
|
@ -3,33 +3,9 @@ import 'package:matrix/matrix.dart';
|
||||||
import '../../config/app_config.dart';
|
import '../../config/app_config.dart';
|
||||||
|
|
||||||
extension VisibleInGuiExtension on List<Event> {
|
extension VisibleInGuiExtension on List<Event> {
|
||||||
List<Event> filterByVisibleInGui({String? exceptionEventId}) {
|
List<Event> filterByVisibleInGui({String? exceptionEventId}) => where(
|
||||||
final visibleEvents =
|
(event) => event.isVisibleInGui || event.eventId == exceptionEventId,
|
||||||
where((e) => e.isVisibleInGui || e.eventId == exceptionEventId)
|
).toList();
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension IsStateExtension on Event {
|
extension IsStateExtension on Event {
|
||||||
|
|
@ -45,19 +21,19 @@ extension IsStateExtension on Event {
|
||||||
// if we enabled to hide all redacted events, don't show those
|
// if we enabled to hide all redacted events, don't show those
|
||||||
(!AppConfig.hideRedactedEvents || !redacted) &&
|
(!AppConfig.hideRedactedEvents || !redacted) &&
|
||||||
// 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 || isEventTypeKnown) &&
|
(!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);
|
|
||||||
|
|
||||||
bool get isState => !{
|
bool get isState => !{
|
||||||
EventTypes.Message,
|
EventTypes.Message,
|
||||||
EventTypes.Sticker,
|
EventTypes.Sticker,
|
||||||
EventTypes.Encrypted,
|
EventTypes.Encrypted,
|
||||||
}.contains(type);
|
}.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) ??
|
store.getBool(SettingKeys.hideUnknownEvents) ??
|
||||||
AppConfig.hideUnknownEvents;
|
AppConfig.hideUnknownEvents;
|
||||||
|
|
||||||
AppConfig.hideUnimportantStateEvents =
|
|
||||||
store.getBool(SettingKeys.hideUnimportantStateEvents) ??
|
|
||||||
AppConfig.hideUnimportantStateEvents;
|
|
||||||
|
|
||||||
AppConfig.separateChatTypes =
|
AppConfig.separateChatTypes =
|
||||||
store.getBool(SettingKeys.separateChatTypes) ??
|
store.getBool(SettingKeys.separateChatTypes) ??
|
||||||
AppConfig.separateChatTypes;
|
AppConfig.separateChatTypes;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue