diff --git a/lib/controllers/room_chat.dart b/lib/controllers/room_chat.dart index 2860a60..94fb7a3 100644 --- a/lib/controllers/room_chat.dart +++ b/lib/controllers/room_chat.dart @@ -243,17 +243,14 @@ class RoomChatController(final ChatParams info) ref.invalidate(provider); } - var taggedMessage = text; - - for (final tag in tags) { - final escaped = RegExp.escape(tag.id); - final pattern = RegExp(r"@+(" + escaped + r")(#[^#]*#)?"); - - taggedMessage = taggedMessage.replaceAllMapped( - pattern, + // Replaces each formatted tag, like "@id#name#", with its ID + final taggedMessage = tags.fold( + text, + (message, tag) => message.replaceAllMapped( + RegExp(r"@+(" + RegExp.escape(tag.id) + r")(#[^#]*#)?"), (match) => match.group(1)!, - ); - } + ), + ); final client = ref.read(ClientController.provider.notifier); final event = await client.sendMessage( diff --git a/lib/controllers/spaces.dart b/lib/controllers/spaces.dart index 8af045c..4d6dffe 100644 --- a/lib/controllers/spaces.dart +++ b/lib/controllers/spaces.dart @@ -25,55 +25,50 @@ class SpacesController extends AsyncNotifier> { MapEntry(spaceId, edges.map((edge) => edge.childId).toList()), ); - Set collectDescendants(String startId) { - final visited = {}; - final stack = [startId]; - - while (stack.isNotEmpty) { - final current = stack.removeLast(); - final children = childrenById[current] ?? const []; - - for (final child in children) { - if (visited.add(child)) { - stack.add(child); - } - } - } - - return visited; - } + /// Everything under [spaceId], however deep, skipping any already + /// [visited] so cycles end. + ISet collectDescendants( + String spaceId, [ + ISet visited = const .empty(), + ]) => (childrenById[spaceId] ?? const []).fold( + visited, + (visited, childId) => visited.contains(childId) + ? visited + : collectDescendants(childId, visited.add(childId)), + ); Space buildSpace(String spaceId) { final space = rooms[spaceId]; final directChildrenIds = childrenById[spaceId] ?? const []; - final directRooms = []; - final subSpaces = []; - - for (final childId in directChildrenIds) { - final room = rooms[childId]; - if (room == null) continue; - - if (childrenById.containsKey(childId)) { - final descendants = collectDescendants(childId); - - subSpaces.add( - .new( - room: room, - children: .new(descendants.map((id) => rooms[id]).nonNulls), - ), - ); - } else { - directRooms.add(room); - } - } - return .new( id: spaceId, room: space, title: space?.metadata?.name ?? "Unnamed Space", - children: .new(directRooms), - subSpaces: .new(subSpaces), + children: .new( + directChildrenIds + .where((childId) => !childrenById.containsKey(childId)) + .map((childId) => rooms[childId]) + .nonNulls, + ), + subSpaces: .new( + directChildrenIds + .where(childrenById.containsKey) + .map( + (childId) => switch (rooms[childId]) { + final room? => Subspace( + room: room, + children: .new( + collectDescendants(childId) + .map((id) => rooms[id]) + .nonNulls, + ), + ), + null => null, + }, + ) + .nonNulls, + ), ); } diff --git a/lib/controllers/via.dart b/lib/controllers/via.dart index 39db682..8c7f0db 100644 --- a/lib/controllers/via.dart +++ b/lib/controllers/via.dart @@ -1,53 +1,48 @@ -import "package:collection/collection.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client_state.dart"; import "package:nexus/models/content/content.dart"; import "package:nexus/models/content/membership.dart"; import "package:nexus/models/content/power_levels.dart"; +import "package:nexus/models/event.dart"; import "package:nexus/models/room.dart"; class ViaController(final Room room) extends Notifier { @override String build() { - final servers = {}; + final powerLevelUsers = + switch (room.state[EventType.powerLevels.type]?[""]) { + final rowId? => switch (room.events[rowId]?.content) { + PowerLevelsContent(:final users) => users.keys.toIList(), + _ => null, + }, + null => null, + } ?? + .new([]); - void addUserId(String? userId) { - final server = userId?.split(":").lastOrNull; - if (server != null) { - servers.add(server); - } - } + final joinedMembers = + (room.state[EventType.membership.type]?.values ?? const []) + .map( + (rowId) => switch (room.events[rowId]) { + Event( + stateKey: final userId?, + content: MembershipContent(status: .join), + ) => + userId, + _ => null, + }, + ) + .nonNulls + .toIList(); - addUserId(ref.watch(ClientStateController.provider)?.userId); - - final powerLevelsEventId = room.state[EventType.powerLevels.type]?[""]; - final powerLevels = powerLevelsEventId == null - ? null - : room.events[powerLevelsEventId]; - - if (powerLevels?.content case PowerLevelsContent(:final users)) { - for (final userId in users.keys) { - addUserId(userId); - if (servers.length >= 5) break; - } - } - - final members = room.state[EventType.membership.type]?.values.toIList(); - for (var i = 0; servers.length < 5; i++) { - final membershipEventId = members?.getOrNull(i); - final member = membershipEventId == null - ? null - : room.events[membershipEventId]; - - if (member?.content case MembershipContent(:final status)) { - if (status == .join) { - addUserId(member?.stateKey); - } - } - - if (members?.getOrNull(i) == null) break; - } + final servers = [?ref.watch(ClientStateController.provider)?.userId] + .toIList() + .addAll(powerLevelUsers) + .addAll(joinedMembers) + .map((userId) => userId.substring(userId.indexOf(":") + 1)) + .whereNoDuplicates() + .take(5) + .toISet(); return servers.isEmpty ? "" diff --git a/lib/main.dart b/lib/main.dart index 5cd2129..8915339 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -147,7 +147,7 @@ class const App({super.key}) extends StatelessWidget { .watch(SettingsController.provider) .maybeWhen( data: (settings) => settings.theme, - orElse: () => ThemeMode.system, + orElse: () => .system, ), home: child, ); diff --git a/lib/models/emoji.dart b/lib/models/emoji.dart deleted file mode 100644 index 75faf08..0000000 --- a/lib/models/emoji.dart +++ /dev/null @@ -1,25 +0,0 @@ -import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:freezed_annotation/freezed_annotation.dart"; -import "package:flutter/widgets.dart"; -part "emoji.freezed.dart"; -part "emoji.g.dart"; - -@Freezed(toJson: false, fromJson: false) -@JsonSerializable(createToJson: false) -class Emoji({ - @JsonKey( - fromJson: Emoji.widgetFromJson, - readValue: Emoji.readWidgetValueFromJson, - ) - required final Widget widget, - @JsonKey(name: "emoji") required final String value, - required final IList aliases, - required final String description, - required final IList tags, -}) with _$Emoji { - static Widget widgetFromJson(String emoji) => Text(emoji); - - static String readWidgetValueFromJson(Map json, _) => - json["emoji"]; - factory Emoji.fromJson(Map json) => _$EmojiFromJson(json); -} diff --git a/lib/models/emoji_category.dart b/lib/models/emoji_category.dart deleted file mode 100644 index cc39bc3..0000000 --- a/lib/models/emoji_category.dart +++ /dev/null @@ -1,12 +0,0 @@ -import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:freezed_annotation/freezed_annotation.dart"; -import "package:nexus/models/emoji.dart"; -import "package:flutter/widgets.dart"; -part "emoji_category.freezed.dart"; - -@Freezed(toJson: false, fromJson: false) -class EmojiCategory({ - required final Widget icon, - required final String name, - required final IList emojis, -}) with _$EmojiCategory; diff --git a/lib/widgets/composer/composer.dart b/lib/widgets/composer/composer.dart index 5f79960..720fa58 100644 --- a/lib/widgets/composer/composer.dart +++ b/lib/widgets/composer/composer.dart @@ -399,11 +399,11 @@ class const Composer( if (!shiftPressed) { send(); - return KeyEventResult.handled; + return .handled; } } - return KeyEventResult.ignored; + return .ignored; }, // Overrides the keyboard shortcut for pasting child: Actions( diff --git a/lib/widgets/composer/relation_preview.dart b/lib/widgets/composer/relation_preview.dart index fca9de6..aaf1f5c 100644 --- a/lib/widgets/composer/relation_preview.dart +++ b/lib/widgets/composer/relation_preview.dart @@ -38,7 +38,7 @@ class const RelationPreview( onPressed: toggleShouldMention, child: Text( shouldMention ? "@On" : "@Off", - style: TextStyle( + style: .new( fontWeight: .w900, color: shouldMention ? null : Theme.of(context).disabledColor, ), diff --git a/lib/widgets/html/html.dart b/lib/widgets/html/html.dart index 5ea584f..d356f6e 100644 --- a/lib/widgets/html/html.dart +++ b/lib/widgets/html/html.dart @@ -76,7 +76,7 @@ class const Html( src == null ? SizedBox.shrink() : InlineCustomWidget( - alignment: PlaceholderAlignment.middle, + alignment: .middle, child: ExpandableImage( .new(mxc: src), downloadFilename: src.pathSegments.last, diff --git a/lib/widgets/lazy_loading/message_displayname.dart b/lib/widgets/lazy_loading/message_displayname.dart index 5d3bf19..d7c91bd 100644 --- a/lib/widgets/lazy_loading/message_displayname.dart +++ b/lib/widgets/lazy_loading/message_displayname.dart @@ -49,7 +49,7 @@ class const MessageDisplayname( final children => Row( spacing: 4, mainAxisSize: .min, - children: [for (final child in children) Flexible(child: child)], + children: children.map((child) => Flexible(child: child)).toList(), ), }, ), diff --git a/lib/widgets/message_image.dart b/lib/widgets/message_image.dart index 969dcd3..70aedd9 100644 --- a/lib/widgets/message_image.dart +++ b/lib/widgets/message_image.dart @@ -27,7 +27,7 @@ final class const MessageImage( child: Image( image: MxcImage(ref, request), width: info?.width?.toDouble(), - fit: BoxFit.fitWidth, + fit: .fitWidth, loadingBuilder: (_, child, loadingProgress) => loadingProgress == null ? child diff --git a/lib/widgets/notifications_list.dart b/lib/widgets/notifications_list.dart index 0b0741f..23ff676 100644 --- a/lib/widgets/notifications_list.dart +++ b/lib/widgets/notifications_list.dart @@ -151,7 +151,7 @@ class const NotificationsList({super.key}) listController: listController.value, controller: scrollController, itemCount: value.length, - padding: const EdgeInsets.symmetric( + padding: const .symmetric( horizontal: 8, vertical: 12, ), diff --git a/lib/widgets/room_menu_button.dart b/lib/widgets/room_menu_button.dart index 38ad847..358c8d8 100644 --- a/lib/widgets/room_menu_button.dart +++ b/lib/widgets/room_menu_button.dart @@ -146,7 +146,7 @@ final class const RoomMenuButton( ), child: ListTile( leading: Icon(Icons.logout, color: danger), - title: Text("Leave", style: TextStyle(color: danger)), + title: Text("Leave", style: .new(color: danger)), ), ), ], diff --git a/lib/widgets/settings/dialog_list_tile.dart b/lib/widgets/settings/dialog_list_tile.dart index b31182f..f884f31 100644 --- a/lib/widgets/settings/dialog_list_tile.dart +++ b/lib/widgets/settings/dialog_list_tile.dart @@ -42,7 +42,7 @@ final class const DialogListTile({ child: Chip( label: Text( value is T ? getName(value as T) : "None", - overflow: TextOverflow.ellipsis, + overflow: .ellipsis, style: onChanged == null || saving.active ? .new(color: Colors.grey) : null, diff --git a/lib/widgets/settings/radio_sheet.dart b/lib/widgets/settings/radio_sheet.dart index 9f9512e..5572d48 100644 --- a/lib/widgets/settings/radio_sheet.dart +++ b/lib/widgets/settings/radio_sheet.dart @@ -36,7 +36,7 @@ final class const RadioSheet({ onChanged: (value) => chosen.value = (value as T,), child: Column( - mainAxisSize: MainAxisSize.min, + mainAxisSize: .min, children: options .map( (option) => RadioListTile( diff --git a/lib/widgets/sync_status_banner.dart b/lib/widgets/sync_status_banner.dart index 3f1e47e..10aec7e 100644 --- a/lib/widgets/sync_status_banner.dart +++ b/lib/widgets/sync_status_banner.dart @@ -67,7 +67,7 @@ class const SyncStatusBanner({super.key}) extends ConsumerWidget { banner.title, maxLines: 1, overflow: .ellipsis, - style: TextStyle( + style: .new( color: banner.foreground, fontWeight: .bold, ),