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>> { class AccountDataController extends Notifier<IMap<String, AccountData>> {
@override @override
IMap<String, AccountData> build() => const IMap.empty(); IMap<String, AccountData> build() => .new();
void update(IMap<String, AccountData> newData) => void update(IMap<String, AccountData> newData) =>
state = IMap({...state.unlock, ...newData.unlock}); state = .new({...state.unlock, ...newData.unlock});
static final provider = static final provider =
NotifierProvider<AccountDataController, IMap<String, AccountData>>( NotifierProvider<AccountDataController, IMap<String, AccountData>>(

View file

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

View file

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

View file

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

View file

@ -12,10 +12,7 @@ class EmojiController extends AsyncNotifier<EmojiTuple> {
@override @override
Future<EmojiTuple> build() async { Future<EmojiTuple> build() async {
final response = await get( final response = await get(
Uri.https( .https("github.com", "github/gemoji/raw/refs/heads/master/db/emoji.json"),
"github.com",
"github/gemoji/raw/refs/heads/master/db/emoji.json",
),
); );
if (response.statusCode != 200) { if (response.statusCode != 200) {
@ -30,19 +27,19 @@ class EmojiController extends AsyncNotifier<EmojiTuple> {
.toIList(); .toIList();
final categoryMap = entries.fold<IMap<String, IList<String>>>( final categoryMap = entries.fold<IMap<String, IList<String>>>(
const IMap.empty(), .new(),
(acc, entry) => acc.update( (acc, entry) => acc.update(
entry.category, entry.category,
(list) => list.add(entry.emoji), (list) => list.add(entry.emoji),
ifAbsent: () => IList([entry.emoji]), ifAbsent: () => .new([entry.emoji]),
), ),
); );
final keywordMap = entries.fold<IMap<String, IList<String>>>( final keywordMap = entries.fold<IMap<String, IList<String>>>(
const IMap.empty(), .new(),
(acc, entry) => acc.add( (acc, entry) => acc.add(
entry.emoji, 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( final customKeywords = IMap(
Map.fromEntries( .fromEntries(
keywordMap.entries.map( 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]), RoomsController.provider.select((value) => value[roomId]),
); );
if (room == null) return const ISet.empty(); if (room == null) return .new();
if (!room.hasFetchedMembers) { if (!room.hasFetchedMembers) {
final fetchedState = await ref final fetchedState = await ref
@ -38,7 +38,7 @@ class MembersController extends AsyncNotifier<ISet<Event>> {
.map((rowId) => room.events[rowId]) .map((rowId) => room.events[rowId])
.nonNulls .nonNulls
.toISet() ?? .toISet() ??
const ISet.empty(); .new();
} }
static final provider = AsyncNotifierProvider.autoDispose static final provider = AsyncNotifierProvider.autoDispose

View file

@ -7,9 +7,8 @@ class MultiProviderController extends AsyncNotifier<void> {
final IList<AsyncNotifierProvider> providers; final IList<AsyncNotifierProvider> providers;
@override @override
FutureOr<void> build() async => await Future.wait( Future<void> build() =>
providers.map((provider) => ref.watch(provider.future)), .wait(providers.map((provider) => ref.watch(provider.future)));
);
static final provider = static final provider =
AsyncNotifierProvider.family< 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/configs/power_level_config.dart";
import "package:nexus/models/content/content.dart"; import "package:nexus/models/content/content.dart";
import "package:nexus/models/content/power_levels.dart"; import "package:nexus/models/content/power_levels.dart";
import "package:nexus/models/requests/membership_action.dart";
class PowerLevelController extends Notifier<bool> { class PowerLevelController extends Notifier<bool> {
final PowerLevelConfig config; final PowerLevelConfig config;
@ -14,7 +13,7 @@ class PowerLevelController extends Notifier<bool> {
bool build() { bool build() {
if (config case EventPowerLevelConfig(:final eventType)) { if (config case EventPowerLevelConfig(:final eventType)) {
assert( assert(
eventType != EventType.redaction, eventType != .redaction,
"Checking power level for a redaction should use [PowerLevelConfig.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 final content = event?.content is PowerLevelsContent
? event!.content ? event!.content
: PowerLevelsContent(); : PowerLevelsContent();
final user = ref.watch( final user = ref.watch(
ClientStateController.provider.select((value) => value?.userId), ClientStateController.provider.select((value) => value?.userId),
); );
@ -45,15 +45,15 @@ class PowerLevelController extends Notifier<bool> {
MembershipActionPowerLevelConfig(:final action, :final targetUser) => MembershipActionPowerLevelConfig(:final action, :final targetUser) =>
switch (action) { switch (action) {
MembershipAction.invite => userLevel >= content.invite, .invite => userLevel >= content.invite,
MembershipAction.kick => .kick =>
userLevel >= content.kick && userLevel > powerLevelOf(targetUser), userLevel >= content.kick && userLevel > powerLevelOf(targetUser),
MembershipAction.ban => .ban =>
userLevel >= content.ban && userLevel > powerLevelOf(targetUser), userLevel >= content.ban && userLevel > powerLevelOf(targetUser),
MembershipAction.unban => userLevel >= content.ban, .unban => userLevel >= content.ban,
}, },
StatePowerLevelConfig(:final eventType) => 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/controllers/rooms_controller.dart";
import "package:nexus/models/configs/reactions_config.dart"; import "package:nexus/models/configs/reactions_config.dart";
import "package:nexus/models/content/reaction.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>>> { class ReactionsController extends AsyncNotifier<IMap<String, IList<String>>> {
final ReactionsConfig config; final ReactionsConfig config;
@ -23,7 +22,7 @@ class ReactionsController extends AsyncNotifier<IMap<String, IList<String>>> {
? await ref ? await ref
.watch(ClientController.provider.notifier) .watch(ClientController.provider.notifier)
.getRelatedEvents( .getRelatedEvents(
GetRelatedEventsRequest( .new(
roomId: config.roomId, roomId: config.roomId,
eventId: eventInfo!.$1, eventId: eventInfo!.$1,
relationType: "m.annotation", relationType: "m.annotation",
@ -33,18 +32,18 @@ class ReactionsController extends AsyncNotifier<IMap<String, IList<String>>> {
return reactionEvents return reactionEvents
?.where((event) => event.redactedBy == null) ?.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?)) { if (event.content case ReactionContent(:final key?)) {
return acc.update( return acc.update(
key, key,
(list) => list.add(event.sender), (list) => list.add(event.sender),
ifAbsent: () => IList([event.sender]), ifAbsent: () => .new([event.sender]),
); );
} }
return acc; return acc;
}) ?? }) ??
const IMap.empty(); .new();
} }
static final provider = 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/content.dart";
import "package:nexus/models/content/reaction.dart"; import "package:nexus/models/content/reaction.dart";
import "package:nexus/models/event.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/requests/redact_event_request.dart";
import "package:nexus/models/relation_type.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/requests/send_message_request.dart";
import "package:nexus/models/room.dart"; import "package:nexus/models/room.dart";
@ -28,12 +24,10 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
final room = ref.watch( final room = ref.watch(
RoomsController.provider.select((rooms) => rooms[roomId]), RoomsController.provider.select((rooms) => rooms[roomId]),
); );
if (room == null) return const IList.empty(); if (room == null) return .new();
if (!room.hasFetchedState) { if (!room.hasFetchedState) {
final state = await client.getRoomState( final state = await client.getRoomState(.new(roomId: roomId));
GetRoomStateRequest(roomId: roomId),
);
await ref.read(RoomsController.provider.notifier).addState(roomId, state); await ref.read(RoomsController.provider.notifier).addState(roomId, state);
} }
@ -45,7 +39,7 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
} }
return IMap<int, int?>.fromValues( return IMap<int, int?>.fromValues(
keyMapper: (id) => 9999999 + (id ?? 0), keyMapper: (id) => 9999999999 + (id ?? 0),
values: room.sticky, values: room.sticky,
) )
.addAll(room.timeline) .addAll(room.timeline)
@ -86,7 +80,7 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
final response = await ref final response = await ref
.watch(ClientController.provider.notifier) .watch(ClientController.provider.notifier)
.paginate( .paginate(
PaginateRequest( .new(
roomId: roomId, roomId: roomId,
maxTimelineId: timelineKeys?.isNotEmpty == true maxTimelineId: timelineKeys?.isNotEmpty == true
? timelineKeys?.reduce(min) ? timelineKeys?.reduce(min)
@ -112,7 +106,7 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
), ),
), ),
}), }),
const ISet.empty(), .new(),
); );
return response.hasMore; return response.hasMore;
@ -153,20 +147,20 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
text: taggedMessage, text: taggedMessage,
relation: relation == null relation: relation == null
? null ? null
: Relation(eventId: relation.eventId, relationType: relationType), : .new(eventId: relation.eventId, relationType: relationType),
), ),
); );
ref ref
.watch(RoomsController.provider.notifier) .watch(RoomsController.provider.notifier)
.update( .update(
{ .new({
roomId: Room( roomId: .new(
events: {event.rowId: event}.toIMap(), events: .new({event.rowId: event}),
sticky: {event.rowId}.toISet(), sticky: .new({event.rowId}),
), ),
}.toIMap(), }),
const ISet.empty(), .new(),
); );
} }
@ -177,7 +171,7 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
) async { ) async {
final client = ref.watch(ClientController.provider.notifier); final client = ref.watch(ClientController.provider.notifier);
final allReactionEvents = await client.getRelatedEvents( final allReactionEvents = await client.getRelatedEvents(
GetRelatedEventsRequest( .new(
roomId: roomId, roomId: roomId,
eventId: event.eventId, eventId: event.eventId,
relationType: "m.annotation", relationType: "m.annotation",
@ -199,9 +193,7 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
if (reactionEvent != null) { if (reactionEvent != null) {
await ref await ref
.watch(ClientController.provider.notifier) .watch(ClientController.provider.notifier)
.redactEvent( .redactEvent(.new(eventId: reactionEvent.eventId, roomId: roomId));
RedactEventRequest(eventId: reactionEvent.eventId, roomId: roomId),
);
} }
} }
@ -209,7 +201,7 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
final client = ref.watch(ClientController.provider.notifier); final client = ref.watch(ClientController.provider.notifier);
await client.sendEvent( await client.sendEvent(
SendEventRequest( .new(
roomId: roomId, roomId: roomId,
type: EventType.reaction.type, type: EventType.reaction.type,
content: { content: {

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@ import "package:flutter_riverpod/flutter_riverpod.dart";
class TopLevelSpacesController extends Notifier<IList<String>> { class TopLevelSpacesController extends Notifier<IList<String>> {
@override @override
IList<String> build() => const IList.empty(); IList<String> build() => .new();
void set(IList<String> newSpaces) => state = newSpaces; 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")) { if (homeserver != null && !link.contains("matrix.to")) {
{ {
final response = await get( final response = await get(
Uri.parse(homeserver) .parse(homeserver)
.resolve("/_matrix/client/v1/media/preview_url") .resolve("/_matrix/client/v1/media/preview_url")
.replace(queryParameters: {"url": link}), .replace(queryParameters: {"url": link}),
headers: await ref.watch(HeaderController.provider.future), headers: await ref.watch(HeaderController.provider.future),
@ -34,7 +34,7 @@ class UrlPreviewController extends AsyncNotifier<OpenGraphData?> {
? null ? null
: Uri.tryParse(mxc)?.mxcToHttps(homeserver); : 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/helpers/extensions/get_localpart.dart";
import "package:nexus/models/configs/user_config.dart"; import "package:nexus/models/configs/user_config.dart";
import "package:nexus/models/content/membership.dart"; import "package:nexus/models/content/membership.dart";
import "package:nexus/models/membership_status.dart";
class UserController extends AsyncNotifier<MembershipContent> { class UserController extends AsyncNotifier<MembershipContent> {
final UserConfig config; final UserConfig config;
@ -31,8 +30,9 @@ class UserController extends AsyncNotifier<MembershipContent> {
final profile = await ref.watch( final profile = await ref.watch(
ProfileController.provider(config.userId).future, ProfileController.provider(config.userId).future,
); );
return MembershipContent(
status: MembershipStatus.leave, return .new(
status: .leave,
avatarUrl: profile.avatarUrl, avatarUrl: profile.avatarUrl,
displayName: profile.displayName ?? config.userId.localpart, 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/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/membership_status.dart";
import "package:nexus/models/room.dart"; import "package:nexus/models/room.dart";
class ViaController extends Notifier<String> { class ViaController extends Notifier<String> {
@ -45,7 +44,7 @@ class ViaController extends Notifier<String> {
: room.events[membershipEventId]; : room.events[membershipEventId];
if (member?.content case MembershipContent(:final status)) { if (member?.content case MembershipContent(:final status)) {
if (status == MembershipStatus.join) { if (status == .join) {
addUserId(member?.stateKey); addUserId(member?.stateKey);
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -14,9 +14,9 @@ extension ShowUserPopover on BuildContext {
children: [ children: [
PopupMenuItem( PopupMenuItem(
enabled: false, enabled: false,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: .symmetric(horizontal: 16, vertical: 8),
child: IconTheme( child: IconTheme(
data: IconThemeData(), data: .new(),
child: UserPopover(member, userId, roomId: roomId), 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 { extension SizeToString on int {
String get sizeAsString { String get sizeAsString {
const IListConst<String> suffixes = IListConst([ const suffixes = IListConst(["B", "KB", "MB", "GB", "TB", "PB"]);
"B",
"KB",
"MB",
"GB",
"TB",
"PB",
]);
var i = 0; var i = 0;
var size = toDouble(); var size = toDouble();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -86,9 +86,7 @@ class Html extends ConsumerWidget {
), ),
errorBuilder: (_, error, _) => Text( errorBuilder: (_, error, _) => Text(
"Image Failed to Load", "Image Failed to Load",
style: TextStyle( style: .new(color: Theme.of(context).colorScheme.error),
color: Theme.of(context).colorScheme.error,
),
), ),
height: height.toDouble(), height: height.toDouble(),
width: width?.toDouble(), width: width?.toDouble(),
@ -146,15 +144,14 @@ class Html extends ConsumerWidget {
element.attributes element.attributes
.mapTo<MapEntry<String, String>?>( .mapTo<MapEntry<String, String>?>(
(key, value) => switch (key) { (key, value) => switch (key) {
"data-mx-color" => MapEntry("color", value), "data-mx-color" => .new("color", value),
"data-mx-bg-color" => MapEntry("background-color", value), "data-mx-bg-color" => .new("background-color", value),
_ => null, _ => null,
}, },
) )
.nonNulls, .nonNulls,
), ),
}, },
onTapUrl: (url) => onTapUrl: (url) => ref.watch(LaunchHelper.provider).launchUrl(.parse(url)),
ref.watch(LaunchHelper.provider).launchUrl(Uri.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/controllers/user_controller.dart";
import "package:nexus/helpers/extensions/link_to_mention.dart"; import "package:nexus/helpers/extensions/link_to_mention.dart";
import "package:nexus/helpers/extensions/show_user_popover.dart"; import "package:nexus/helpers/extensions/show_user_popover.dart";
import "package:nexus/models/configs/user_config.dart";
class MentionChip extends ConsumerWidget { class MentionChip extends ConsumerWidget {
final String? roomId; final String? roomId;
@ -16,9 +15,7 @@ class MentionChip extends ConsumerWidget {
final membership = mention?.startsWith("@") == true final membership = mention?.startsWith("@") == true
? ref ? ref
.watch( .watch(
UserController.provider( UserController.provider(.new(roomId: roomId, userId: mention!)),
UserConfig(roomId: roomId, userId: mention!),
),
) )
.whenOrNull(data: (data) => data) .whenOrNull(data: (data) => data)
: null; : null;
@ -41,8 +38,8 @@ class MentionChip extends ConsumerWidget {
label: Text( label: Text(
(membership == null ? null : "@${membership.displayName}") ?? (membership == null ? null : "@${membership.displayName}") ??
mention, mention,
style: TextStyle( style: .new(
fontWeight: FontWeight.bold, fontWeight: .bold,
color: Theme.of(context).colorScheme.onPrimary, color: Theme.of(context).colorScheme.onPrimary,
), ),
), ),

View file

@ -8,9 +8,9 @@ class Quoted extends StatelessWidget {
Widget build(BuildContext context) => Container( Widget build(BuildContext context) => Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( 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( return InkWell(
onTap: () => revealed.value = !revealed.value, onTap: () => revealed.value = !revealed.value,
child: AnimatedContainer( child: AnimatedContainer(
duration: const Duration(milliseconds: 100), duration: const .new(milliseconds: 100),
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), padding: const .symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration( decoration: BoxDecoration(
color: revealed.value ? Colors.transparent : Colors.blueGrey, color: revealed.value ? Colors.transparent : Colors.blueGrey,
borderRadius: BorderRadius.circular(4), borderRadius: .circular(4),
), ),
child: Text( child: Text(
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:collection/collection.dart";
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_hooks/flutter_hooks.dart";
import "package:hooks_riverpod/hooks_riverpod.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/key_controller.dart";
import "package:nexus/controllers/spaces_controller.dart"; import "package:nexus/controllers/spaces_controller.dart";
import "package:nexus/helpers/extensions/link_to_mention.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"; import "package:nexus/widgets/form_text_input.dart";
class JoinDialog extends HookWidget { class JoinDialog extends HookWidget {
@ -20,8 +18,8 @@ class JoinDialog extends HookWidget {
return AlertDialog( return AlertDialog(
title: Text("Join a Room"), title: Text("Join a Room"),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: .min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: .start,
children: [ children: [
Text("Enter the room alias, Matrix URI, or Matrix.to link."), Text("Enter the room alias, Matrix URI, or Matrix.to link."),
SizedBox(height: 12), SizedBox(height: 12),
@ -45,7 +43,7 @@ class JoinDialog extends HookWidget {
final scaffoldMessenger = ScaffoldMessenger.of(context); final scaffoldMessenger = ScaffoldMessenger.of(context);
final snackbar = scaffoldMessenger.showSnackBar( final snackbar = scaffoldMessenger.showSnackBar(
SnackBar( .new(
content: Text("Joining room $roomIdOrAlias."), content: Text("Joining room $roomIdOrAlias."),
duration: Duration(days: 999), duration: Duration(days: 999),
), ),
@ -55,9 +53,9 @@ class JoinDialog extends HookWidget {
final id = await ref final id = await ref
.watch(ClientController.provider.notifier) .watch(ClientController.provider.notifier)
.joinRoom( .joinRoom(
JoinRoomRequest( .new(
roomIdOrAlias: roomIdOrAlias, roomIdOrAlias: roomIdOrAlias,
via: IList( via: .new(
Uri.tryParse( Uri.tryParse(
roomAlias.text.replaceAll("/#", ""), roomAlias.text.replaceAll("/#", ""),
)?.queryParametersAll["via"] ?? )?.queryParametersAll["via"] ??
@ -69,9 +67,9 @@ class JoinDialog extends HookWidget {
snackbar.close(); snackbar.close();
scaffoldMessenger.showSnackBar( scaffoldMessenger.showSnackBar(
SnackBar( .new(
content: Text("Room $roomIdOrAlias successfully joined."), content: Text("Room $roomIdOrAlias successfully joined."),
action: SnackBarAction( action: .new(
label: "Open", label: "Open",
onPressed: () async { onPressed: () async {
final spaces = ref.watch(SpacesController.provider); final spaces = ref.watch(SpacesController.provider);
@ -113,13 +111,13 @@ class JoinDialog extends HookWidget {
snackbar.close(); snackbar.close();
if (context.mounted) { if (context.mounted) {
scaffoldMessenger.showSnackBar( scaffoldMessenger.showSnackBar(
SnackBar( .new(
backgroundColor: Theme.of( backgroundColor: Theme.of(
context, context,
).colorScheme.errorContainer, ).colorScheme.errorContainer,
content: Text( content: Text(
error.toString(), error.toString(),
style: TextStyle( style: .new(
color: Theme.of(context).colorScheme.onErrorContainer, color: Theme.of(context).colorScheme.onErrorContainer,
), ),
), ),

View file

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

View file

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

View file

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

View file

@ -17,7 +17,7 @@ class MessageImage extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) => ExpandableImage( Widget build(BuildContext context, WidgetRef ref) => ExpandableImage(
url.toString(), url.toString(),
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(8)), borderRadius: .all(.circular(8)),
child: Image( child: Image(
image: CachedNetworkImage( image: CachedNetworkImage(
url.toString(), url.toString(),
@ -47,7 +47,7 @@ class MessageImage extends ConsumerWidget {
errorBuilder: (context, error, stackTrace) => Center( errorBuilder: (context, error, stackTrace) => Center(
child: Text( child: Text(
"Image Failed to Load", "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 @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final player = useMemoized( final player = useMemoized(
() => Player( () => Player(configuration: .new(bufferSize: 128 * 1024 * 1024)),
configuration: PlayerConfiguration(bufferSize: 128 * 1024 * 1024),
),
); );
final playing = useState(false); final playing = useState(false);
@ -66,7 +64,7 @@ class AudioPlayer extends HookConsumerWidget {
child: Card( child: Card(
color: Theme.of(context).colorScheme.surfaceContainer, color: Theme.of(context).colorScheme.surfaceContainer,
child: Padding( child: Padding(
padding: EdgeInsetsGeometry.only(left: 8, right: 16), padding: .only(left: 8, right: 16),
child: Row( child: Row(
children: [ children: [
IconButton( IconButton(
@ -88,7 +86,7 @@ class AudioPlayer extends HookConsumerWidget {
: duration.value.inMilliseconds.toDouble(), : duration.value.inMilliseconds.toDouble(),
value: position.value.inMilliseconds.toDouble(), value: position.value.inMilliseconds.toDouble(),
onChanged: (value) => onChanged: (value) =>
player.seek(Duration(milliseconds: value.toInt())), player.seek(.new(milliseconds: value.toInt())),
), ),
), ),
Text( Text(

View file

@ -16,9 +16,7 @@ class VideoPlayer extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final player = useMemoized( final player = useMemoized(
() => Player( () => Player(configuration: .new(bufferSize: 128 * 1024 * 1024)),
configuration: PlayerConfiguration(bufferSize: 128 * 1024 * 1024),
),
); );
final controller = useMemoized(() => VideoController(player)); 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/controllers/room_chat_controller.dart";
import "package:nexus/helpers/extensions/get_headers.dart"; import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/helpers/extensions/mxc_to_https.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/models/event.dart";
import "package:nexus/widgets/error_dialog.dart"; import "package:nexus/widgets/error_dialog.dart";
import "package:nexus/main.dart"; import "package:nexus/main.dart";
@ -24,7 +23,7 @@ class ReactionRow extends ConsumerWidget {
return switch (ref.watch( return switch (ref.watch(
ReactionsController.provider( ReactionsController.provider(
ReactionsConfig(roomId: event.roomId, eventRowId: event.rowId), .new(roomId: event.roomId, eventRowId: event.rowId),
), ),
)) { )) {
AsyncData(value: final IMap<String, IList<String>>? reactors) || AsyncData(value: final IMap<String, IList<String>>? reactors) ||
@ -47,7 +46,7 @@ class ReactionRow extends ConsumerWidget {
showCheckmark: false, showCheckmark: false,
selected: selected, selected: selected,
label: Row( label: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: .min,
spacing: 8, spacing: 8,
children: [ children: [
Flexible( Flexible(
@ -64,15 +63,9 @@ class ReactionRow extends ConsumerWidget {
ref.watch(CrossCacheController.provider), ref.watch(CrossCacheController.provider),
), ),
) )
: Text( : Text(reaction, overflow: .ellipsis),
reaction,
overflow: TextOverflow.ellipsis,
),
),
Text(
count.toString(),
overflow: TextOverflow.ellipsis,
), ),
Text(count.toString(), overflow: .ellipsis),
], ],
), ),
onSelected: enabled.value onSelected: enabled.value

View file

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

View file

@ -7,14 +7,11 @@ class GenericEventRenderer extends StatelessWidget {
@override @override
Widget build(BuildContext context) => Padding( Widget build(BuildContext context) => Padding(
padding: EdgeInsets.only(bottom: 8), padding: .only(bottom: 8),
child: Row( child: Row(
spacing: 8, spacing: 8,
children: [ children: [
Padding( Padding(padding: .symmetric(horizontal: 4), child: Icon(icon)),
padding: EdgeInsets.symmetric(horizontal: 4),
child: Icon(icon),
),
Expanded(child: Wrap(spacing: 4, children: children)), 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/helpers/extensions/string_to_color.dart";
import "package:nexus/models/content/membership.dart"; import "package:nexus/models/content/membership.dart";
import "package:nexus/models/event.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/lazy_loading/message_displayname.dart";
import "package:nexus/widgets/renderers/generic_event.dart"; import "package:nexus/widgets/renderers/generic_event.dart";
@ -29,24 +28,21 @@ class MembershipRenderer extends StatelessWidget {
globalPosition: details.globalPosition, globalPosition: details.globalPosition,
), ),
child: Text( child: Text(
overflow: TextOverflow.ellipsis, overflow: .ellipsis,
content.displayName ?? event.stateKey!.localpart, content.displayName ?? event.stateKey!.localpart,
maxLines: 1, maxLines: 1,
style: TextStyle( style: .new(color: event.sender.colorHash, fontWeight: .bold),
color: event.sender.colorHash,
fontWeight: FontWeight.bold,
),
), ),
), ),
Text( Text(
overflow: TextOverflow.ellipsis, overflow: .ellipsis,
maxLines: 1, maxLines: 1,
"${switch (content.status) { "${switch (content.status) {
MembershipStatus.invite => "was invited to", .invite => "was invited to",
MembershipStatus.join => "joined", .join => "joined",
MembershipStatus.leave => event.sender == event.stateKey ? "left" : (event.unsigned["prev_content"]?["membership"] == "ban" ? "was unbanned from" : "was kicked from"), .leave => event.sender == event.stateKey ? "left" : (event.unsigned["prev_content"]?["membership"] == "ban" ? "was unbanned from" : "was kicked from"),
MembershipStatus.ban => "was banned from", .ban => "was banned from",
MembershipStatus.knock => "asked to join", .knock => "asked to join",
}} the room${event.sender == event.stateKey ? "" : " by "}", }} the room${event.sender == event.stateKey ? "" : " by "}",
), ),
if (event.sender != event.stateKey) MessageDisplayname(event), 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/message.dart";
import "package:nexus/models/content/sticker.dart"; import "package:nexus/models/content/sticker.dart";
import "package:nexus/models/event.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/file_card.dart";
import "package:nexus/widgets/html/html.dart"; import "package:nexus/widgets/html/html.dart";
import "package:nexus/widgets/lazy_loading/message_avatar.dart"; import "package:nexus/widgets/lazy_loading/message_avatar.dart";
@ -49,19 +48,19 @@ class MessageRenderer extends ConsumerWidget {
child: Text( child: Text(
format(event.timestamp), format(event.timestamp),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: .ellipsis,
style: theme.textTheme.labelSmall?.copyWith(color: Colors.grey), style: theme.textTheme.labelSmall?.copyWith(color: Colors.grey),
), ),
); );
final textStyle = TextStyle( final textStyle = TextStyle(
fontSize: event.localContent?.bigEmoji == true ? 32 : null, fontSize: event.localContent?.bigEmoji == true ? 32 : null,
fontStyle: event.content is EmoteMessageContent ? FontStyle.italic : null, fontStyle: event.content is EmoteMessageContent ? .italic : null,
); );
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: .start,
mainAxisSize: MainAxisSize.min, mainAxisSize: .min,
spacing: 8, spacing: 8,
children: [ children: [
if (!textOnly) if (!textOnly)
@ -72,7 +71,7 @@ class MessageRenderer extends ConsumerWidget {
Flexible( Flexible(
child: Column( child: Column(
spacing: 4, spacing: 4,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: .start,
children: [ children: [
if (!isGrouped && !textOnly) if (!isGrouped && !textOnly)
Row( Row(
@ -83,7 +82,7 @@ class MessageRenderer extends ConsumerWidget {
], ],
), ),
Card( Card(
margin: textOnly ? EdgeInsets.zero : EdgeInsets.only(bottom: 4), margin: textOnly ? .zero : .only(bottom: 4),
color: textOnly color: textOnly
? Colors.transparent ? Colors.transparent
: ref.watch( : ref.watch(
@ -99,24 +98,21 @@ class MessageRenderer extends ConsumerWidget {
elevation: textOnly ? 0 : null, elevation: textOnly ? 0 : null,
child: Padding( child: Padding(
padding: textOnly ? EdgeInsets.zero : EdgeInsets.all(12), padding: textOnly ? .zero : .all(12),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: .start,
children: [ children: [
if (!textOnly && event.replyTo != null) if (!textOnly && event.replyTo != null)
Card( Card(
margin: EdgeInsets.only(bottom: 8), margin: .only(bottom: 8),
color: theme.colorScheme.surfaceContainerHigh, color: theme.colorScheme.surfaceContainerHigh,
child: InkWell( child: InkWell(
onTap: onTapReply, onTap: onTapReply,
child: Padding( child: Padding(
padding: EdgeInsetsGeometry.symmetric( padding: .symmetric(vertical: 8, horizontal: 12),
vertical: 8,
horizontal: 12,
),
child: switch (ref.watch( child: switch (ref.watch(
EventController.provider( EventController.provider(
GetEventRequest( .new(
roomId: event.roomId, roomId: event.roomId,
eventId: event.replyTo!, eventId: event.replyTo!,
), ),
@ -145,12 +141,10 @@ class MessageRenderer extends ConsumerWidget {
? Text( ? Text(
body, body,
maxLines: maxLines, maxLines: maxLines,
overflow: TextOverflow.ellipsis, overflow: .ellipsis,
) )
: ConstrainedBox( : ConstrainedBox(
constraints: BoxConstraints.loose( constraints: .loose(.square(200)),
Size.square(200),
),
child: MessageImage( child: MessageImage(
url.mxcToHttps( url.mxcToHttps(
ref.watch( ref.watch(
@ -199,9 +193,9 @@ class MessageRenderer extends ConsumerWidget {
:final formattedBody, :final formattedBody,
:final format, :final format,
) => Column( ) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: .start,
children: [ children: [
format == MessageFormat.html && !textOnly format == .html && !textOnly
? Html( ? Html(
roomId: event.roomId, roomId: event.roomId,
textStyle: textStyle, textStyle: textStyle,
@ -243,9 +237,7 @@ class MessageRenderer extends ConsumerWidget {
), ),
)) { )) {
final url? => ConstrainedBox( final url? => ConstrainedBox(
constraints: BoxConstraints.loose( constraints: .loose(.square(500)),
Size.square(500),
),
child: switch (event.content) { child: switch (event.content) {
VideoMessageContent(:final info) => VideoMessageContent(:final info) =>
VideoPlayer(url, info), VideoPlayer(url, info),
@ -286,7 +278,7 @@ class MessageRenderer extends ConsumerWidget {
), ),
MessageContent(:final body) => Row( MessageContent(:final body) => Row(
spacing: 8, spacing: 8,
mainAxisSize: MainAxisSize.min, mainAxisSize: .min,
children: [ children: [
Text("Unknown message type:", style: errorStyle), Text("Unknown message type:", style: errorStyle),
Text(body), Text(body),

View file

@ -38,17 +38,17 @@ class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget {
: () => showDialog( : () => showDialog(
context: context, context: context,
builder: (context) => Dialog( builder: (context) => Dialog(
constraints: BoxConstraints.loose(Size.fromWidth(400)), constraints: .loose(.fromWidth(400)),
child: Padding( child: Padding(
padding: EdgeInsetsGeometry.all(24), padding: .all(24),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: .min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: .start,
spacing: 8, spacing: 8,
children: [ children: [
Row( Row(
spacing: 12, spacing: 12,
mainAxisSize: MainAxisSize.min, mainAxisSize: .min,
children: [ children: [
if (room.metadata?.avatar != null) if (room.metadata?.avatar != null)
ExpandableImage( ExpandableImage(
@ -71,7 +71,7 @@ class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget {
Expanded( Expanded(
child: Text( child: Text(
room.metadata?.name ?? "Unnamed Room", room.metadata?.name ?? "Unnamed Room",
overflow: TextOverflow.ellipsis, overflow: .ellipsis,
maxLines: 3, maxLines: 3,
style: Theme.of(context).textTheme.headlineSmall, style: Theme.of(context).textTheme.headlineSmall,
), ),
@ -105,18 +105,18 @@ class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget {
title: room == null title: room == null
? null ? null
: Column( : Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: .start,
children: [ children: [
Text( Text(
room.metadata?.name ?? "Unnamed Room", room.metadata?.name ?? "Unnamed Room",
overflow: TextOverflow.ellipsis, overflow: .ellipsis,
maxLines: 1, maxLines: 1,
), ),
if (room.metadata?.topic?.isNotEmpty == true) if (room.metadata?.topic?.isNotEmpty == true)
Text( Text(
room.metadata!.topic!, room.metadata!.topic!,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: .ellipsis,
style: Theme.of(context).textTheme.labelMedium?.copyWith( style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant, 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/room_chat_controller.dart";
import "package:nexus/controllers/via_controller.dart"; import "package:nexus/controllers/via_controller.dart";
import "package:nexus/models/configs/power_level_config.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/content/message.dart";
import "package:nexus/models/event.dart"; import "package:nexus/models/event.dart";
import "package:nexus/models/relation_type.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/composer/composer.dart";
import "package:nexus/widgets/emoji_picker_button.dart"; import "package:nexus/widgets/emoji_picker_button.dart";
import "package:nexus/widgets/renderers/event.dart"; import "package:nexus/widgets/renderers/event.dart";
@ -102,8 +100,7 @@ class RoomChat extends HookConsumerWidget {
final composerNode = useFocusNode( final composerNode = useFocusNode(
onKeyEvent: (_, event) { onKeyEvent: (_, event) {
if (event is KeyDownEvent && if (event is KeyDownEvent && event.logicalKey == .escape) {
event.logicalKey == LogicalKeyboardKey.escape) {
relatedEvent.value = null; relatedEvent.value = null;
return KeyEventResult.handled; return KeyEventResult.handled;
} }
@ -118,7 +115,7 @@ class RoomChat extends HookConsumerWidget {
return [ return [
if (ref.watch( if (ref.watch(
PowerLevelController.provider( PowerLevelController.provider(
PowerLevelConfig(eventType: EventType.reaction, roomId: roomId), .new(eventType: .reaction, roomId: roomId),
), ),
)) ))
PopupMenuItem( PopupMenuItem(
@ -134,7 +131,7 @@ class RoomChat extends HookConsumerWidget {
value["m.recent_emoji"] value["m.recent_emoji"]
?.content["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( if (ref.watch(
PowerLevelController.provider( PowerLevelController.provider(
PowerLevelConfig(eventType: EventType.message, roomId: roomId), PowerLevelConfig(eventType: .message, roomId: roomId),
), ),
)) ))
PopupMenuItem( PopupMenuItem(
onTap: () { onTap: () {
relatedEvent.value = event; relatedEvent.value = event;
relationType.value = RelationType.reply; relationType.value = .reply;
composerNode.requestFocus(); composerNode.requestFocus();
}, },
child: ListTile(leading: Icon(Icons.reply), title: Text("Reply")), child: ListTile(leading: Icon(Icons.reply), title: Text("Reply")),
@ -182,7 +179,7 @@ class RoomChat extends HookConsumerWidget {
PopupMenuItem( PopupMenuItem(
onTap: () { onTap: () {
relatedEvent.value = event; relatedEvent.value = event;
relationType.value = RelationType.edit; relationType.value = .edit;
composerNode.requestFocus(); composerNode.requestFocus();
}, },
child: ListTile(leading: Icon(Icons.edit), title: Text("Edit")), child: ListTile(leading: Icon(Icons.edit), title: Text("Edit")),
@ -207,10 +204,7 @@ class RoomChat extends HookConsumerWidget {
), ),
if (ref.watch( if (ref.watch(
PowerLevelController.provider( PowerLevelController.provider(
PowerLevelConfig.redaction( .redaction(targetUser: event.sender, roomId: roomId),
targetUser: event.sender,
roomId: roomId,
),
), ),
)) ))
PopupMenuItem( PopupMenuItem(
@ -222,8 +216,8 @@ class RoomChat extends HookConsumerWidget {
return AlertDialog( return AlertDialog(
title: Text("Delete Message"), title: Text("Delete Message"),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: .min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: .start,
children: [ children: [
Text( Text(
"Are you sure you want to delete this message? This can not be reversed.", "Are you sure you want to delete this message? This can not be reversed.",
@ -261,7 +255,7 @@ class RoomChat extends HookConsumerWidget {
), ),
child: ListTile( child: ListTile(
leading: Icon(Icons.delete, color: danger), leading: Icon(Icons.delete, color: danger),
title: Text("Delete", style: TextStyle(color: danger)), title: Text("Delete", style: .new(color: danger)),
), ),
), ),
PopupMenuItem( PopupMenuItem(
@ -273,8 +267,8 @@ class RoomChat extends HookConsumerWidget {
return AlertDialog( return AlertDialog(
title: Text("Report"), title: Text("Report"),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: .min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: .start,
children: [ children: [
Text( Text(
"Report this event to your server administrators, who can take action like banning this server or room.", "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( TextButton(
onPressed: () { onPressed: () {
client.reportEvent( client.reportEvent(
ReportRequest( .new(
roomId: roomId, roomId: roomId,
eventId: event.eventId, eventId: event.eventId,
reason: reasonController.text.isEmpty reason: reasonController.text.isEmpty
@ -316,7 +310,7 @@ class RoomChat extends HookConsumerWidget {
), ),
child: ListTile( child: ListTile(
leading: Icon(Icons.report, color: danger), leading: Icon(Icons.report, color: danger),
title: Text("Report", style: TextStyle(color: danger)), title: Text("Report", style: .new(color: danger)),
), ),
), ),
].toIList(); ].toIList();
@ -341,7 +335,7 @@ class RoomChat extends HookConsumerWidget {
children: [ children: [
Positioned.fill( Positioned.fill(
child: Padding( child: Padding(
padding: EdgeInsets.symmetric(horizontal: 12), padding: .symmetric(horizontal: 12),
child: switch (controllerData) { child: switch (controllerData) {
AsyncData(:final value) || AsyncData(:final value) ||
AsyncLoading(:final value?) => CustomScrollView( AsyncLoading(:final value?) => CustomScrollView(
@ -349,9 +343,7 @@ class RoomChat extends HookConsumerWidget {
controller: scrollController, controller: scrollController,
slivers: [ slivers: [
SliverPadding( SliverPadding(
padding: EdgeInsetsGeometry.only( padding: .only(bottom: composerSize.value),
bottom: composerSize.value,
),
), ),
SuperSliverList.builder( SuperSliverList.builder(
@ -371,19 +363,15 @@ class RoomChat extends HookConsumerWidget {
), ),
scrollController: scrollController, scrollController: scrollController,
alignment: 0.5, alignment: 0.5,
duration: (_) => duration: (_) => .new(milliseconds: 700),
Duration(milliseconds: 700),
curve: (_) => Curves.easeInOut, curve: (_) => Curves.easeInOut,
); );
flashingEvent.value = replyId; flashingEvent.value = replyId;
await Future.delayed( await Future.delayed(.new(seconds: 1), () {
Duration(seconds: 1), if (flashingEvent.value == replyId) {
() { flashingEvent.value = null;
if (flashingEvent.value == replyId) { }
flashingEvent.value = null; });
}
},
);
}, },
getEventOptions: getEventOptions, getEventOptions: getEventOptions,
isGrouped: isGrouped:
@ -403,7 +391,7 @@ class RoomChat extends HookConsumerWidget {
SliverToBoxAdapter( SliverToBoxAdapter(
child: Padding( child: Padding(
padding: EdgeInsets.symmetric(vertical: 36), padding: .symmetric(vertical: 36),
child: Center( child: Center(
child: controllerData is AsyncLoading child: controllerData is AsyncLoading
? Loading() ? Loading()

View file

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

View file

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

View file

@ -13,25 +13,24 @@ class UrlPreview extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) => ConstrainedBox( Widget build(BuildContext context, WidgetRef ref) => ConstrainedBox(
constraints: BoxConstraints.loose(Size.fromWidth(400)), constraints: .loose(.fromWidth(400)),
child: ref child: ref
.watch(UrlPreviewController.provider(link)) .watch(UrlPreviewController.provider(link))
.betterWhen( .betterWhen(
data: (preview) => preview == null data: (preview) => preview == null
? SizedBox.shrink() ? SizedBox.shrink()
: InkWell( : InkWell(
onTap: () => ref onTap: () =>
.watch(LaunchHelper.provider) ref.watch(LaunchHelper.provider).launchUrl(.parse(link)),
.launchUrl(Uri.parse(link)),
child: Card( child: Card(
margin: EdgeInsets.symmetric(vertical: 4), margin: .symmetric(vertical: 4),
color: Theme.of( color: Theme.of(
context, context,
).colorScheme.surfaceContainerHighest, ).colorScheme.surfaceContainerHighest,
child: Padding( child: Padding(
padding: EdgeInsetsGeometry.all(16), padding: .all(16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: .start,
spacing: 4, spacing: 4,
children: [ children: [
if (preview.title != null) if (preview.title != null)
@ -45,9 +44,7 @@ class UrlPreview extends ConsumerWidget {
], ],
if (preview.imageUrl != null) if (preview.imageUrl != null)
ClipRRect( ClipRRect(
borderRadius: BorderRadius.all( borderRadius: .all(.circular(8)),
Radius.circular(8),
),
child: Image( child: Image(
errorBuilder: (_, _, _) => SizedBox.shrink(), errorBuilder: (_, _, _) => SizedBox.shrink(),
width: preview.width, width: preview.width,
@ -56,7 +53,7 @@ class UrlPreview extends ConsumerWidget {
ref.watch(CrossCacheController.provider), ref.watch(CrossCacheController.provider),
headers: ref.headers, 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/better_when.dart";
import "package:nexus/helpers/extensions/get_localpart.dart"; import "package:nexus/helpers/extensions/get_localpart.dart";
import "package:nexus/helpers/extensions/mxc_to_https.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/content/membership.dart";
import "package:nexus/models/membership_status.dart";
import "package:nexus/models/requests/membership_action.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/widgets/avatar_or_hash.dart";
import "package:nexus/main.dart"; import "package:nexus/main.dart";
import "package:nexus/widgets/expandable_image.dart"; import "package:nexus/widgets/expandable_image.dart";
@ -39,8 +36,8 @@ class UserPopover extends ConsumerWidget {
return AlertDialog( return AlertDialog(
title: Text("${toBeginningOfSentenceCase(action.name)} $userId"), title: Text("${toBeginningOfSentenceCase(action.name)} $userId"),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: .min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: .start,
children: [ children: [
Text("Are you sure you want to ${action.name} $userId?"), Text("Are you sure you want to ${action.name} $userId?"),
SizedBox(height: 12), SizedBox(height: 12),
@ -62,7 +59,7 @@ class UserPopover extends ConsumerWidget {
Navigator.of(context).pop(); Navigator.of(context).pop();
client client
.setMembership( .setMembership(
SetMembershipRequest( .new(
userId: userId, userId: userId,
roomId: roomId!, roomId: roomId!,
action: action, action: action,
@ -80,20 +77,18 @@ class UserPopover extends ConsumerWidget {
); );
final actionButton = ButtonStyle( final actionButton = ButtonStyle(
padding: WidgetStatePropertyAll( padding: WidgetStatePropertyAll(.symmetric(horizontal: 24, vertical: 18)),
EdgeInsets.symmetric(horizontal: 24, vertical: 18),
),
shape: WidgetStatePropertyAll( shape: WidgetStatePropertyAll(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), RoundedRectangleBorder(borderRadius: .circular(8)),
), ),
); );
return Column( return Column(
spacing: 16, spacing: 16,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: .stretch,
children: [ children: [
Wrap( Wrap(
alignment: WrapAlignment.center, alignment: .center,
spacing: 16, spacing: 16,
runSpacing: 8, runSpacing: 8,
children: [ children: [
@ -126,7 +121,7 @@ class UserPopover extends ConsumerWidget {
.betterWhen( .betterWhen(
loading: SizedBox.shrink, loading: SizedBox.shrink,
data: (profile) => Wrap( data: (profile) => Wrap(
alignment: WrapAlignment.center, alignment: .center,
spacing: 4, spacing: 4,
runSpacing: 4, runSpacing: 4,
children: [ children: [
@ -135,7 +130,7 @@ class UserPopover extends ConsumerWidget {
)) ))
Chip( Chip(
label: Text(pronoun.summary), label: Text(pronoun.summary),
labelStyle: TextStyle( labelStyle: .new(
color: theme.colorScheme.onPrimary, color: theme.colorScheme.onPrimary,
), ),
color: WidgetStatePropertyAll( color: WidgetStatePropertyAll(
@ -145,7 +140,7 @@ class UserPopover extends ConsumerWidget {
if (profile.timezone != null) if (profile.timezone != null)
Chip( Chip(
label: Text(profile.timezone!), label: Text(profile.timezone!),
labelStyle: TextStyle( labelStyle: .new(
color: theme.colorScheme.onPrimary, color: theme.colorScheme.onPrimary,
), ),
color: WidgetStatePropertyAll( color: WidgetStatePropertyAll(
@ -162,7 +157,7 @@ class UserPopover extends ConsumerWidget {
if (userId != ref.watch(ClientStateController.provider)?.userId && if (userId != ref.watch(ClientStateController.provider)?.userId &&
roomId != null) roomId != null)
Wrap( Wrap(
alignment: WrapAlignment.center, alignment: .center,
spacing: 8, spacing: 8,
runSpacing: 8, runSpacing: 8,
children: [ children: [
@ -175,17 +170,17 @@ class UserPopover extends ConsumerWidget {
if (ref.watch( if (ref.watch(
PowerLevelController.provider( PowerLevelController.provider(
PowerLevelConfig.membershipAction( .membershipAction(
action: MembershipAction.kick, action: .kick,
roomId: roomId!, roomId: roomId!,
targetUser: userId, targetUser: userId,
), ),
), ),
) && ) &&
member.status == MembershipStatus.join || member.status == .join ||
member.status == MembershipStatus.invite) member.status == .invite)
FilledButton.icon( FilledButton.icon(
onPressed: () => showMembershipDialog(MembershipAction.kick), onPressed: () => showMembershipDialog(.kick),
label: Text("Kick"), label: Text("Kick"),
style: actionButton.copyWith( style: actionButton.copyWith(
backgroundColor: WidgetStatePropertyAll( backgroundColor: WidgetStatePropertyAll(
@ -199,23 +194,19 @@ class UserPopover extends ConsumerWidget {
), ),
if (ref.watch( if (ref.watch(
PowerLevelController.provider( PowerLevelController.provider(
PowerLevelConfig.membershipAction( .membershipAction(
roomId: roomId!, roomId: roomId!,
action: MembershipAction.ban, action: .ban,
targetUser: userId, targetUser: userId,
), ),
), ),
)) ))
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () => showMembershipDialog( onPressed: () => showMembershipDialog(
member.status == MembershipStatus.ban member.status == .ban ? .unban : .ban,
? MembershipAction.unban
: MembershipAction.ban,
), ),
icon: Icon(Icons.gavel), icon: Icon(Icons.gavel),
label: Text( label: Text(member.status == .ban ? "Unban" : "Ban"),
member.status == MembershipStatus.ban ? "Unban" : "Ban",
),
style: actionButton.copyWith( style: actionButton.copyWith(
backgroundColor: WidgetStatePropertyAll( backgroundColor: WidgetStatePropertyAll(
theme.colorScheme.errorContainer, theme.colorScheme.errorContainer,