Watch
1
0
Fork
You've already forked nexus
0
forked from Nexus/nexus

Compare commits

...
Sign in to create a new pull request.
Author SHA1 Message Date
a1a15af30d
use dot shorthands for default immutable collections where possible 2026-10-20 18:23:41 -04:00
800fb5566d add a global toggle to hide membership events (#142)
fixes #141

Co-authored-by: seres <seres@seres.eu.org>
Reviewed-on: Nexus/nexus#142
2026-10-20 18:15:01 -04:00
16 changed files with 51 additions and 23 deletions

View file

@ -35,7 +35,7 @@ class RoomsController extends Notifier<IMap<String, Room>> {
void update( void update(
IMap<String, Room> rooms, [ IMap<String, Room> rooms, [
ISet<String> leftRooms = const ISet.empty(), ISet<String> leftRooms = const .empty(),
]) { ]) {
final merged = rooms.entries.fold(state, (acc, entry) { final merged = rooms.entries.fold(state, (acc, entry) {
final roomId = entry.key; final roomId = entry.key;

View file

@ -166,6 +166,21 @@ class SettingsSectionsController
: null, : null,
), ),
), ),
.new(
title: "Hide Membership Events",
description: "Hides membership events (invites, joins, leaves, bans & knocks) from appearing in conversations.",
icon: Icons.person_off,
builder: (title, description, icon) => SwitchListTile(
title: Text(title),
subtitle: Text(description),
secondary: Icon(icon),
value: settings.hideMembershipEvents,
onChanged: (value) => ref
.read(SettingsController.provider.notifier)
.set(settings.copyWith(hideMembershipEvents: value))
.onError(showError),
),
),
]), ]),
), ),
]), ]),

View file

@ -12,7 +12,7 @@ class const AccountData({
const InvitePermissionConfig(), const InvitePermissionConfig(),
@JsonKey(name: AccountData.directKey) @JsonKey(name: AccountData.directKey)
final IMap<String, IList<String>> directMessages = const IMap.empty(), final IMap<String, IList<String>> directMessages = const .empty(),
@JsonKey(name: AccountData.recentEmojiKey) @JsonKey(name: AccountData.recentEmojiKey)
final RecentEmojiData recentEmoji = const RecentEmojiData(), final RecentEmojiData recentEmoji = const RecentEmojiData(),
@ -30,7 +30,7 @@ class const AccountData({
@Freezed(toJson: false, fromJson: false) @Freezed(toJson: false, fromJson: false)
@JsonSerializable() @JsonSerializable()
class const RecentEmojiData({ class const RecentEmojiData({
final IList<RecentEmoji> recentEmoji = const IList.empty(), final IList<RecentEmoji> recentEmoji = const .empty(),
}) with _$RecentEmojiData { }) with _$RecentEmojiData {
Map<String, Object?> toJson() => _$RecentEmojiDataToJson(this); Map<String, Object?> toJson() => _$RecentEmojiDataToJson(this);

View file

@ -28,7 +28,7 @@ sealed class PowerLevelsContent extends Content with _$PowerLevelsContent {
@JsonSerializable() @JsonSerializable()
class const Notifications({ class const Notifications({
final int room = 50, final int room = 50,
final IMap<String, int> other = const IMap.empty(), final IMap<String, int> other = const .empty(),
}) with _$Notifications { }) with _$Notifications {
Map<String, Object?> toJson() => _$NotificationsToJson(this); Map<String, Object?> toJson() => _$NotificationsToJson(this);

View file

@ -18,7 +18,7 @@ class const Event({
required final String sender, required final String sender,
@JsonKey(readValue: Event.typeJsonFromJson) required final String type, @JsonKey(readValue: Event.typeJsonFromJson) required final String type,
@EpochDateTimeConverter() required final DateTime timestamp, @EpochDateTimeConverter() required final DateTime timestamp,
final IMap<String, dynamic> unsigned = const IMap.empty(), final IMap<String, dynamic> unsigned = const .empty(),
final LocalContent? localContent, final LocalContent? localContent,
final String? transactionId, final String? transactionId,
final String? redactedBy, final String? redactedBy,
@ -27,7 +27,7 @@ class const Event({
final String? replyTo, final String? replyTo,
final String? decryptionError, final String? decryptionError,
final String? sendError, final String? sendError,
final IMap<String, int> reactions = const IMap.empty(), final IMap<String, int> reactions = const .empty(),
@JsonKey(name: "last_edit_rowid") final int lastEditRowId = 0, @JsonKey(name: "last_edit_rowid") final int lastEditRowId = 0,
final UnreadType? unreadType, final UnreadType? unreadType,
final Profile? pmp, final Profile? pmp,

View file

@ -13,7 +13,7 @@ class const EventContext({
required final IList<Event> after, required final IList<Event> after,
required final String start, required final String start,
required final String end, required final String end,
final IList<Event> relatedEvents = const IList.empty(), final IList<Event> relatedEvents = const .empty(),
}) with _$EventContext { }) with _$EventContext {
Map<String, Object?> toJson() => _$EventContextToJson(this); Map<String, Object?> toJson() => _$EventContextToJson(this);

View file

@ -9,7 +9,7 @@ part "paginate_manual.g.dart";
@JsonSerializable() @JsonSerializable()
class const PaginateManual({ class const PaginateManual({
required final IList<Event> events, required final IList<Event> events,
final IList<Event> relatedEvents = const IList.empty(), final IList<Event> relatedEvents = const .empty(),
required final String? nextBatch, required final String? nextBatch,
}) with _$PaginateManual { }) with _$PaginateManual {
Map<String, Object?> toJson() => _$PaginateManualToJson(this); Map<String, Object?> toJson() => _$PaginateManualToJson(this);

View file

@ -40,7 +40,7 @@ class const Profile({
final String? timezone, final String? timezone,
@JsonKey(readValue: Profile.readPronouns, name: "m.pronouns") @JsonKey(readValue: Profile.readPronouns, name: "m.pronouns")
final IList<Pronoun> pronouns = const IList.empty(), final IList<Pronoun> pronouns = const .empty(),
}) with _$Profile { }) with _$Profile {
Map<String, Object?> toJson() => _$ProfileToJson(this); Map<String, Object?> toJson() => _$ProfileToJson(this);

View file

@ -13,10 +13,10 @@ class const CreateRoomRequest({
final String? roomAliasName, final String? roomAliasName,
final String? name, final String? name,
final String? topic, final String? topic,
final IList<String> invite = const IList.empty(), final IList<String> invite = const .empty(),
@JsonKey(name: "uk.timedout.msc4491.invite_reason") @JsonKey(name: "uk.timedout.msc4491.invite_reason")
final String? inviteReason, final String? inviteReason,
final IList<InitialStateEvent> initialState = const IList.empty(), final IList<InitialStateEvent> initialState = const .empty(),
final RoomPreset? preset, final RoomPreset? preset,
final bool isDirect = false, final bool isDirect = false,
final IMap<String, dynamic>? creationContent, final IMap<String, dynamic>? creationContent,

View file

@ -8,7 +8,7 @@ part "join_room.g.dart";
@JsonSerializable() @JsonSerializable()
class const JoinRoomRequest({ class const JoinRoomRequest({
required final String roomIdOrAlias, required final String roomIdOrAlias,
final IList<String> via = const IList.empty(), final IList<String> via = const .empty(),
/// Set when accepting an invite, so gomuks can mark DMs as such. /// Set when accepting an invite, so gomuks can mark DMs as such.
final bool fromInvite = false, final bool fromInvite = false,

View file

@ -25,7 +25,7 @@ class const SendMessageRequest({
@JsonSerializable() @JsonSerializable()
class const Mentions({ class const Mentions({
final bool room = false, final bool room = false,
final IList<String> userIds = const IList.empty(), final IList<String> userIds = const .empty(),
}) with _$Mentions { }) with _$Mentions {
Map<String, Object?> toJson() => _$MentionsToJson(this); Map<String, Object?> toJson() => _$MentionsToJson(this);

View file

@ -14,15 +14,15 @@ class const Room({
/// [timeline] is an IMap of timelineRowId to eventRowId /// [timeline] is an IMap of timelineRowId to eventRowId
@JsonKey(fromJson: Room.timelineTupleJsonToIMap) @JsonKey(fromJson: Room.timelineTupleJsonToIMap)
final IMap<int, int?> timeline = const IMap.empty(), final IMap<int, int?> timeline = const .empty(),
/// [clientSticky] is an ISet of eventRowId /// [clientSticky] is an ISet of eventRowId
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
final ISet<int> clientSticky = const ISet.empty(), final ISet<int> clientSticky = const .empty(),
/// [events] is an IMap of eventRowId to event /// [events] is an IMap of eventRowId to event
@JsonKey(fromJson: Room.eventsJsonToIMap) @JsonKey(fromJson: Room.eventsJsonToIMap)
final IMap<int, Event> events = const IMap.empty(), final IMap<int, Event> events = const .empty(),
final bool reset = false, final bool reset = false,
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@ -34,9 +34,9 @@ class const Room({
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
final bool hasFetchedMembers = false, final bool hasFetchedMembers = false,
final IMap<String, IMap<String, int>> state = const IMap.empty(), final IMap<String, IMap<String, int>> state = const .empty(),
final IMap<String, IList<ReadReceipt>> receipts = const IMap.empty(), final IMap<String, IList<ReadReceipt>> receipts = const .empty(),
final bool dismissNotifications = false, final bool dismissNotifications = false,
final bool hasMore = true, final bool hasMore = true,

View file

@ -14,6 +14,7 @@ class const Settings({
final bool showAllInHome = false, final bool showAllInHome = false,
final bool showMinimizeButton = false, final bool showMinimizeButton = false,
final bool swipeToReply = false, final bool swipeToReply = false,
final bool hideMembershipEvents = false,
final RoomOrder roomOrder = .unreadFirst, final RoomOrder roomOrder = .unreadFirst,
}) with _$Settings { }) with _$Settings {
Map<String, Object?> toJson() => _$SettingsToJson(this); Map<String, Object?> toJson() => _$SettingsToJson(this);

View file

@ -11,10 +11,10 @@ part "sync_data.g.dart";
@JsonSerializable(createToJson: false) @JsonSerializable(createToJson: false)
class const SyncData({ class const SyncData({
final bool clearState = false, final bool clearState = false,
final IMap<String, IMap<String, dynamic>> accountData = const IMap.empty(), final IMap<String, IMap<String, dynamic>> accountData = const .empty(),
final IMap<String, Room> rooms = const IMap.empty(), final IMap<String, Room> rooms = const .empty(),
final ISet<String> leftRooms = const ISet.empty(), final ISet<String> leftRooms = const .empty(),
final IList<InvitedRoom> invitedRooms = const IList.empty(), final IList<InvitedRoom> invitedRooms = const .empty(),
final IMap<String, IList<SpaceEdge>>? spaceEdges, final IMap<String, IList<SpaceEdge>>? spaceEdges,
final IList<String>? topLevelSpaces, final IList<String>? topLevelSpaces,
}) with _$SyncData { }) with _$SyncData {

View file

@ -6,6 +6,7 @@ import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/event.dart"; import "package:nexus/controllers/event.dart";
import "package:nexus/controllers/room_chat.dart"; import "package:nexus/controllers/room_chat.dart";
import "package:nexus/controllers/rooms.dart"; import "package:nexus/controllers/rooms.dart";
import "package:nexus/controllers/settings.dart";
import "package:nexus/helpers/extensions/show_adaptive_menu.dart"; import "package:nexus/helpers/extensions/show_adaptive_menu.dart";
import "package:nexus/helpers/hooks/chat_scroll.dart"; import "package:nexus/helpers/hooks/chat_scroll.dart";
import "package:nexus/models/content/avatar.dart"; import "package:nexus/models/content/avatar.dart";
@ -78,6 +79,17 @@ class const EventRenderer(
if (event == null) return SizedBox.shrink(); if (event == null) return SizedBox.shrink();
final hideMembershipEvents = (ref
.watch(SettingsController.provider)
.maybeWhen(
orElse: () => false,
data: (settings) => settings.hideMembershipEvents,
));
if (event.content is MembershipContent && hideMembershipEvents) {
return SizedBox.shrink();
}
final isGrouped = final isGrouped =
previousEvent?.content is MessageContent && previousEvent?.content is MessageContent &&
previousEvent?.redactedBy == null && previousEvent?.redactedBy == null &&

View file

@ -15,7 +15,7 @@ import "package:nexus/main.dart";
final class const RoomMenuButton( final class const RoomMenuButton(
final Room? room, { final Room? room, {
final IList<Room> children = const IList.empty(), final IList<Room> children = const .empty(),
super.key, super.key,
}) extends ConsumerWidget { }) extends ConsumerWidget {
@override @override