treewide cleanups

This commit is contained in:
Henry Hiles 2026-10-23 20:26:37 -04:00
commit 853116ab2f
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
16 changed files with 88 additions and 138 deletions

View file

@ -243,17 +243,14 @@ class RoomChatController(final ChatParams info)
ref.invalidate(provider); ref.invalidate(provider);
} }
var taggedMessage = text; // Replaces each formatted tag, like "@id#name#", with its ID
final taggedMessage = tags.fold(
for (final tag in tags) { text,
final escaped = RegExp.escape(tag.id); (message, tag) => message.replaceAllMapped(
final pattern = RegExp(r"@+(" + escaped + r")(#[^#]*#)?"); RegExp(r"@+(" + RegExp.escape(tag.id) + r")(#[^#]*#)?"),
taggedMessage = taggedMessage.replaceAllMapped(
pattern,
(match) => match.group(1)!, (match) => match.group(1)!,
); ),
} );
final client = ref.read(ClientController.provider.notifier); final client = ref.read(ClientController.provider.notifier);
final event = await client.sendMessage( final event = await client.sendMessage(

View file

@ -25,55 +25,50 @@ class SpacesController extends AsyncNotifier<IList<Space>> {
MapEntry(spaceId, edges.map((edge) => edge.childId).toList()), MapEntry(spaceId, edges.map((edge) => edge.childId).toList()),
); );
Set<String> collectDescendants(String startId) { /// Everything under [spaceId], however deep, skipping any already
final visited = <String>{}; /// [visited] so cycles end.
final stack = [startId]; ISet<String> collectDescendants(
String spaceId, [
while (stack.isNotEmpty) { ISet<String> visited = const .empty(),
final current = stack.removeLast(); ]) => (childrenById[spaceId] ?? const []).fold(
final children = childrenById[current] ?? const []; visited,
(visited, childId) => visited.contains(childId)
for (final child in children) { ? visited
if (visited.add(child)) { : collectDescendants(childId, visited.add(childId)),
stack.add(child); );
}
}
}
return visited;
}
Space buildSpace(String spaceId) { Space buildSpace(String spaceId) {
final space = rooms[spaceId]; final space = rooms[spaceId];
final directChildrenIds = childrenById[spaceId] ?? const []; final directChildrenIds = childrenById[spaceId] ?? const [];
final directRooms = <Room>[];
final subSpaces = <Subspace>[];
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( return .new(
id: spaceId, id: spaceId,
room: space, room: space,
title: space?.metadata?.name ?? "Unnamed Space", title: space?.metadata?.name ?? "Unnamed Space",
children: .new(directRooms), children: .new(
subSpaces: .new(subSpaces), 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,
),
); );
} }

View file

@ -1,53 +1,48 @@
import "package:collection/collection.dart";
import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/client_state.dart"; import "package:nexus/controllers/client_state.dart";
import "package:nexus/models/content/content.dart"; import "package:nexus/models/content/content.dart";
import "package:nexus/models/content/membership.dart"; import "package:nexus/models/content/membership.dart";
import "package:nexus/models/content/power_levels.dart"; import "package:nexus/models/content/power_levels.dart";
import "package:nexus/models/event.dart";
import "package:nexus/models/room.dart"; import "package:nexus/models/room.dart";
class ViaController(final Room room) extends Notifier<String> { class ViaController(final Room room) extends Notifier<String> {
@override @override
String build() { String build() {
final servers = <String>{}; 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 joinedMembers =
final server = userId?.split(":").lastOrNull; (room.state[EventType.membership.type]?.values ?? const [])
if (server != null) { .map(
servers.add(server); (rowId) => switch (room.events[rowId]) {
} Event(
} stateKey: final userId?,
content: MembershipContent(status: .join),
) =>
userId,
_ => null,
},
)
.nonNulls
.toIList();
addUserId(ref.watch(ClientStateController.provider)?.userId); final servers = [?ref.watch(ClientStateController.provider)?.userId]
.toIList()
final powerLevelsEventId = room.state[EventType.powerLevels.type]?[""]; .addAll(powerLevelUsers)
final powerLevels = powerLevelsEventId == null .addAll(joinedMembers)
? null .map((userId) => userId.substring(userId.indexOf(":") + 1))
: room.events[powerLevelsEventId]; .whereNoDuplicates()
.take(5)
if (powerLevels?.content case PowerLevelsContent(:final users)) { .toISet();
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;
}
return servers.isEmpty return servers.isEmpty
? "" ? ""

View file

@ -147,7 +147,7 @@ class const App({super.key}) extends StatelessWidget {
.watch(SettingsController.provider) .watch(SettingsController.provider)
.maybeWhen( .maybeWhen(
data: (settings) => settings.theme, data: (settings) => settings.theme,
orElse: () => ThemeMode.system, orElse: () => .system,
), ),
home: child, home: child,
); );

View file

@ -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<String> aliases,
required final String description,
required final IList<String> tags,
}) with _$Emoji {
static Widget widgetFromJson(String emoji) => Text(emoji);
static String readWidgetValueFromJson(Map<dynamic, dynamic> json, _) =>
json["emoji"];
factory Emoji.fromJson(Map<String, Object?> json) => _$EmojiFromJson(json);
}

View file

@ -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<Emoji> emojis,
}) with _$EmojiCategory;

View file

@ -399,11 +399,11 @@ class const Composer(
if (!shiftPressed) { if (!shiftPressed) {
send(); send();
return KeyEventResult.handled; return .handled;
} }
} }
return KeyEventResult.ignored; return .ignored;
}, },
// Overrides the keyboard shortcut for pasting // Overrides the keyboard shortcut for pasting
child: Actions( child: Actions(

View file

@ -38,7 +38,7 @@ class const RelationPreview(
onPressed: toggleShouldMention, onPressed: toggleShouldMention,
child: Text( child: Text(
shouldMention ? "@On" : "@Off", shouldMention ? "@On" : "@Off",
style: TextStyle( style: .new(
fontWeight: .w900, fontWeight: .w900,
color: shouldMention ? null : Theme.of(context).disabledColor, color: shouldMention ? null : Theme.of(context).disabledColor,
), ),

View file

@ -76,7 +76,7 @@ class const Html(
src == null src == null
? SizedBox.shrink() ? SizedBox.shrink()
: InlineCustomWidget( : InlineCustomWidget(
alignment: PlaceholderAlignment.middle, alignment: .middle,
child: ExpandableImage( child: ExpandableImage(
.new(mxc: src), .new(mxc: src),
downloadFilename: src.pathSegments.last, downloadFilename: src.pathSegments.last,

View file

@ -49,7 +49,7 @@ class const MessageDisplayname(
final children => Row( final children => Row(
spacing: 4, spacing: 4,
mainAxisSize: .min, mainAxisSize: .min,
children: [for (final child in children) Flexible(child: child)], children: children.map((child) => Flexible(child: child)).toList(),
), ),
}, },
), ),

View file

@ -27,7 +27,7 @@ final class const MessageImage(
child: Image( child: Image(
image: MxcImage(ref, request), image: MxcImage(ref, request),
width: info?.width?.toDouble(), width: info?.width?.toDouble(),
fit: BoxFit.fitWidth, fit: .fitWidth,
loadingBuilder: (_, child, loadingProgress) => loadingBuilder: (_, child, loadingProgress) =>
loadingProgress == null loadingProgress == null
? child ? child

View file

@ -151,7 +151,7 @@ class const NotificationsList({super.key})
listController: listController.value, listController: listController.value,
controller: scrollController, controller: scrollController,
itemCount: value.length, itemCount: value.length,
padding: const EdgeInsets.symmetric( padding: const .symmetric(
horizontal: 8, horizontal: 8,
vertical: 12, vertical: 12,
), ),

View file

@ -146,7 +146,7 @@ final class const RoomMenuButton(
), ),
child: ListTile( child: ListTile(
leading: Icon(Icons.logout, color: danger), leading: Icon(Icons.logout, color: danger),
title: Text("Leave", style: TextStyle(color: danger)), title: Text("Leave", style: .new(color: danger)),
), ),
), ),
], ],

View file

@ -42,7 +42,7 @@ final class const DialogListTile<T>({
child: Chip( child: Chip(
label: Text( label: Text(
value is T ? getName(value as T) : "None", value is T ? getName(value as T) : "None",
overflow: TextOverflow.ellipsis, overflow: .ellipsis,
style: onChanged == null || saving.active style: onChanged == null || saving.active
? .new(color: Colors.grey) ? .new(color: Colors.grey)
: null, : null,

View file

@ -36,7 +36,7 @@ final class const RadioSheet<T>({
onChanged: (value) => chosen.value = (value as T,), onChanged: (value) => chosen.value = (value as T,),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: .min,
children: options children: options
.map( .map(
(option) => RadioListTile<T>( (option) => RadioListTile<T>(

View file

@ -67,7 +67,7 @@ class const SyncStatusBanner({super.key}) extends ConsumerWidget {
banner.title, banner.title,
maxLines: 1, maxLines: 1,
overflow: .ellipsis, overflow: .ellipsis,
style: TextStyle( style: .new(
color: banner.foreground, color: banner.foreground,
fontWeight: .bold, fontWeight: .bold,
), ),