treewide: use dot shorthands where possible

Now this feature is stable, we should use it. I might have missed some usecases, but these can be added in future commits.
This commit is contained in:
Henry Hiles 2026-06-02 11:53:14 -04:00
commit d2ec5f035b
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
67 changed files with 391 additions and 534 deletions

View file

@ -4,10 +4,10 @@ import "package:nexus/models/account_data.dart";
class AccountDataController extends Notifier<IMap<String, AccountData>> {
@override
IMap<String, AccountData> build() => const IMap.empty();
IMap<String, AccountData> build() => .new();
void update(IMap<String, AccountData> newData) =>
state = IMap({...state.unlock, ...newData.unlock});
state = .new({...state.unlock, ...newData.unlock});
static final provider =
NotifierProvider<AccountDataController, IMap<String, AccountData>>(

View file

@ -1,7 +1,6 @@
import "dart:async";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/user_controller.dart";
import "package:nexus/models/configs/user_config.dart";
import "package:nexus/models/content/membership.dart";
import "package:nexus/models/event.dart";
@ -13,11 +12,11 @@ class AuthorController extends AsyncNotifier<MembershipContent> {
Future<MembershipContent> build() async {
final member = await ref.watch(
UserController.provider(
UserConfig(roomId: event.roomId, userId: event.sender),
.new(roomId: event.roomId, userId: event.sender),
).future,
);
return MembershipContent(
return .new(
status: member.status,
avatarUrl: event.pmp?.avatarUrl ?? member.avatarUrl,
displayName: event.pmp?.displayName ?? member.displayName,

View file

@ -14,7 +14,6 @@ import "package:nexus/controllers/sync_status_controller.dart";
import "package:nexus/controllers/top_level_spaces_controller.dart";
import "package:nexus/helpers/extensions/gomuks_buffer.dart";
import "package:nexus/main.dart";
import "package:nexus/models/client_state.dart";
import "package:nexus/models/event.dart";
import "package:nexus/models/paginate.dart";
import "package:nexus/models/requests/get_event_request.dart";
@ -31,7 +30,6 @@ import "package:nexus/models/requests/send_message_request.dart";
import "package:nexus/models/requests/set_membership_request.dart";
import "package:nexus/models/room.dart";
import "package:nexus/models/sync_data.dart";
import "package:nexus/models/sync_status.dart";
import "package:nexus/src/third_party/gomuks.g.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:path_provider/path_provider.dart";
@ -66,12 +64,12 @@ class ClientController extends AsyncNotifier<int> {
case "client_state":
ref
.watch(ClientStateController.provider.notifier)
.set(ClientState.fromJson(decodedMuksEvent));
.set(.fromJson(decodedMuksEvent));
break;
case "sync_status":
ref
.watch(SyncStatusController.provider.notifier)
.set(SyncStatus.fromJson(decodedMuksEvent));
.set(.fromJson(decodedMuksEvent));
break;
case "init_complete":
ref.watch(InitCompleteController.provider.notifier).complete();
@ -81,12 +79,10 @@ class ClientController extends AsyncNotifier<int> {
ref
.watch(RoomsController.provider.notifier)
.update(
{
event.roomId: Room(
events: {event.rowId: event}.toIMap(),
),
}.toIMap(),
const ISet.empty(),
.new({
event.roomId: .new(events: .new({event.rowId: event})),
}),
.new(),
);
break;
@ -210,9 +206,11 @@ class ClientController extends AsyncNotifier<int> {
(await _sendCommand("get_room_state", request.toJson())) as List?;
final response = await getState(request);
return (response ?? await getState(request.copyWith(refetch: true)) ?? [])
.map((event) => Event.fromJson(event))
.toIList();
return .new(
(response ?? await getState(request.copyWith(refetch: true)) ?? []).map(
(event) => .fromJson(event),
),
);
}
Future<IList<Event>?> getRelatedEvents(
@ -220,20 +218,20 @@ class ClientController extends AsyncNotifier<int> {
) async {
final response =
(await _sendCommand("get_related_events", request.toJson())) as List?;
return response?.map((event) => Event.fromJson(event)).toIList();
return .new(response?.map((event) => .fromJson(event)));
}
Future<Event?> getEvent(GetEventRequest request) async {
final json = await _sendCommand("get_event", request.toJson());
return json == null ? null : Event.fromJson(json);
return json == null ? null : .fromJson(json);
}
Future<Paginate> paginate(PaginateRequest request) async =>
Paginate.fromJson(await _sendCommand("paginate", request.toJson()));
.fromJson(await _sendCommand("paginate", request.toJson()));
Future<Profile> getProfile(String userId) async {
final json = await _sendCommand("get_profile", {"user_id": userId});
return Profile.fromJsonWithCatch({...json, "id": userId});
return .fromJsonWithCatch({...json, "id": userId});
}
Future<void> reportEvent(ReportRequest request) =>

View file

@ -5,9 +5,7 @@ class ClientStateController extends Notifier<ClientState?> {
@override
Null build() => null;
void set(ClientState newState) {
state = newState;
}
void set(ClientState newState) => state = newState;
static final provider = NotifierProvider<ClientStateController, ClientState?>(
ClientStateController.new,

View file

@ -2,11 +2,8 @@ import "package:cross_cache/cross_cache.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
class CrossCacheController extends Notifier<CrossCache> {
static const String spaceKey = "space";
static const String roomKey = "room";
@override
CrossCache build() => CrossCache();
CrossCache build() => .new();
static final provider = NotifierProvider<CrossCacheController, CrossCache>(
CrossCacheController.new,

View file

@ -12,10 +12,7 @@ class EmojiController extends AsyncNotifier<EmojiTuple> {
@override
Future<EmojiTuple> build() async {
final response = await get(
Uri.https(
"github.com",
"github/gemoji/raw/refs/heads/master/db/emoji.json",
),
.https("github.com", "github/gemoji/raw/refs/heads/master/db/emoji.json"),
);
if (response.statusCode != 200) {
@ -30,19 +27,19 @@ class EmojiController extends AsyncNotifier<EmojiTuple> {
.toIList();
final categoryMap = entries.fold<IMap<String, IList<String>>>(
const IMap.empty(),
.new(),
(acc, entry) => acc.update(
entry.category,
(list) => list.add(entry.emoji),
ifAbsent: () => IList([entry.emoji]),
ifAbsent: () => .new([entry.emoji]),
),
);
final keywordMap = entries.fold<IMap<String, IList<String>>>(
const IMap.empty(),
.new(),
(acc, entry) => acc.add(
entry.emoji,
IList<String>([...entry.tags, ...entry.aliases, entry.description]),
.new([...entry.tags, ...entry.aliases, entry.description]),
),
);
@ -71,9 +68,9 @@ class EmojiController extends AsyncNotifier<EmojiTuple> {
);
final customKeywords = IMap(
Map.fromEntries(
.fromEntries(
keywordMap.entries.map(
(e) => MapEntry(e.key, e.value.toList(growable: false)),
(e) => .new(e.key, e.value.toList(growable: false)),
),
),
);

View file

@ -16,7 +16,7 @@ class MembersController extends AsyncNotifier<ISet<Event>> {
RoomsController.provider.select((value) => value[roomId]),
);
if (room == null) return const ISet.empty();
if (room == null) return .new();
if (!room.hasFetchedMembers) {
final fetchedState = await ref
@ -38,7 +38,7 @@ class MembersController extends AsyncNotifier<ISet<Event>> {
.map((rowId) => room.events[rowId])
.nonNulls
.toISet() ??
const ISet.empty();
.new();
}
static final provider = AsyncNotifierProvider.autoDispose

View file

@ -7,9 +7,8 @@ class MultiProviderController extends AsyncNotifier<void> {
final IList<AsyncNotifierProvider> providers;
@override
FutureOr<void> build() async => await Future.wait(
providers.map((provider) => ref.watch(provider.future)),
);
Future<void> build() =>
.wait(providers.map((provider) => ref.watch(provider.future)));
static final provider =
AsyncNotifierProvider.family<

View file

@ -4,7 +4,6 @@ import "package:nexus/controllers/rooms_controller.dart";
import "package:nexus/models/configs/power_level_config.dart";
import "package:nexus/models/content/content.dart";
import "package:nexus/models/content/power_levels.dart";
import "package:nexus/models/requests/membership_action.dart";
class PowerLevelController extends Notifier<bool> {
final PowerLevelConfig config;
@ -14,7 +13,7 @@ class PowerLevelController extends Notifier<bool> {
bool build() {
if (config case EventPowerLevelConfig(:final eventType)) {
assert(
eventType != EventType.redaction,
eventType != .redaction,
"Checking power level for a redaction should use [PowerLevelConfig.redaction].",
);
}
@ -29,6 +28,7 @@ class PowerLevelController extends Notifier<bool> {
final content = event?.content is PowerLevelsContent
? event!.content
: PowerLevelsContent();
final user = ref.watch(
ClientStateController.provider.select((value) => value?.userId),
);
@ -45,15 +45,15 @@ class PowerLevelController extends Notifier<bool> {
MembershipActionPowerLevelConfig(:final action, :final targetUser) =>
switch (action) {
MembershipAction.invite => userLevel >= content.invite,
.invite => userLevel >= content.invite,
MembershipAction.kick =>
.kick =>
userLevel >= content.kick && userLevel > powerLevelOf(targetUser),
MembershipAction.ban =>
.ban =>
userLevel >= content.ban && userLevel > powerLevelOf(targetUser),
MembershipAction.unban => userLevel >= content.ban,
.unban => userLevel >= content.ban,
},
StatePowerLevelConfig(:final eventType) =>

View file

@ -4,7 +4,6 @@ import "package:nexus/controllers/client_controller.dart";
import "package:nexus/controllers/rooms_controller.dart";
import "package:nexus/models/configs/reactions_config.dart";
import "package:nexus/models/content/reaction.dart";
import "package:nexus/models/requests/get_related_events_request.dart";
class ReactionsController extends AsyncNotifier<IMap<String, IList<String>>> {
final ReactionsConfig config;
@ -23,7 +22,7 @@ class ReactionsController extends AsyncNotifier<IMap<String, IList<String>>> {
? await ref
.watch(ClientController.provider.notifier)
.getRelatedEvents(
GetRelatedEventsRequest(
.new(
roomId: config.roomId,
eventId: eventInfo!.$1,
relationType: "m.annotation",
@ -33,18 +32,18 @@ class ReactionsController extends AsyncNotifier<IMap<String, IList<String>>> {
return reactionEvents
?.where((event) => event.redactedBy == null)
.fold<IMap<String, IList<String>>>(IMap(), (acc, event) {
.fold<IMap<String, IList<String>>>(.new(), (acc, event) {
if (event.content case ReactionContent(:final key?)) {
return acc.update(
key,
(list) => list.add(event.sender),
ifAbsent: () => IList([event.sender]),
ifAbsent: () => .new([event.sender]),
);
}
return acc;
}) ??
const IMap.empty();
.new();
}
static final provider =

View file

@ -9,12 +9,8 @@ import "package:nexus/controllers/rooms_controller.dart";
import "package:nexus/models/content/content.dart";
import "package:nexus/models/content/reaction.dart";
import "package:nexus/models/event.dart";
import "package:nexus/models/requests/get_related_events_request.dart";
import "package:nexus/models/requests/get_room_state_request.dart";
import "package:nexus/models/requests/paginate_request.dart";
import "package:nexus/models/requests/redact_event_request.dart";
import "package:nexus/models/relation_type.dart";
import "package:nexus/models/requests/send_event_request.dart";
import "package:nexus/models/requests/send_message_request.dart";
import "package:nexus/models/room.dart";
@ -28,12 +24,10 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
final room = ref.watch(
RoomsController.provider.select((rooms) => rooms[roomId]),
);
if (room == null) return const IList.empty();
if (room == null) return .new();
if (!room.hasFetchedState) {
final state = await client.getRoomState(
GetRoomStateRequest(roomId: roomId),
);
final state = await client.getRoomState(.new(roomId: roomId));
await ref.read(RoomsController.provider.notifier).addState(roomId, state);
}
@ -45,7 +39,7 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
}
return IMap<int, int?>.fromValues(
keyMapper: (id) => 9999999 + (id ?? 0),
keyMapper: (id) => 9999999999 + (id ?? 0),
values: room.sticky,
)
.addAll(room.timeline)
@ -86,7 +80,7 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
final response = await ref
.watch(ClientController.provider.notifier)
.paginate(
PaginateRequest(
.new(
roomId: roomId,
maxTimelineId: timelineKeys?.isNotEmpty == true
? timelineKeys?.reduce(min)
@ -112,7 +106,7 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
),
),
}),
const ISet.empty(),
.new(),
);
return response.hasMore;
@ -153,20 +147,20 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
text: taggedMessage,
relation: relation == null
? null
: Relation(eventId: relation.eventId, relationType: relationType),
: .new(eventId: relation.eventId, relationType: relationType),
),
);
ref
.watch(RoomsController.provider.notifier)
.update(
{
roomId: Room(
events: {event.rowId: event}.toIMap(),
sticky: {event.rowId}.toISet(),
.new({
roomId: .new(
events: .new({event.rowId: event}),
sticky: .new({event.rowId}),
),
}.toIMap(),
const ISet.empty(),
}),
.new(),
);
}
@ -177,7 +171,7 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
) async {
final client = ref.watch(ClientController.provider.notifier);
final allReactionEvents = await client.getRelatedEvents(
GetRelatedEventsRequest(
.new(
roomId: roomId,
eventId: event.eventId,
relationType: "m.annotation",
@ -199,9 +193,7 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
if (reactionEvent != null) {
await ref
.watch(ClientController.provider.notifier)
.redactEvent(
RedactEventRequest(eventId: reactionEvent.eventId, roomId: roomId),
);
.redactEvent(.new(eventId: reactionEvent.eventId, roomId: roomId));
}
}
@ -209,7 +201,7 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
final client = ref.watch(ClientController.provider.notifier);
await client.sendEvent(
SendEventRequest(
.new(
roomId: roomId,
type: EventType.reaction.type,
content: {

View file

@ -1,33 +1,29 @@
import "dart:isolate";
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/models/event.dart";
import "package:nexus/models/read_receipt.dart";
import "package:nexus/models/room.dart";
class RoomsController extends Notifier<IMap<String, Room>> {
@override
IMap<String, Room> build() => const IMap.empty();
IMap<String, Room> build() => .new();
Future<void> addState(
String roomId,
IList<Event> state, {
bool isMembers = false,
}) async => update(
{
.new({
roomId: Room(
events: IMap.fromEntries(
state.map((event) => MapEntry(event.rowId, event)),
),
events: .fromEntries(state.map((event) => .new(event.rowId, event))),
hasFetchedState: true,
hasFetchedMembers: isMembers,
state: await Isolate.run(() {
final newState = state.fold(
const IMap<String, IMap<String, int>>.empty(),
final newState = state.fold<IMap<String, IMap<String, int>>>(
.new(),
(previousValue, stateEvent) => previousValue.add(
stateEvent.type,
(previousValue[stateEvent.type] ?? const IMap.empty()).add(
(previousValue[stateEvent.type] ?? .new()).add(
stateEvent.stateKey!,
stateEvent.rowId,
),
@ -36,8 +32,8 @@ class RoomsController extends Notifier<IMap<String, Room>> {
return newState;
}),
),
}.toIMap(),
const ISet.empty(),
}),
.new(),
);
void update(IMap<String, Room> rooms, ISet<String> leftRooms) {
@ -65,9 +61,7 @@ class RoomsController extends Notifier<IMap<String, Room>> {
existing.state,
(previousValue, event) => previousValue.add(
event.key,
(previousValue[event.key] ?? const IMap.empty()).addAll(
event.value,
),
(previousValue[event.key] ?? .new()).addAll(event.value),
),
),
reset: false,
@ -82,9 +76,7 @@ class RoomsController extends Notifier<IMap<String, Room>> {
existing.receipts,
(receiptAcc, event) => receiptAcc.add(
event.key,
(receiptAcc[event.key] ?? IList<ReadReceipt>()).addAll(
event.value,
),
(receiptAcc[event.key] ?? .new()).addAll(event.value),
),
),
) ??

View file

@ -3,7 +3,7 @@ import "package:shared_preferences/shared_preferences.dart";
class SharedPrefsController extends AsyncNotifier<SharedPreferences> {
@override
Future<SharedPreferences> build() => SharedPreferences.getInstance();
Future<SharedPreferences> build() async => .getInstance();
static final provider =
AsyncNotifierProvider<SharedPrefsController, SharedPreferences>(

View file

@ -4,7 +4,7 @@ import "package:nexus/models/space_edge.dart";
class SpaceEdgesController extends Notifier<IMap<String, IList<SpaceEdge>>> {
@override
IMap<String, IList<SpaceEdge>> build() => const IMap.empty();
IMap<String, IList<SpaceEdge>> build() => .new();
void set(IMap<String, IList<SpaceEdge>> newEdges) =>
state = state.addAll(newEdges);

View file

@ -7,8 +7,6 @@ import "package:nexus/controllers/rooms_controller.dart";
import "package:nexus/controllers/top_level_spaces_controller.dart";
import "package:nexus/controllers/space_edges_controller.dart";
import "package:nexus/models/space.dart";
import "package:nexus/models/room.dart";
import "package:nexus/models/space_edge.dart";
class SpacesController extends Notifier<IList<Space>> {
@override
@ -20,15 +18,15 @@ class SpacesController extends Notifier<IList<Space>> {
final childRoomsBySpaceId = IMap.fromEntries(
topLevelSpaceIds.map((spaceId) {
ISet<String> walk(String currentId) {
final children = spaceEdges[currentId] ?? IList<SpaceEdge>();
final children = spaceEdges[currentId] ?? .new();
return children.fold<ISet<String>>(const ISet.empty(), (acc, edge) {
return children.fold<ISet<String>>(.new(), (acc, edge) {
final childId = edge.childId;
final isSpace = spaceEdges.containsKey(childId);
return acc
.addAll(!isSpace ? ISet([childId]) : const ISet.empty())
.addAll(isSpace ? walk(childId) : const ISet.empty());
.addAll(!isSpace ? ISet([childId]) : const .empty())
.addAll(isSpace ? walk(childId) : const .empty());
});
}
@ -88,7 +86,7 @@ class SpacesController extends Notifier<IList<Space>> {
final room = rooms[id];
if (room == null) return null;
final children = childRoomsBySpaceId[id] ?? IList<Room>();
final children = childRoomsBySpaceId[id] ?? .new();
return Space(
id: id,
title: room.metadata?.name ?? "Unnamed Room",
@ -100,13 +98,13 @@ class SpacesController extends Notifier<IList<Space>> {
.toIList();
return <Space>[
Space(
.new(
id: "home",
title: "Home",
icon: Icons.home,
children: homeRooms,
),
Space(
.new(
id: "dms",
title: "Direct Messages",
icon: Icons.people,
@ -116,7 +114,8 @@ class SpacesController extends Notifier<IList<Space>> {
]
.map(
(space) => space.copyWith(
children: space.children
children: .new(
space.children
.sortedBy(
(element) =>
element
@ -126,8 +125,8 @@ class SpacesController extends Notifier<IList<Space>> {
0,
)
.sortedBy((room) => room.metadata?.unreadMessages ?? 0)
.reversed
.toIList(),
.reversed,
),
),
)
.toIList();

View file

@ -7,7 +7,7 @@ class SyncStatusController extends Notifier<SyncStatus?> {
Null build() => null;
void set(SyncStatus newStatus) {
if (newStatus.type == SyncStatusType.permanentlyFailed) {
if (newStatus.type == .permanentlyFailed) {
showError(newStatus.error ?? "Syncing failed");
}
state = newStatus;

View file

@ -3,7 +3,7 @@ import "package:flutter_riverpod/flutter_riverpod.dart";
class TopLevelSpacesController extends Notifier<IList<String>> {
@override
IList<String> build() => const IList.empty();
IList<String> build() => .new();
void set(IList<String> newSpaces) => state = newSpaces;

View file

@ -19,7 +19,7 @@ class UrlPreviewController extends AsyncNotifier<OpenGraphData?> {
if (homeserver != null && !link.contains("matrix.to")) {
{
final response = await get(
Uri.parse(homeserver)
.parse(homeserver)
.resolve("/_matrix/client/v1/media/preview_url")
.replace(queryParameters: {"url": link}),
headers: await ref.watch(HeaderController.provider.future),
@ -34,7 +34,7 @@ class UrlPreviewController extends AsyncNotifier<OpenGraphData?> {
? null
: Uri.tryParse(mxc)?.mxcToHttps(homeserver);
return OpenGraphData.fromJson(decodedValue).copyWith(imageUrl: image);
return .fromJson(decodedValue).copyWith(imageUrl: image);
}
}
}

View file

@ -6,7 +6,6 @@ import "package:nexus/controllers/profile_controller.dart";
import "package:nexus/helpers/extensions/get_localpart.dart";
import "package:nexus/models/configs/user_config.dart";
import "package:nexus/models/content/membership.dart";
import "package:nexus/models/membership_status.dart";
class UserController extends AsyncNotifier<MembershipContent> {
final UserConfig config;
@ -31,8 +30,9 @@ class UserController extends AsyncNotifier<MembershipContent> {
final profile = await ref.watch(
ProfileController.provider(config.userId).future,
);
return MembershipContent(
status: MembershipStatus.leave,
return .new(
status: .leave,
avatarUrl: profile.avatarUrl,
displayName: profile.displayName ?? config.userId.localpart,
);

View file

@ -5,7 +5,6 @@ import "package:nexus/controllers/client_state_controller.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/membership_status.dart";
import "package:nexus/models/room.dart";
class ViaController extends Notifier<String> {
@ -45,7 +44,7 @@ class ViaController extends Notifier<String> {
: room.events[membershipEventId];
if (member?.content case MembershipContent(:final status)) {
if (status == MembershipStatus.join) {
if (status == .join) {
addUserId(member?.stateKey);
}
}

View file

@ -7,8 +7,8 @@ import "package:nexus/src/third_party/gomuks.g.dart";
extension GomuksOwnedBufferToX on GomuksOwnedBuffer {
Uint8List toBytes() {
try {
if (base == nullptr || length <= 0) return Uint8List(0);
return Uint8List.fromList(base.asTypedList(length));
if (base == nullptr || length <= 0) return .new(0);
return .fromList(base.asTypedList(length));
} finally {
calloc.free(base);
}

View file

@ -1,5 +1,4 @@
extension MxcToHttps on Uri {
Uri mxcToHttps(String homeserver) => Uri.parse(
homeserver,
).resolve("_matrix/client/v1/media/download/$host$path");
Uri mxcToHttps(String homeserver) =>
.parse(homeserver).resolve("_matrix/client/v1/media/download/$host$path");
}

View file

@ -1,16 +1,16 @@
import "package:flutter/material.dart";
extension SchemeToTheme on ColorScheme {
ThemeData get theme => ThemeData.from(colorScheme: this).copyWith(
cardTheme: CardThemeData(color: primaryContainer),
ThemeData get theme => .from(colorScheme: this).copyWith(
cardTheme: .new(color: primaryContainer),
popupMenuTheme: .new(
shape: RoundedRectangleBorder(borderRadius: .circular(16)),
color: surfaceContainerHigh,
),
appBarTheme: AppBarTheme(
titleSpacing: 0,
backgroundColor: surfaceContainerLow,
),
popupMenuTheme: PopupMenuThemeData(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
color: surfaceContainerHigh,
),
textTheme: ThemeData(
fontFamilyFallback: ["sans", "emoji"],
brightness: brightness,

View file

@ -9,8 +9,8 @@ extension ShowContextMenu on BuildContext {
showMenu(
context: this,
constraints: BoxConstraints.loose(Size.infinite),
position: RelativeRect.fromLTRB(
constraints: .loose(Size.infinite),
position: .fromLTRB(
globalPosition.dx,
globalPosition.dy,
overlay.size.width - globalPosition.dx,

View file

@ -14,9 +14,9 @@ extension ShowUserPopover on BuildContext {
children: [
PopupMenuItem(
enabled: false,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: .symmetric(horizontal: 16, vertical: 8),
child: IconTheme(
data: IconThemeData(),
data: .new(),
child: UserPopover(member, userId, roomId: roomId),
),
),

View file

@ -2,14 +2,7 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart";
extension SizeToString on int {
String get sizeAsString {
const IListConst<String> suffixes = IListConst([
"B",
"KB",
"MB",
"GB",
"TB",
"PB",
]);
const suffixes = IListConst(["B", "KB", "MB", "GB", "TB", "PB"]);
var i = 0;
var size = toDouble();

View file

@ -10,9 +10,7 @@ class LaunchHelper {
try {
return await ul.launchUrl(
url,
mode: useWebview
? ul.LaunchMode.inAppBrowserView
: ul.LaunchMode.externalApplication,
mode: useWebview ? .inAppBrowserView : .externalApplication,
);
} on PlatformException catch (_) {
return false;

View file

@ -34,7 +34,7 @@ class Content {
?.contentFromJson ??
Content.fromJson)(json);
} catch (error) {
if (error is Error) return Content(parseError: error);
if (error is Error) return .new(parseError: error);
rethrow;
}
}

View file

@ -35,7 +35,7 @@ class ChatPage extends ConsumerWidget {
)
: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [Loading(), Text("Syncing...")],
),
),

View file

@ -5,7 +5,6 @@ import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/client_controller.dart";
import "package:nexus/helpers/launch_helper.dart";
import "package:nexus/models/homeserver.dart";
import "package:nexus/models/requests/login_request.dart";
import "package:nexus/widgets/appbar.dart";
import "package:nexus/widgets/divider_text.dart";
import "package:nexus/widgets/loading.dart";
@ -36,7 +35,7 @@ class LoginPage extends HookConsumerWidget {
if (homeserver.value == null && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
.new(
content: Text(
"Homeserver verification failed. Is your homeserver down?",
style: TextStyle(color: theme.colorScheme.onErrorContainer),
@ -56,9 +55,9 @@ class LoginPage extends HookConsumerWidget {
appBar: Appbar(),
body: Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 600),
constraints: .new(maxWidth: 600),
child: ListView(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 64),
padding: .symmetric(horizontal: 16, vertical: 64),
children: [
Row(
children: [
@ -66,23 +65,20 @@ class LoginPage extends HookConsumerWidget {
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: .start,
children: [
Text("Nexus", style: theme.textTheme.displayMedium),
Text(
"A Simple Matrix Client",
style: theme.textTheme.headlineMedium,
overflow: TextOverflow.ellipsis,
overflow: .ellipsis,
),
],
),
),
],
),
Padding(
padding: EdgeInsetsGeometry.symmetric(vertical: 12),
child: Divider(),
),
Padding(padding: .symmetric(vertical: 12), child: Divider()),
DividerText("Enter a homeserver domain:"),
Row(
@ -91,7 +87,7 @@ class LoginPage extends HookConsumerWidget {
Expanded(
child: TextField(
controller: homeserverUrl,
decoration: InputDecoration(
decoration: .new(
labelText: "Homeserver URL (e.g. matrix.org)",
),
),
@ -100,7 +96,7 @@ class LoginPage extends HookConsumerWidget {
tooltip: "Confirm homeserver choice",
onPressed: isLoading.value
? null
: () => setHomeserver(Uri.tryParse(homeserverUrl.text)),
: () => setHomeserver(.tryParse(homeserverUrl.text)),
icon: Icon(Icons.check),
),
],
@ -108,26 +104,26 @@ class LoginPage extends HookConsumerWidget {
DividerText("Or, choose from some popular homeservers:"),
...(<Homeserver>[
Homeserver(
.new(
name: "Matrix.org",
description:
"The Matrix.org Foundation offers the matrix.org homeserver as an easy entry point for anyone wanting to try out Matrix.",
url: Uri.https("matrix.org"),
url: .https("matrix.org"),
iconUrl:
"https://raw.githubusercontent.com/element-hq/logos/refs/heads/master/matrix/matrix-favicon${Theme.brightnessOf(context) == Brightness.dark ? "-white" : ""}.png",
),
Homeserver(
.new(
name: "Federated Nexus",
description:
"Federated Nexus is a community resource hosting multiple FOSS (especially federated) services, including Matrix and Forgejo. By the same developers who made Nexus client.",
url: Uri.https("federated.nexus"),
url: .https("federated.nexus"),
iconUrl: "https://federated.nexus/images/icon.png",
),
Homeserver(
.new(
name: "Unredacted",
description:
"Unredacted is a 501(c)(3) non-profit organization that builds Internet infrastructure and services to help people evade censorship and protect their right to privacy.",
url: Uri.https("unredacted.org", "services/si/matrix"),
url: .https("unredacted.org", "services/si/matrix"),
iconUrl: "https://unredacted.org/favicon.ico",
),
].map(
@ -153,21 +149,21 @@ class LoginPage extends HookConsumerWidget {
)),
SizedBox(height: 8),
TextButton(
onPressed: () => launch(Uri.https("servers.joinmatrix.org")),
onPressed: () => launch(.https("servers.joinmatrix.org")),
child: Text("See more homeservers..."),
),
if (isLoading.value)
Padding(padding: EdgeInsets.only(top: 32), child: Loading())
Padding(padding: .only(top: 32), child: Loading())
else if (homeserver.value != null) ...[
DividerText("Then, sign in:"),
SizedBox(height: 4),
TextField(
decoration: InputDecoration(label: Text("Username")),
decoration: .new(label: Text("Username")),
controller: username,
),
SizedBox(height: 12),
TextField(
decoration: InputDecoration(label: Text("Password")),
decoration: .new(label: Text("Password")),
controller: password,
obscureText: true,
),
@ -176,7 +172,7 @@ class LoginPage extends HookConsumerWidget {
onPressed: () async {
isLoading.value = true;
final error = await client.login(
LoginRequest(
.new(
username: username.text,
password: password.text,
homeserverUrl: homeserver.value!,
@ -185,10 +181,10 @@ class LoginPage extends HookConsumerWidget {
if (error != null && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
.new(
content: Text(
"Login failed. Is your password right?\nError: $error",
style: TextStyle(
style: .new(
color: theme.colorScheme.onErrorContainer,
),
),

View file

@ -17,8 +17,8 @@ class VerifyPage extends HookConsumerWidget {
body: AlertDialog(
title: Text("Verify"),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: .min,
crossAxisAlignment: .start,
children: [
Text(
"Enter your recovery key or passphrase below to unlock encrypted events.\nYour passphrase is usually not the same as your password.",
@ -41,11 +41,11 @@ class VerifyPage extends HookConsumerWidget {
: () async {
final scaffoldMessenger = ScaffoldMessenger.of(context);
final snackbar = scaffoldMessenger.showSnackBar(
SnackBar(
.new(
content: Text(
"Attempting to verify with recovery key...",
),
duration: Duration(days: 999),
duration: .new(days: 999),
),
);
@ -60,13 +60,13 @@ class VerifyPage extends HookConsumerWidget {
isVerifying.value = false;
if (context.mounted) {
scaffoldMessenger.showSnackBar(
SnackBar(
.new(
backgroundColor: Theme.of(
context,
).colorScheme.errorContainer,
content: Text(
"Verification failed. Is your passphrase correct?\nError: $error",
style: TextStyle(
style: .new(
color: Theme.of(
context,
).colorScheme.onErrorContainer,

View file

@ -18,11 +18,11 @@ class Appbar extends StatelessWidget implements PreferredSizeWidget {
this.backgroundColor,
this.scrolledUnderElevation,
this.leading,
this.actions = const IList.empty(),
this.actions = const .empty(),
});
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
Size get preferredSize => const .fromHeight(kToolbarHeight);
@override
Widget build(BuildContext context) {
@ -42,7 +42,7 @@ class Appbar extends StatelessWidget implements PreferredSizeWidget {
leading: InkWell(onTap: onTap, child: leading),
backgroundColor: backgroundColor,
scrolledUnderElevation: scrolledUnderElevation,
actionsPadding: const EdgeInsets.symmetric(horizontal: 8),
actionsPadding: const .symmetric(horizontal: 8),
title: InkWell(
onTap: onTap,
child: IgnorePointer(child: title),

View file

@ -48,7 +48,7 @@ class AvatarOrHash extends ConsumerWidget {
smallSize: 12,
backgroundColor: Theme.of(context).colorScheme.primary,
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular((height - 8) / 2.5)),
borderRadius: .all(.circular((height - 8) / 2.5)),
child: SizedBox(
width: height,
height: height,
@ -60,7 +60,7 @@ class AvatarOrHash extends ConsumerWidget {
ref.watch(CrossCacheController.provider),
headers: ref.headers,
),
fit: BoxFit.cover,
fit: .cover,
loadingBuilder: (_, child, loadingProgress) =>
loadingProgress == null ? child : box,
errorBuilder: (_, _, _) => box,

View file

@ -4,8 +4,6 @@ import "package:flutter_hooks/flutter_hooks.dart";
import "package:fluttertagger/fluttertagger.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/power_level_controller.dart";
import "package:nexus/models/configs/power_level_config.dart";
import "package:nexus/models/content/content.dart";
import "package:nexus/models/event.dart";
import "package:nexus/models/relation_type.dart";
import "package:nexus/widgets/composer/mention_overlay.dart";
@ -42,7 +40,7 @@ class Composer extends HookConsumerWidget {
final shouldMention = useState(true);
final query = useState("");
if (relationType == RelationType.edit && controller.value.text.isEmpty) {
if (relationType == .edit && controller.value.text.isEmpty) {
controller.value.text = relatedEvent?.localContent?.editSource ?? "";
}
@ -51,7 +49,7 @@ class Composer extends HookConsumerWidget {
onSend(
controller.value.formattedText,
shouldMention: shouldMention.value,
tags: controller.value.tags.toIList(),
tags: .new(controller.value.tags),
);
onDismiss();
@ -60,13 +58,13 @@ class Composer extends HookConsumerWidget {
final style = TextStyle(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
fontWeight: .bold,
);
return Padding(
padding: EdgeInsetsGeometry.all(12),
padding: .all(12),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(12)),
borderRadius: .all(.circular(12)),
child: Column(
children: [
RelationPreview(
@ -79,17 +77,14 @@ class Composer extends HookConsumerWidget {
),
Container(
color: theme.colorScheme.surfaceContainerHighest,
padding: EdgeInsets.symmetric(horizontal: 8),
padding: .symmetric(horizontal: 8),
child: Row(
spacing: 8,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: .center,
children:
ref.watch(
PowerLevelController.provider(
PowerLevelConfig(
eventType: EventType.message,
roomId: roomId,
),
.new(eventType: .message, roomId: roomId),
),
)
? [
@ -124,7 +119,7 @@ class Composer extends HookConsumerWidget {
),
Expanded(
child: FlutterTagger(
triggerStrategy: TriggerStrategy.eager,
triggerStrategy: .eager,
overlay: MentionOverlay(
roomId,
query: query.value,
@ -144,16 +139,16 @@ class Composer extends HookConsumerWidget {
maxLines: 12,
minLines: 1,
autofocus: true,
decoration: InputDecoration(
decoration: .new(
hintText: "Your message here...",
border: InputBorder.none,
border: .none,
),
controller: controller.value,
key: key,
onSubmitted: (_) => send(),
// Don't defocus on submit
onEditingComplete: () {},
textInputAction: TextInputAction.done,
textInputAction: .done,
focusNode: node,
),
),
@ -167,10 +162,7 @@ class Composer extends HookConsumerWidget {
: [
Expanded(
child: Padding(
padding: EdgeInsetsGeometry.symmetric(
horizontal: 8,
vertical: 12,
),
padding: .symmetric(horizontal: 8, vertical: 12),
child: Text(
"You don't have permission to send messages in this room...",
),

View file

@ -5,9 +5,7 @@ import "package:nexus/controllers/rooms_controller.dart";
import "package:nexus/controllers/via_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/helpers/extensions/get_localpart.dart";
import "package:nexus/models/configs/members_by_status_config.dart";
import "package:nexus/models/content/membership.dart";
import "package:nexus/models/membership_status.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
import "package:nexus/widgets/loading.dart";
@ -29,21 +27,18 @@ class MentionOverlay extends ConsumerWidget {
final rooms = ref.watch(RoomsController.provider);
return Padding(
padding: EdgeInsets.all(8),
padding: .all(8),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(12)),
borderRadius: .all(.circular(12)),
child: Container(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
padding: EdgeInsets.all(8),
padding: .all(8),
child: switch (triggerCharacter) {
"@" =>
ref
.watch(
MembersByStatusController.provider(
MembersByStatusConfig(
roomId: roomId,
status: MembershipStatus.join,
),
.new(roomId: roomId, status: .join),
),
)
.betterWhen(

View file

@ -27,38 +27,34 @@ class RelationPreview extends ConsumerWidget {
return Container(
color: theme.colorScheme.surfaceContainerHigh,
padding: EdgeInsets.symmetric(horizontal: 12),
padding: .symmetric(horizontal: 12),
child: Row(
spacing: 8,
children: [
if (relationType == RelationType.edit)
Text(
"Editing message:",
style: TextStyle(fontWeight: FontWeight.bold),
),
if (relationType == .edit)
Text("Editing message:", style: .new(fontWeight: .bold)),
Expanded(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 8),
padding: .symmetric(vertical: 8),
child: EventPreview(relatedEvent!),
),
),
if (relationType == RelationType.reply)
if (relationType == .reply)
TextButton(
onPressed: toggleShouldMention,
child: Text(
shouldMention ? "@On" : "@Off",
style: TextStyle(
fontWeight: FontWeight.w900,
fontWeight: .w900,
color: shouldMention ? null : Theme.of(context).disabledColor,
),
),
),
IconButton(
tooltip:
"Cancel ${relationType == RelationType.edit ? "edit" : "reply"}",
tooltip: "Cancel ${relationType == .edit ? "edit" : "reply"}",
onPressed: onDismiss,
icon: const Icon(Icons.close),
iconSize: 20,

View file

@ -14,9 +14,9 @@ class DividerText extends StatelessWidget {
child: Divider(color: Theme.of(context).colorScheme.onSurface),
),
ConstrainedBox(
constraints: BoxConstraints(maxWidth: constraints.maxWidth - 32),
constraints: .new(maxWidth: constraints.maxWidth - 32),
child: Padding(
padding: const EdgeInsets.all(8),
padding: const .all(8),
child: Text(text, style: Theme.of(context).textTheme.labelLarge),
),
),

View file

@ -20,14 +20,14 @@ class EmojiPickerButton extends HookConsumerWidget {
Widget build(_, WidgetRef ref) => IconButton(
onPressed: () async {
onPressed?.call();
final controller = this.controller ?? TextEditingController();
final controller = this.controller ?? .new();
final emojis = await ref.watch(EmojiController.provider.future);
if (context.mounted) {
showModalBottomSheet(
context: context,
builder: (context) => EmojiKeyboardView(
config: EmojiViewConfig(
config: .new(
showRecentTab: false,
customCategories: emojis.$1.unlock,
customKeywords: emojis.$2.unlock,
@ -37,7 +37,8 @@ class EmojiPickerButton extends HookConsumerWidget {
textController: controller
..addListener(() {
// Without this, there will sometimes be a debugLocked is not true error sometimes
Future.delayed(Duration.zero, () {
// It might be preferable to use a microtask instead of a `Future.delayed`.
Future.delayed(.zero, () {
if (context.mounted) Navigator.of(context).pop();
});
onSelection?.call(controller.text);

View file

@ -12,16 +12,16 @@ class EventPreview extends StatelessWidget {
@override
Widget build(BuildContext context) => IgnorePointer(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 4),
padding: .symmetric(vertical: 4),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
spacing: 12,
children: [
if (event.content is MessageContent) MessageAvatar(event),
Flexible(
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
crossAxisAlignment: .center,
spacing: 8,
runSpacing: 2,
children: [

View file

@ -20,14 +20,12 @@ class ExpandableImage extends ConsumerWidget {
builder: (_) => LayoutBuilder(
builder: (context, constraints) => Dialog(
backgroundColor: Colors.transparent,
insetPadding: EdgeInsets.all(constraints.maxWidth / 100),
insetPadding: .all(constraints.maxWidth / 100),
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: min(constraints.maxWidth, 1000),
),
constraints: .new(minWidth: min(constraints.maxWidth, 1000)),
child: InteractiveViewer(
child: Image(
fit: BoxFit.contain,
fit: .contain,
errorBuilder: (_, error, stackTrace) => ErrorDialog(
"Loading failed for $source\nError: $error",
stackTrace,

View file

@ -15,11 +15,7 @@ class FileCard extends StatelessWidget {
color: Theme.of(context).colorScheme.surfaceContainer,
child: ListTile(
leading: Icon(Icons.file_copy),
title: Text(
filename ?? "file",
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
title: Text(filename ?? "file", maxLines: 1, overflow: .ellipsis),
subtitle: info?.size == null ? null : Text(info!.size!.sizeAsString),
// TODO: Downloading files
trailing: IconButton(onPressed: null, icon: Icon(Icons.download)),

View file

@ -7,13 +7,13 @@ class FlashWrapper extends StatelessWidget {
@override
Widget build(BuildContext context) => ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(12)),
borderRadius: .all(.circular(12)),
child: AnimatedContainer(
padding: isFlashing ? EdgeInsets.all(8) : EdgeInsets.all(0),
padding: isFlashing ? .all(8) : .all(0),
color: isFlashing
? Theme.of(context).colorScheme.onSurface.withAlpha(50)
: Colors.transparent,
duration: Duration(milliseconds: 250),
duration: .new(milliseconds: 250),
child: child,
),
);

View file

@ -55,14 +55,12 @@ class FormTextInput extends StatelessWidget {
maxLines: maxLines,
maxLength: maxLength,
inputFormatters: formatters,
textCapitalization: capitalize
? TextCapitalization.sentences
: TextCapitalization.none,
textCapitalization: capitalize ? .sentences : .none,
initialValue: initialValue,
autocorrect: autocorrect,
obscureText: obscure,
onTap: onTap,
decoration: InputDecoration(
decoration: .new(
labelText: title,
border: border ?? (outlined ? null : const UnderlineInputBorder()),
suffixIcon: trailing,

View file

@ -11,20 +11,20 @@ class CodeBlock extends StatelessWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
return ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(16)),
borderRadius: .all(.circular(16)),
child: ColoredBox(
color: theme.colorScheme.surfaceContainerHighest,
child: IntrinsicWidth(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: .spaceBetween,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 8),
padding: .symmetric(horizontal: 8),
child: Text(
lang.substring(0, min(lang.length, 15)),
style: TextStyle(fontFamily: "monospace"),
style: .new(fontFamily: "monospace"),
),
),
TextButton.icon(
@ -37,13 +37,13 @@ class CodeBlock extends StatelessWidget {
ColoredBox(
color: theme.colorScheme.surfaceContainerHigh,
child: Container(
constraints: BoxConstraints(minWidth: 250),
padding: EdgeInsets.all(8),
constraints: .new(minWidth: 250),
padding: .all(8),
child: SelectableText(
code,
minLines: 1,
maxLines: 99,
style: TextStyle(fontFamily: "monospace"),
style: .new(fontFamily: "monospace"),
),
),
),

View file

@ -86,9 +86,7 @@ class Html extends ConsumerWidget {
),
errorBuilder: (_, error, _) => Text(
"Image Failed to Load",
style: TextStyle(
color: Theme.of(context).colorScheme.error,
),
style: .new(color: Theme.of(context).colorScheme.error),
),
height: height.toDouble(),
width: width?.toDouble(),
@ -146,15 +144,14 @@ class Html extends ConsumerWidget {
element.attributes
.mapTo<MapEntry<String, String>?>(
(key, value) => switch (key) {
"data-mx-color" => MapEntry("color", value),
"data-mx-bg-color" => MapEntry("background-color", value),
"data-mx-color" => .new("color", value),
"data-mx-bg-color" => .new("background-color", value),
_ => null,
},
)
.nonNulls,
),
},
onTapUrl: (url) =>
ref.watch(LaunchHelper.provider).launchUrl(Uri.parse(url)),
onTapUrl: (url) => ref.watch(LaunchHelper.provider).launchUrl(.parse(url)),
);
}

View file

@ -3,7 +3,6 @@ import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/user_controller.dart";
import "package:nexus/helpers/extensions/link_to_mention.dart";
import "package:nexus/helpers/extensions/show_user_popover.dart";
import "package:nexus/models/configs/user_config.dart";
class MentionChip extends ConsumerWidget {
final String? roomId;
@ -16,9 +15,7 @@ class MentionChip extends ConsumerWidget {
final membership = mention?.startsWith("@") == true
? ref
.watch(
UserController.provider(
UserConfig(roomId: roomId, userId: mention!),
),
UserController.provider(.new(roomId: roomId, userId: mention!)),
)
.whenOrNull(data: (data) => data)
: null;
@ -41,8 +38,8 @@ class MentionChip extends ConsumerWidget {
label: Text(
(membership == null ? null : "@${membership.displayName}") ??
mention,
style: TextStyle(
fontWeight: FontWeight.bold,
style: .new(
fontWeight: .bold,
color: Theme.of(context).colorScheme.onPrimary,
),
),

View file

@ -8,9 +8,9 @@ class Quoted extends StatelessWidget {
Widget build(BuildContext context) => Container(
decoration: BoxDecoration(
border: Border(
left: BorderSide(width: 4, color: Theme.of(context).dividerColor),
left: .new(width: 4, color: Theme.of(context).dividerColor),
),
),
child: Padding(padding: EdgeInsets.only(left: 8), child: child),
child: Padding(padding: .only(left: 8), child: child),
);
}

View file

@ -13,15 +13,15 @@ class SpoilerText extends HookWidget {
return InkWell(
onTap: () => revealed.value = !revealed.value,
child: AnimatedContainer(
duration: const Duration(milliseconds: 100),
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
duration: const .new(milliseconds: 100),
padding: const .symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
color: revealed.value ? Colors.transparent : Colors.blueGrey,
borderRadius: BorderRadius.circular(4),
borderRadius: .circular(4),
),
child: Text(
text,
style: TextStyle(color: revealed.value ? null : Colors.transparent),
style: .new(color: revealed.value ? null : Colors.transparent),
),
),
);

View file

@ -1,5 +1,4 @@
import "package:collection/collection.dart";
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart";
import "package:flutter_hooks/flutter_hooks.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
@ -7,7 +6,6 @@ import "package:nexus/controllers/client_controller.dart";
import "package:nexus/controllers/key_controller.dart";
import "package:nexus/controllers/spaces_controller.dart";
import "package:nexus/helpers/extensions/link_to_mention.dart";
import "package:nexus/models/requests/join_room_request.dart";
import "package:nexus/widgets/form_text_input.dart";
class JoinDialog extends HookWidget {
@ -20,8 +18,8 @@ class JoinDialog extends HookWidget {
return AlertDialog(
title: Text("Join a Room"),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: .min,
crossAxisAlignment: .start,
children: [
Text("Enter the room alias, Matrix URI, or Matrix.to link."),
SizedBox(height: 12),
@ -45,7 +43,7 @@ class JoinDialog extends HookWidget {
final scaffoldMessenger = ScaffoldMessenger.of(context);
final snackbar = scaffoldMessenger.showSnackBar(
SnackBar(
.new(
content: Text("Joining room $roomIdOrAlias."),
duration: Duration(days: 999),
),
@ -55,9 +53,9 @@ class JoinDialog extends HookWidget {
final id = await ref
.watch(ClientController.provider.notifier)
.joinRoom(
JoinRoomRequest(
.new(
roomIdOrAlias: roomIdOrAlias,
via: IList(
via: .new(
Uri.tryParse(
roomAlias.text.replaceAll("/#", ""),
)?.queryParametersAll["via"] ??
@ -69,9 +67,9 @@ class JoinDialog extends HookWidget {
snackbar.close();
scaffoldMessenger.showSnackBar(
SnackBar(
.new(
content: Text("Room $roomIdOrAlias successfully joined."),
action: SnackBarAction(
action: .new(
label: "Open",
onPressed: () async {
final spaces = ref.watch(SpacesController.provider);
@ -113,13 +111,13 @@ class JoinDialog extends HookWidget {
snackbar.close();
if (context.mounted) {
scaffoldMessenger.showSnackBar(
SnackBar(
.new(
backgroundColor: Theme.of(
context,
).colorScheme.errorContainer,
content: Text(
error.toString(),
style: TextStyle(
style: .new(
color: Theme.of(context).colorScheme.onErrorContainer,
),
),

View file

@ -18,8 +18,9 @@ class MessageDisplayname extends ConsumerWidget {
});
@override
Widget build(BuildContext context, WidgetRef ref) =>
switch (ref.watch(AuthorController.provider(event))) {
Widget build(BuildContext context, WidgetRef ref) => switch (ref.watch(
AuthorController.provider(event),
)) {
AsyncData(:final value) || AsyncLoading(:final value?) => InkWell(
onTapUp: clickable
? (details) => context.showUserPopover(
@ -31,18 +32,14 @@ class MessageDisplayname extends ConsumerWidget {
: null,
child: Wrap(
spacing: 4,
crossAxisAlignment: WrapCrossAlignment.center,
crossAxisAlignment: .center,
children: [
Text(
value.displayName ?? event.sender.localpart,
style:
style ??
TextStyle(
color: event.sender.colorHash,
fontWeight: FontWeight.bold,
),
style ?? .new(color: event.sender.colorHash, fontWeight: .bold),
maxLines: 1,
overflow: TextOverflow.ellipsis,
overflow: .ellipsis,
),
if (event.pmp != null)
@ -50,20 +47,17 @@ class MessageDisplayname extends ConsumerWidget {
"(via ${event.sender})",
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: event.sender.colorHash,
fontWeight: FontWeight.bold,
fontWeight: .bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
overflow: .ellipsis,
),
],
),
),
_ => Text(
event.sender.localpart,
style: TextStyle(
color: event.sender.colorHash,
fontWeight: FontWeight.bold,
),
style: .new(color: event.sender.colorHash, fontWeight: .bold),
),
};
}

View file

@ -14,10 +14,10 @@ class LinkifiedText extends ConsumerWidget {
text: text,
maxLines: maxLines,
style: style,
options: LinkifyOptions(humanize: false),
options: .new(humanize: false),
onOpen: (link) =>
ref.watch(LaunchHelper.provider).launchUrl(Uri.parse(link.url)),
linkStyle: TextStyle(color: Theme.of(context).colorScheme.primary),
overflow: maxLines == null ? null : TextOverflow.ellipsis,
ref.watch(LaunchHelper.provider).launchUrl(.parse(link.url)),
linkStyle: .new(color: Theme.of(context).colorScheme.primary),
overflow: maxLines == null ? null : .ellipsis,
);
}

View file

@ -7,7 +7,7 @@ class Loading extends StatelessWidget {
@override
Widget build(BuildContext context) => Center(
child: Padding(
padding: EdgeInsets.all(16),
padding: .all(16),
child: SizedBox(height: height, child: CircularProgressIndicator()),
),
);

View file

@ -5,7 +5,6 @@ import "package:nexus/controllers/members_by_status_controller.dart";
import "package:nexus/helpers/extensions/get_localpart.dart";
import "package:nexus/helpers/extensions/show_user_popover.dart";
import "package:nexus/helpers/extensions/string_to_color.dart";
import "package:nexus/models/configs/members_by_status_config.dart";
import "package:nexus/models/content/membership.dart";
import "package:nexus/models/membership_status.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
@ -21,7 +20,7 @@ class MemberList extends HookConsumerWidget {
final status = useState(MembershipStatus.join);
final membersProvider = ref.watch(
MembersByStatusController.provider(
MembersByStatusConfig(roomId: roomId, status: status.value),
.new(roomId: roomId, status: status.value),
),
);
@ -33,7 +32,7 @@ class MemberList extends HookConsumerWidget {
scrolledUnderElevation: 0,
leading: Icon(Icons.people),
title: Text("Members"),
actionsPadding: EdgeInsets.only(right: 4),
actionsPadding: .only(right: 4),
actions: [
if (Scaffold.of(context).hasEndDrawer)
IconButton(
@ -44,24 +43,24 @@ class MemberList extends HookConsumerWidget {
],
),
Wrap(
alignment: WrapAlignment.center,
alignment: .center,
spacing: 8,
runSpacing: 8,
children: [
FilterChip(
label: Text("Joined"),
onSelected: (value) => status.value = MembershipStatus.join,
selected: status.value == MembershipStatus.join,
onSelected: (value) => status.value = .join,
selected: status.value == .join,
),
FilterChip(
label: Text("Invited"),
onSelected: (value) => status.value = MembershipStatus.invite,
selected: status.value == MembershipStatus.invite,
onSelected: (value) => status.value = .invite,
selected: status.value == .invite,
),
FilterChip(
label: Text("Banned"),
onSelected: (value) => status.value = MembershipStatus.ban,
selected: status.value == MembershipStatus.ban,
onSelected: (value) => status.value = .ban,
selected: status.value == .ban,
),
],
),
@ -93,15 +92,15 @@ class MemberList extends HookConsumerWidget {
),
title: Text(
displayName ?? member.stateKey!.localpart,
overflow: TextOverflow.ellipsis,
style: TextStyle(
overflow: .ellipsis,
style: .new(
color: member.stateKey!.colorHash,
fontWeight: FontWeight.bold,
fontWeight: .bold,
),
),
subtitle: Text(
member.stateKey!,
overflow: TextOverflow.ellipsis,
overflow: .ellipsis,
),
),
),

View file

@ -17,7 +17,7 @@ class MessageImage extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) => ExpandableImage(
url.toString(),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(8)),
borderRadius: .all(.circular(8)),
child: Image(
image: CachedNetworkImage(
url.toString(),
@ -47,7 +47,7 @@ class MessageImage extends ConsumerWidget {
errorBuilder: (context, error, stackTrace) => Center(
child: Text(
"Image Failed to Load",
style: TextStyle(color: Theme.of(context).colorScheme.error),
style: .new(color: Theme.of(context).colorScheme.error),
),
),
),

View file

@ -16,9 +16,7 @@ class AudioPlayer extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final player = useMemoized(
() => Player(
configuration: PlayerConfiguration(bufferSize: 128 * 1024 * 1024),
),
() => Player(configuration: .new(bufferSize: 128 * 1024 * 1024)),
);
final playing = useState(false);
@ -66,7 +64,7 @@ class AudioPlayer extends HookConsumerWidget {
child: Card(
color: Theme.of(context).colorScheme.surfaceContainer,
child: Padding(
padding: EdgeInsetsGeometry.only(left: 8, right: 16),
padding: .only(left: 8, right: 16),
child: Row(
children: [
IconButton(
@ -88,7 +86,7 @@ class AudioPlayer extends HookConsumerWidget {
: duration.value.inMilliseconds.toDouble(),
value: position.value.inMilliseconds.toDouble(),
onChanged: (value) =>
player.seek(Duration(milliseconds: value.toInt())),
player.seek(.new(milliseconds: value.toInt())),
),
),
Text(

View file

@ -16,9 +16,7 @@ class VideoPlayer extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final player = useMemoized(
() => Player(
configuration: PlayerConfiguration(bufferSize: 128 * 1024 * 1024),
),
() => Player(configuration: .new(bufferSize: 128 * 1024 * 1024)),
);
final controller = useMemoized(() => VideoController(player));

View file

@ -8,7 +8,6 @@ import "package:nexus/controllers/reactions_controller.dart";
import "package:nexus/controllers/room_chat_controller.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/helpers/extensions/mxc_to_https.dart";
import "package:nexus/models/configs/reactions_config.dart";
import "package:nexus/models/event.dart";
import "package:nexus/widgets/error_dialog.dart";
import "package:nexus/main.dart";
@ -24,7 +23,7 @@ class ReactionRow extends ConsumerWidget {
return switch (ref.watch(
ReactionsController.provider(
ReactionsConfig(roomId: event.roomId, eventRowId: event.rowId),
.new(roomId: event.roomId, eventRowId: event.rowId),
),
)) {
AsyncData(value: final IMap<String, IList<String>>? reactors) ||
@ -47,7 +46,7 @@ class ReactionRow extends ConsumerWidget {
showCheckmark: false,
selected: selected,
label: Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
spacing: 8,
children: [
Flexible(
@ -64,15 +63,9 @@ class ReactionRow extends ConsumerWidget {
ref.watch(CrossCacheController.provider),
),
)
: Text(
reaction,
overflow: TextOverflow.ellipsis,
),
),
Text(
count.toString(),
overflow: TextOverflow.ellipsis,
: Text(reaction, overflow: .ellipsis),
),
Text(count.toString(), overflow: .ellipsis),
],
),
onSelected: enabled.value

View file

@ -64,12 +64,11 @@ class EventRenderer extends ConsumerWidget {
textOnly: textOnly,
),
MembershipContent content =>
event.previousContent is MembershipContent &&
(event.previousContent as MembershipContent).status ==
content.status
? null
: MembershipRenderer(event),
MembershipContent content => switch (event.previousContent) {
MembershipContent(:final status) =>
status == content.status ? null : MembershipRenderer(event),
_ => null,
},
AvatarContent() => GenericEventRenderer(Icons.interests, [
MessageDisplayname(event),
@ -101,10 +100,10 @@ class EventRenderer extends ConsumerWidget {
MessageDisplayname(event),
Text(
"changed the room's history visibility to ${switch (historyVisibility) {
HistoryVisibility.invited => "since invited",
HistoryVisibility.joined => "since joined",
HistoryVisibility.shared => "all history visible (shared)",
HistoryVisibility.worldReadable => "all history visible (world readable)",
.invited => "since invited",
.joined => "since joined",
.shared => "all history visible (shared)",
.worldReadable => "all history visible (world readable)",
}}",
),
]),
@ -158,7 +157,7 @@ class EventRenderer extends ConsumerWidget {
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: .start,
children: [
if (child != null) ...[
if (textOnly)
@ -168,7 +167,7 @@ class EventRenderer extends ConsumerWidget {
onSecondaryTapUp: contextMenuCallback,
onLongPressStart: contextMenuCallback,
child: Padding(
padding: isGrouped ? EdgeInsets.zero : EdgeInsets.only(top: 8),
padding: isGrouped ? .zero : .only(top: 8),
child: child,
),
),
@ -183,12 +182,7 @@ class EventRenderer extends ConsumerWidget {
color: theme.colorScheme.error,
),
),
].map(
(child) => Padding(
padding: EdgeInsetsGeometry.only(left: 4),
child: child,
),
),
].map((child) => Padding(padding: .only(left: 4), child: child)),
],
] else if (textOnly)
Text("Unknown event type", style: errorStyle),

View file

@ -7,14 +7,11 @@ class GenericEventRenderer extends StatelessWidget {
@override
Widget build(BuildContext context) => Padding(
padding: EdgeInsets.only(bottom: 8),
padding: .only(bottom: 8),
child: Row(
spacing: 8,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 4),
child: Icon(icon),
),
Padding(padding: .symmetric(horizontal: 4), child: Icon(icon)),
Expanded(child: Wrap(spacing: 4, children: children)),
],
),

View file

@ -4,7 +4,6 @@ import "package:nexus/helpers/extensions/show_user_popover.dart";
import "package:nexus/helpers/extensions/string_to_color.dart";
import "package:nexus/models/content/membership.dart";
import "package:nexus/models/event.dart";
import "package:nexus/models/membership_status.dart";
import "package:nexus/widgets/lazy_loading/message_displayname.dart";
import "package:nexus/widgets/renderers/generic_event.dart";
@ -29,24 +28,21 @@ class MembershipRenderer extends StatelessWidget {
globalPosition: details.globalPosition,
),
child: Text(
overflow: TextOverflow.ellipsis,
overflow: .ellipsis,
content.displayName ?? event.stateKey!.localpart,
maxLines: 1,
style: TextStyle(
color: event.sender.colorHash,
fontWeight: FontWeight.bold,
),
style: .new(color: event.sender.colorHash, fontWeight: .bold),
),
),
Text(
overflow: TextOverflow.ellipsis,
overflow: .ellipsis,
maxLines: 1,
"${switch (content.status) {
MembershipStatus.invite => "was invited to",
MembershipStatus.join => "joined",
MembershipStatus.leave => event.sender == event.stateKey ? "left" : (event.unsigned["prev_content"]?["membership"] == "ban" ? "was unbanned from" : "was kicked from"),
MembershipStatus.ban => "was banned from",
MembershipStatus.knock => "asked to join",
.invite => "was invited to",
.join => "joined",
.leave => event.sender == event.stateKey ? "left" : (event.unsigned["prev_content"]?["membership"] == "ban" ? "was unbanned from" : "was kicked from"),
.ban => "was banned from",
.knock => "asked to join",
}} the room${event.sender == event.stateKey ? "" : " by "}",
),
if (event.sender != event.stateKey) MessageDisplayname(event),

View file

@ -9,7 +9,6 @@ import "package:nexus/models/content/encrypted.dart";
import "package:nexus/models/content/message.dart";
import "package:nexus/models/content/sticker.dart";
import "package:nexus/models/event.dart";
import "package:nexus/models/requests/get_event_request.dart";
import "package:nexus/widgets/file_card.dart";
import "package:nexus/widgets/html/html.dart";
import "package:nexus/widgets/lazy_loading/message_avatar.dart";
@ -49,19 +48,19 @@ class MessageRenderer extends ConsumerWidget {
child: Text(
format(event.timestamp),
maxLines: 1,
overflow: TextOverflow.ellipsis,
overflow: .ellipsis,
style: theme.textTheme.labelSmall?.copyWith(color: Colors.grey),
),
);
final textStyle = TextStyle(
fontSize: event.localContent?.bigEmoji == true ? 32 : null,
fontStyle: event.content is EmoteMessageContent ? FontStyle.italic : null,
fontStyle: event.content is EmoteMessageContent ? .italic : null,
);
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: .start,
mainAxisSize: .min,
spacing: 8,
children: [
if (!textOnly)
@ -72,7 +71,7 @@ class MessageRenderer extends ConsumerWidget {
Flexible(
child: Column(
spacing: 4,
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: .start,
children: [
if (!isGrouped && !textOnly)
Row(
@ -83,7 +82,7 @@ class MessageRenderer extends ConsumerWidget {
],
),
Card(
margin: textOnly ? EdgeInsets.zero : EdgeInsets.only(bottom: 4),
margin: textOnly ? .zero : .only(bottom: 4),
color: textOnly
? Colors.transparent
: ref.watch(
@ -99,24 +98,21 @@ class MessageRenderer extends ConsumerWidget {
elevation: textOnly ? 0 : null,
child: Padding(
padding: textOnly ? EdgeInsets.zero : EdgeInsets.all(12),
padding: textOnly ? .zero : .all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: .start,
children: [
if (!textOnly && event.replyTo != null)
Card(
margin: EdgeInsets.only(bottom: 8),
margin: .only(bottom: 8),
color: theme.colorScheme.surfaceContainerHigh,
child: InkWell(
onTap: onTapReply,
child: Padding(
padding: EdgeInsetsGeometry.symmetric(
vertical: 8,
horizontal: 12,
),
padding: .symmetric(vertical: 8, horizontal: 12),
child: switch (ref.watch(
EventController.provider(
GetEventRequest(
.new(
roomId: event.roomId,
eventId: event.replyTo!,
),
@ -145,12 +141,10 @@ class MessageRenderer extends ConsumerWidget {
? Text(
body,
maxLines: maxLines,
overflow: TextOverflow.ellipsis,
overflow: .ellipsis,
)
: ConstrainedBox(
constraints: BoxConstraints.loose(
Size.square(200),
),
constraints: .loose(.square(200)),
child: MessageImage(
url.mxcToHttps(
ref.watch(
@ -199,9 +193,9 @@ class MessageRenderer extends ConsumerWidget {
:final formattedBody,
:final format,
) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: .start,
children: [
format == MessageFormat.html && !textOnly
format == .html && !textOnly
? Html(
roomId: event.roomId,
textStyle: textStyle,
@ -243,9 +237,7 @@ class MessageRenderer extends ConsumerWidget {
),
)) {
final url? => ConstrainedBox(
constraints: BoxConstraints.loose(
Size.square(500),
),
constraints: .loose(.square(500)),
child: switch (event.content) {
VideoMessageContent(:final info) =>
VideoPlayer(url, info),
@ -286,7 +278,7 @@ class MessageRenderer extends ConsumerWidget {
),
MessageContent(:final body) => Row(
spacing: 8,
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
Text("Unknown message type:", style: errorStyle),
Text(body),

View file

@ -38,17 +38,17 @@ class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget {
: () => showDialog(
context: context,
builder: (context) => Dialog(
constraints: BoxConstraints.loose(Size.fromWidth(400)),
constraints: .loose(.fromWidth(400)),
child: Padding(
padding: EdgeInsetsGeometry.all(24),
padding: .all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: .min,
crossAxisAlignment: .start,
spacing: 8,
children: [
Row(
spacing: 12,
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
if (room.metadata?.avatar != null)
ExpandableImage(
@ -71,7 +71,7 @@ class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget {
Expanded(
child: Text(
room.metadata?.name ?? "Unnamed Room",
overflow: TextOverflow.ellipsis,
overflow: .ellipsis,
maxLines: 3,
style: Theme.of(context).textTheme.headlineSmall,
),
@ -105,18 +105,18 @@ class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget {
title: room == null
? null
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: .start,
children: [
Text(
room.metadata?.name ?? "Unnamed Room",
overflow: TextOverflow.ellipsis,
overflow: .ellipsis,
maxLines: 1,
),
if (room.metadata?.topic?.isNotEmpty == true)
Text(
room.metadata!.topic!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
overflow: .ellipsis,
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),

View file

@ -12,11 +12,9 @@ import "package:nexus/controllers/rooms_controller.dart";
import "package:nexus/controllers/room_chat_controller.dart";
import "package:nexus/controllers/via_controller.dart";
import "package:nexus/models/configs/power_level_config.dart";
import "package:nexus/models/content/content.dart";
import "package:nexus/models/content/message.dart";
import "package:nexus/models/event.dart";
import "package:nexus/models/relation_type.dart";
import "package:nexus/models/requests/report_request.dart";
import "package:nexus/widgets/composer/composer.dart";
import "package:nexus/widgets/emoji_picker_button.dart";
import "package:nexus/widgets/renderers/event.dart";
@ -102,8 +100,7 @@ class RoomChat extends HookConsumerWidget {
final composerNode = useFocusNode(
onKeyEvent: (_, event) {
if (event is KeyDownEvent &&
event.logicalKey == LogicalKeyboardKey.escape) {
if (event is KeyDownEvent && event.logicalKey == .escape) {
relatedEvent.value = null;
return KeyEventResult.handled;
}
@ -118,7 +115,7 @@ class RoomChat extends HookConsumerWidget {
return [
if (ref.watch(
PowerLevelController.provider(
PowerLevelConfig(eventType: EventType.reaction, roomId: roomId),
.new(eventType: .reaction, roomId: roomId),
),
))
PopupMenuItem(
@ -134,7 +131,7 @@ class RoomChat extends HookConsumerWidget {
value["m.recent_emoji"]
?.content["recent_emoji"] ??
[],
).map((entry) => entry["emoji"]),
).map((entry) => entry["emoji"]).toIList(),
),
),
"👍",
@ -167,13 +164,13 @@ class RoomChat extends HookConsumerWidget {
),
if (ref.watch(
PowerLevelController.provider(
PowerLevelConfig(eventType: EventType.message, roomId: roomId),
PowerLevelConfig(eventType: .message, roomId: roomId),
),
))
PopupMenuItem(
onTap: () {
relatedEvent.value = event;
relationType.value = RelationType.reply;
relationType.value = .reply;
composerNode.requestFocus();
},
child: ListTile(leading: Icon(Icons.reply), title: Text("Reply")),
@ -182,7 +179,7 @@ class RoomChat extends HookConsumerWidget {
PopupMenuItem(
onTap: () {
relatedEvent.value = event;
relationType.value = RelationType.edit;
relationType.value = .edit;
composerNode.requestFocus();
},
child: ListTile(leading: Icon(Icons.edit), title: Text("Edit")),
@ -207,10 +204,7 @@ class RoomChat extends HookConsumerWidget {
),
if (ref.watch(
PowerLevelController.provider(
PowerLevelConfig.redaction(
targetUser: event.sender,
roomId: roomId,
),
.redaction(targetUser: event.sender, roomId: roomId),
),
))
PopupMenuItem(
@ -222,8 +216,8 @@ class RoomChat extends HookConsumerWidget {
return AlertDialog(
title: Text("Delete Message"),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: .min,
crossAxisAlignment: .start,
children: [
Text(
"Are you sure you want to delete this message? This can not be reversed.",
@ -261,7 +255,7 @@ class RoomChat extends HookConsumerWidget {
),
child: ListTile(
leading: Icon(Icons.delete, color: danger),
title: Text("Delete", style: TextStyle(color: danger)),
title: Text("Delete", style: .new(color: danger)),
),
),
PopupMenuItem(
@ -273,8 +267,8 @@ class RoomChat extends HookConsumerWidget {
return AlertDialog(
title: Text("Report"),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: .min,
crossAxisAlignment: .start,
children: [
Text(
"Report this event to your server administrators, who can take action like banning this server or room.",
@ -297,7 +291,7 @@ class RoomChat extends HookConsumerWidget {
TextButton(
onPressed: () {
client.reportEvent(
ReportRequest(
.new(
roomId: roomId,
eventId: event.eventId,
reason: reasonController.text.isEmpty
@ -316,7 +310,7 @@ class RoomChat extends HookConsumerWidget {
),
child: ListTile(
leading: Icon(Icons.report, color: danger),
title: Text("Report", style: TextStyle(color: danger)),
title: Text("Report", style: .new(color: danger)),
),
),
].toIList();
@ -341,7 +335,7 @@ class RoomChat extends HookConsumerWidget {
children: [
Positioned.fill(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
padding: .symmetric(horizontal: 12),
child: switch (controllerData) {
AsyncData(:final value) ||
AsyncLoading(:final value?) => CustomScrollView(
@ -349,9 +343,7 @@ class RoomChat extends HookConsumerWidget {
controller: scrollController,
slivers: [
SliverPadding(
padding: EdgeInsetsGeometry.only(
bottom: composerSize.value,
),
padding: .only(bottom: composerSize.value),
),
SuperSliverList.builder(
@ -371,19 +363,15 @@ class RoomChat extends HookConsumerWidget {
),
scrollController: scrollController,
alignment: 0.5,
duration: (_) =>
Duration(milliseconds: 700),
duration: (_) => .new(milliseconds: 700),
curve: (_) => Curves.easeInOut,
);
flashingEvent.value = replyId;
await Future.delayed(
Duration(seconds: 1),
() {
await Future.delayed(.new(seconds: 1), () {
if (flashingEvent.value == replyId) {
flashingEvent.value = null;
}
},
);
});
},
getEventOptions: getEventOptions,
isGrouped:
@ -403,7 +391,7 @@ class RoomChat extends HookConsumerWidget {
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 36),
padding: .symmetric(vertical: 36),
child: Center(
child: controllerData is AsyncLoading
? Loading()

View file

@ -34,7 +34,7 @@ class RoomMenu extends ConsumerWidget {
final vias = ref.watch(ViaController.provider(room!));
await Clipboard.setData(
ClipboardData(
.new(
text:
"matrix:roomid/${room!.metadata?.id.substring(1)}$vias)",
),
@ -63,7 +63,7 @@ class RoomMenu extends ConsumerWidget {
Navigator.of(context).pop();
final snackbar = ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar(
.new(
content: Text("Leaving room..."),
duration: Duration(days: 1),
),

View file

@ -74,14 +74,14 @@ class Sidebar extends HookConsumerWidget {
),
),
label: Text(space.title),
padding: EdgeInsets.only(top: 4),
padding: .only(top: 4),
),
)
.toList(),
selectedIndex: selectedIndex,
trailingAtBottom: true,
trailing: Padding(
padding: EdgeInsets.symmetric(vertical: 16),
padding: .symmetric(vertical: 16),
child: Column(
spacing: 8,
children: [
@ -136,10 +136,7 @@ class Sidebar extends HookConsumerWidget {
selectedSpace.title,
),
title: Text(
selectedSpace.title,
overflow: TextOverflow.ellipsis,
),
title: Text(selectedSpace.title, overflow: .ellipsis),
backgroundColor: Colors.transparent,
actions: [
RoomMenu(

View file

@ -13,25 +13,24 @@ class UrlPreview extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) => ConstrainedBox(
constraints: BoxConstraints.loose(Size.fromWidth(400)),
constraints: .loose(.fromWidth(400)),
child: ref
.watch(UrlPreviewController.provider(link))
.betterWhen(
data: (preview) => preview == null
? SizedBox.shrink()
: InkWell(
onTap: () => ref
.watch(LaunchHelper.provider)
.launchUrl(Uri.parse(link)),
onTap: () =>
ref.watch(LaunchHelper.provider).launchUrl(.parse(link)),
child: Card(
margin: EdgeInsets.symmetric(vertical: 4),
margin: .symmetric(vertical: 4),
color: Theme.of(
context,
).colorScheme.surfaceContainerHighest,
child: Padding(
padding: EdgeInsetsGeometry.all(16),
padding: .all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: .start,
spacing: 4,
children: [
if (preview.title != null)
@ -45,9 +44,7 @@ class UrlPreview extends ConsumerWidget {
],
if (preview.imageUrl != null)
ClipRRect(
borderRadius: BorderRadius.all(
Radius.circular(8),
),
borderRadius: .all(.circular(8)),
child: Image(
errorBuilder: (_, _, _) => SizedBox.shrink(),
width: preview.width,
@ -56,7 +53,7 @@ class UrlPreview extends ConsumerWidget {
ref.watch(CrossCacheController.provider),
headers: ref.headers,
),
fit: BoxFit.fitWidth,
fit: .fitWidth,
),
),
],

View file

@ -9,11 +9,8 @@ import "package:nexus/controllers/profile_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/helpers/extensions/get_localpart.dart";
import "package:nexus/helpers/extensions/mxc_to_https.dart";
import "package:nexus/models/configs/power_level_config.dart";
import "package:nexus/models/content/membership.dart";
import "package:nexus/models/membership_status.dart";
import "package:nexus/models/requests/membership_action.dart";
import "package:nexus/models/requests/set_membership_request.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
import "package:nexus/main.dart";
import "package:nexus/widgets/expandable_image.dart";
@ -39,8 +36,8 @@ class UserPopover extends ConsumerWidget {
return AlertDialog(
title: Text("${toBeginningOfSentenceCase(action.name)} $userId"),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: .min,
crossAxisAlignment: .start,
children: [
Text("Are you sure you want to ${action.name} $userId?"),
SizedBox(height: 12),
@ -62,7 +59,7 @@ class UserPopover extends ConsumerWidget {
Navigator.of(context).pop();
client
.setMembership(
SetMembershipRequest(
.new(
userId: userId,
roomId: roomId!,
action: action,
@ -80,20 +77,18 @@ class UserPopover extends ConsumerWidget {
);
final actionButton = ButtonStyle(
padding: WidgetStatePropertyAll(
EdgeInsets.symmetric(horizontal: 24, vertical: 18),
),
padding: WidgetStatePropertyAll(.symmetric(horizontal: 24, vertical: 18)),
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
RoundedRectangleBorder(borderRadius: .circular(8)),
),
);
return Column(
spacing: 16,
crossAxisAlignment: CrossAxisAlignment.stretch,
crossAxisAlignment: .stretch,
children: [
Wrap(
alignment: WrapAlignment.center,
alignment: .center,
spacing: 16,
runSpacing: 8,
children: [
@ -126,7 +121,7 @@ class UserPopover extends ConsumerWidget {
.betterWhen(
loading: SizedBox.shrink,
data: (profile) => Wrap(
alignment: WrapAlignment.center,
alignment: .center,
spacing: 4,
runSpacing: 4,
children: [
@ -135,7 +130,7 @@ class UserPopover extends ConsumerWidget {
))
Chip(
label: Text(pronoun.summary),
labelStyle: TextStyle(
labelStyle: .new(
color: theme.colorScheme.onPrimary,
),
color: WidgetStatePropertyAll(
@ -145,7 +140,7 @@ class UserPopover extends ConsumerWidget {
if (profile.timezone != null)
Chip(
label: Text(profile.timezone!),
labelStyle: TextStyle(
labelStyle: .new(
color: theme.colorScheme.onPrimary,
),
color: WidgetStatePropertyAll(
@ -162,7 +157,7 @@ class UserPopover extends ConsumerWidget {
if (userId != ref.watch(ClientStateController.provider)?.userId &&
roomId != null)
Wrap(
alignment: WrapAlignment.center,
alignment: .center,
spacing: 8,
runSpacing: 8,
children: [
@ -175,17 +170,17 @@ class UserPopover extends ConsumerWidget {
if (ref.watch(
PowerLevelController.provider(
PowerLevelConfig.membershipAction(
action: MembershipAction.kick,
.membershipAction(
action: .kick,
roomId: roomId!,
targetUser: userId,
),
),
) &&
member.status == MembershipStatus.join ||
member.status == MembershipStatus.invite)
member.status == .join ||
member.status == .invite)
FilledButton.icon(
onPressed: () => showMembershipDialog(MembershipAction.kick),
onPressed: () => showMembershipDialog(.kick),
label: Text("Kick"),
style: actionButton.copyWith(
backgroundColor: WidgetStatePropertyAll(
@ -199,23 +194,19 @@ class UserPopover extends ConsumerWidget {
),
if (ref.watch(
PowerLevelController.provider(
PowerLevelConfig.membershipAction(
.membershipAction(
roomId: roomId!,
action: MembershipAction.ban,
action: .ban,
targetUser: userId,
),
),
))
ElevatedButton.icon(
onPressed: () => showMembershipDialog(
member.status == MembershipStatus.ban
? MembershipAction.unban
: MembershipAction.ban,
member.status == .ban ? .unban : .ban,
),
icon: Icon(Icons.gavel),
label: Text(
member.status == MembershipStatus.ban ? "Unban" : "Ban",
),
label: Text(member.status == .ban ? "Unban" : "Ban"),
style: actionButton.copyWith(
backgroundColor: WidgetStatePropertyAll(
theme.colorScheme.errorContainer,