some performance improvements
This commit is contained in:
parent
e76c0aac16
commit
e00cd12bb9
10 changed files with 130 additions and 117 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import "dart:ffi";
|
||||
import "dart:io";
|
||||
import "dart:isolate";
|
||||
import "package:collection/collection.dart";
|
||||
import "dart:math";
|
||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:ffi/ffi.dart";
|
||||
import "package:flutter/foundation.dart";
|
||||
|
|
@ -237,9 +237,8 @@ class ClientController extends AsyncNotifier<int> {
|
|||
_sendCommand("set_membership", request.toJson());
|
||||
|
||||
Future<void> markRead(Room room) async {
|
||||
final event = room.events.firstWhereOrNull(
|
||||
(event) => event.rowId == room.timeline.last.eventRowId,
|
||||
);
|
||||
final eventRowId = room.timeline[room.timeline.keys.reduce(max)];
|
||||
final event = eventRowId == null ? null : room.events[eventRowId];
|
||||
if (event == null || room.metadata == null) return;
|
||||
|
||||
await _sendCommand("mark_read", {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class EventController extends AsyncNotifier<Event?> {
|
|||
final room = ref.watch(
|
||||
RoomsController.provider.select((value) => value[request.roomId]),
|
||||
);
|
||||
final event = room?.events.firstWhereOrNull(
|
||||
final event = room?.events.values.firstWhereOrNull(
|
||||
(event) => event.eventId == request.eventId,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ import "package:nexus/models/configs/members_by_status_config.dart";
|
|||
import "package:nexus/models/content/membership.dart";
|
||||
import "package:nexus/models/event.dart";
|
||||
|
||||
class MembersByStatusController extends AsyncNotifier<IList<Event>> {
|
||||
class MembersByStatusController extends AsyncNotifier<ISet<Event>> {
|
||||
final MembersByStatusConfig config;
|
||||
MembersByStatusController(this.config);
|
||||
|
||||
@override
|
||||
Future<IList<Event>> build() => ref.watch(
|
||||
Future<ISet<Event>> build() => ref.watch(
|
||||
MembersController.provider(config.roomId).selectAsync(
|
||||
(members) => members
|
||||
.where(
|
||||
|
|
@ -19,14 +19,14 @@ class MembersByStatusController extends AsyncNotifier<IList<Event>> {
|
|||
_ => false,
|
||||
},
|
||||
)
|
||||
.toIList(),
|
||||
.toISet(),
|
||||
),
|
||||
);
|
||||
|
||||
static final provider =
|
||||
AsyncNotifierProvider.family<
|
||||
MembersByStatusController,
|
||||
IList<Event>,
|
||||
ISet<Event>,
|
||||
MembersByStatusConfig
|
||||
>(MembersByStatusController.new);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import "package:collection/collection.dart";
|
||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:nexus/controllers/client_controller.dart";
|
||||
|
|
@ -7,17 +6,17 @@ import "package:nexus/models/content/content.dart";
|
|||
import "package:nexus/models/event.dart";
|
||||
import "package:nexus/models/requests/get_room_state_request.dart";
|
||||
|
||||
class MembersController extends AsyncNotifier<IList<Event>> {
|
||||
class MembersController extends AsyncNotifier<ISet<Event>> {
|
||||
final String roomId;
|
||||
MembersController(this.roomId);
|
||||
|
||||
@override
|
||||
Future<IList<Event>> build() async {
|
||||
Future<ISet<Event>> build() async {
|
||||
final room = ref.watch(
|
||||
RoomsController.provider.select((value) => value[roomId]),
|
||||
);
|
||||
|
||||
if (room == null) return const IList.empty();
|
||||
if (room == null) return const ISet.empty();
|
||||
|
||||
if (!room.hasFetchedMembers) {
|
||||
final fetchedState = await ref
|
||||
|
|
@ -30,21 +29,18 @@ class MembersController extends AsyncNotifier<IList<Event>> {
|
|||
),
|
||||
);
|
||||
|
||||
ref
|
||||
await ref
|
||||
.read(RoomsController.provider.notifier)
|
||||
.addState(roomId, fetchedState, isMembers: true);
|
||||
}
|
||||
|
||||
return room.state[EventType.membership.type]?.values
|
||||
.map(
|
||||
(rowId) =>
|
||||
room.events.firstWhereOrNull((event) => event.rowId == rowId),
|
||||
)
|
||||
.map((rowId) => room.events[rowId])
|
||||
.nonNulls
|
||||
.toIList() ??
|
||||
const IList.empty();
|
||||
.toISet() ??
|
||||
const ISet.empty();
|
||||
}
|
||||
|
||||
static final provider = AsyncNotifierProvider.autoDispose
|
||||
.family<MembersController, IList<Event>, String>(MembersController.new);
|
||||
.family<MembersController, ISet<Event>, String>(MembersController.new);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import "package:collection/collection.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:nexus/controllers/client_state_controller.dart";
|
||||
import "package:nexus/controllers/rooms_controller.dart";
|
||||
|
|
@ -24,9 +23,9 @@ class PowerLevelController extends Notifier<bool> {
|
|||
RoomsController.provider.select((value) => value[config.roomId]),
|
||||
);
|
||||
|
||||
final event = room?.events.firstWhereOrNull(
|
||||
(event) => event.rowId == room.state[EventType.powerLevels.type]?[""],
|
||||
);
|
||||
final eventRowId = room?.state[EventType.powerLevels.type]?[""];
|
||||
|
||||
final event = eventRowId == null ? null : room?.events[eventRowId];
|
||||
final content = event?.content is PowerLevelsContent
|
||||
? event!.content
|
||||
: PowerLevelsContent();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import "dart:async";
|
||||
import "dart:math";
|
||||
import "package:collection/collection.dart";
|
||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
|
|
@ -33,7 +34,7 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
|
|||
GetRoomStateRequest(roomId: roomId),
|
||||
);
|
||||
|
||||
ref.read(RoomsController.provider.notifier).addState(roomId, state);
|
||||
await ref.read(RoomsController.provider.notifier).addState(roomId, state);
|
||||
}
|
||||
|
||||
// While there are under 30 messages, try up to load more messages until there's no more or we have 20 messages.
|
||||
|
|
@ -41,21 +42,21 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
|
|||
loadOlder();
|
||||
}
|
||||
|
||||
return room.timeline.reversed
|
||||
.map((timeline) {
|
||||
final foundEvent = room.events.firstWhereOrNull(
|
||||
(event) => event.rowId == timeline.eventRowId,
|
||||
);
|
||||
return room.timeline
|
||||
.toEntryIList(compare: (a, b) => (a?.key ?? 0).compareTo(b?.key ?? 0))
|
||||
.map((entry) {
|
||||
if (entry.value == null) return null;
|
||||
|
||||
final editedEvent = foundEvent?.lastEditRowId == 0
|
||||
final foundEvent = room.events[entry.value!];
|
||||
|
||||
final editedEvent =
|
||||
foundEvent == null || foundEvent.lastEditRowId == 0
|
||||
? null
|
||||
: room.events.firstWhereOrNull(
|
||||
(event) => event.rowId == foundEvent?.lastEditRowId,
|
||||
);
|
||||
: room.events[foundEvent.lastEditRowId];
|
||||
|
||||
return foundEvent?.copyWith(
|
||||
content: editedEvent?.content ?? foundEvent.content,
|
||||
);
|
||||
return editedEvent == null
|
||||
? foundEvent
|
||||
: foundEvent?.copyWith(content: editedEvent.content);
|
||||
})
|
||||
.nonNulls
|
||||
.toIList();
|
||||
|
|
@ -72,34 +73,37 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
|
|||
);
|
||||
|
||||
Future<bool> loadOlder() async {
|
||||
final timelineKeys = ref
|
||||
.read(RoomsController.provider.select((value) => value[roomId]))
|
||||
?.timeline
|
||||
.keys;
|
||||
final response = await ref
|
||||
.watch(ClientController.provider.notifier)
|
||||
.paginate(
|
||||
PaginateRequest(
|
||||
roomId: roomId,
|
||||
maxTimelineId: ref
|
||||
.read(RoomsController.provider.select((value) => value[roomId]))
|
||||
?.timeline
|
||||
.firstOrNull
|
||||
?.timelineRowId,
|
||||
maxTimelineId: timelineKeys?.isNotEmpty == true
|
||||
? timelineKeys?.reduce(min)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
|
||||
ref
|
||||
await ref
|
||||
.watch(RoomsController.provider.notifier)
|
||||
.update(
|
||||
IMap({
|
||||
roomId: Room(
|
||||
events: response.events.addAll(response.relatedEvents),
|
||||
events: IMap.fromIterable(
|
||||
response.events.addAll(response.relatedEvents),
|
||||
keyMapper: (event) => event.rowId,
|
||||
valueMapper: (event) => event,
|
||||
),
|
||||
hasMore: response.hasMore,
|
||||
timeline: response.events
|
||||
.map(
|
||||
(event) => TimelineRowTuple(
|
||||
timelineRowId: event.timelineRowId,
|
||||
eventRowId: event.rowId,
|
||||
),
|
||||
)
|
||||
.toIList(),
|
||||
timeline: IMap.fromIterable(
|
||||
response.events,
|
||||
keyMapper: (event) => event.timelineRowId,
|
||||
valueMapper: (event) => event.rowId,
|
||||
),
|
||||
),
|
||||
}),
|
||||
const ISet.empty(),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import "package:collection/collection.dart";
|
||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:nexus/models/event.dart";
|
||||
|
|
@ -9,47 +8,49 @@ class RoomsController extends Notifier<IMap<String, Room>> {
|
|||
@override
|
||||
IMap<String, Room> build() => const IMap.empty();
|
||||
|
||||
void addState(String roomId, IList<Event> state, {bool isMembers = false}) =>
|
||||
update(
|
||||
{
|
||||
roomId: Room(
|
||||
events: state,
|
||||
hasFetchedState: true,
|
||||
hasFetchedMembers: isMembers,
|
||||
state: state.fold(
|
||||
const IMap.empty(),
|
||||
(previousValue, stateEvent) => previousValue.add(
|
||||
stateEvent.type,
|
||||
(previousValue[stateEvent.type] ?? const IMap.empty()).addAll(
|
||||
IMap({
|
||||
if (stateEvent.stateKey != null)
|
||||
stateEvent.stateKey!: stateEvent.rowId,
|
||||
}),
|
||||
),
|
||||
),
|
||||
Future<void> addState(
|
||||
String roomId,
|
||||
IList<Event> state, {
|
||||
bool isMembers = false,
|
||||
}) => update(
|
||||
{
|
||||
roomId: Room(
|
||||
events: IMap.fromEntries(
|
||||
state.map((event) => MapEntry(event.rowId, event)),
|
||||
),
|
||||
hasFetchedState: true,
|
||||
hasFetchedMembers: isMembers,
|
||||
state: state.fold(
|
||||
const IMap.empty(),
|
||||
(previousValue, stateEvent) => previousValue.add(
|
||||
stateEvent.type,
|
||||
(previousValue[stateEvent.type] ?? const IMap.empty()).addAll(
|
||||
IMap({
|
||||
if (stateEvent.stateKey != null)
|
||||
stateEvent.stateKey!: stateEvent.rowId,
|
||||
}),
|
||||
),
|
||||
),
|
||||
}.toIMap(),
|
||||
const ISet.empty(),
|
||||
);
|
||||
),
|
||||
),
|
||||
}.toIMap(),
|
||||
const ISet.empty(),
|
||||
);
|
||||
|
||||
void update(IMap<String, Room> rooms, ISet<String> leftRooms) {
|
||||
Future<void> update(IMap<String, Room> rooms, ISet<String> leftRooms) async {
|
||||
final merged = rooms.entries.fold(state, (acc, entry) {
|
||||
final roomId = entry.key;
|
||||
final incoming = entry.value;
|
||||
final existing = acc[roomId];
|
||||
|
||||
final events = existing?.events.updateById(
|
||||
incoming.events,
|
||||
(item) => item.eventId,
|
||||
);
|
||||
|
||||
return acc.add(
|
||||
roomId,
|
||||
existing?.copyWith(
|
||||
hasMore: incoming.hasMore,
|
||||
metadata: incoming.metadata ?? existing.metadata,
|
||||
events: events!,
|
||||
events: incoming.events.isEmpty
|
||||
? existing.events
|
||||
: existing.events.addAll(incoming.events),
|
||||
state: incoming.state.entries.fold(
|
||||
existing.state,
|
||||
(previousValue, event) => previousValue.add(
|
||||
|
|
@ -64,15 +65,9 @@ class RoomsController extends Notifier<IMap<String, Room>> {
|
|||
incoming.hasFetchedMembers || existing.hasFetchedMembers,
|
||||
hasFetchedState:
|
||||
incoming.hasFetchedState || existing.hasFetchedState,
|
||||
timeline:
|
||||
(incoming.reset
|
||||
? incoming.timeline
|
||||
: existing.timeline.updateById(
|
||||
incoming.timeline,
|
||||
(item) => item.timelineRowId,
|
||||
))
|
||||
.sortedBy((element) => element.timelineRowId)
|
||||
.toIList(),
|
||||
timeline: (incoming.reset
|
||||
? incoming.timeline
|
||||
: existing.timeline.addAll(incoming.timeline)),
|
||||
receipts: incoming.receipts.entries.fold(
|
||||
existing.receipts,
|
||||
(receiptAcc, event) => receiptAcc.add(
|
||||
|
|
|
|||
|
|
@ -25,9 +25,10 @@ class ViaController extends Notifier<String> {
|
|||
|
||||
addUserId(ref.watch(ClientStateController.provider)?.userId);
|
||||
|
||||
final powerLevels = room.events.firstWhereOrNull(
|
||||
(event) => event.rowId == room.state[EventType.powerLevels.type]?[""],
|
||||
);
|
||||
final powerLevelsEventId = room.state[EventType.powerLevels.type]?[""];
|
||||
final powerLevels = powerLevelsEventId == null
|
||||
? null
|
||||
: room.events[powerLevelsEventId];
|
||||
|
||||
if (powerLevels?.content case PowerLevelsContent(:final users)) {
|
||||
for (final userId in users.keys) {
|
||||
|
|
@ -38,9 +39,10 @@ class ViaController extends Notifier<String> {
|
|||
|
||||
final members = room.state[EventType.membership.type]?.values.toIList();
|
||||
for (var i = 0; servers.length < 5; i++) {
|
||||
final member = room.events.firstWhereOrNull(
|
||||
(event) => event.rowId == members?.getOrNull(i),
|
||||
);
|
||||
final membershipEventId = members?.getOrNull(i);
|
||||
final member = membershipEventId == null
|
||||
? null
|
||||
: room.events[membershipEventId];
|
||||
|
||||
if (member?.content case MembershipContent(:final status)) {
|
||||
if (status == MembershipStatus.join) {
|
||||
|
|
|
|||
|
|
@ -8,31 +8,48 @@ part "room.g.dart";
|
|||
|
||||
@freezed
|
||||
abstract class Room with _$Room {
|
||||
static IMap<int, int?> timelineTupleJsonToIMap(List<dynamic> json) =>
|
||||
IMap.fromEntries(
|
||||
json.map(
|
||||
(timelineTuple) => MapEntry(
|
||||
timelineTuple["timeline_rowid"],
|
||||
timelineTuple["event_rowid"],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
static IMap<int, Event> eventsJsonToIMap(List<dynamic> json) =>
|
||||
IMap.fromEntries(
|
||||
json.map((eventJson) {
|
||||
final event = Event.fromJson(eventJson);
|
||||
return MapEntry(event.rowId, event);
|
||||
}),
|
||||
);
|
||||
|
||||
/// [timeline] is an IMap of timelineRowId to eventRowId
|
||||
/// [events] is an IMap of eventRowId to event
|
||||
const factory Room({
|
||||
@JsonKey(name: "meta") RoomMetadata? metadata,
|
||||
@Default(IList.empty()) IList<TimelineRowTuple> timeline,
|
||||
@Default(IMap.empty())
|
||||
@JsonKey(fromJson: Room.timelineTupleJsonToIMap)
|
||||
IMap<int, int?> timeline,
|
||||
|
||||
@Default(IMap.empty())
|
||||
@JsonKey(fromJson: Room.eventsJsonToIMap)
|
||||
IMap<int, Event> events,
|
||||
|
||||
@Default(false) bool reset,
|
||||
@Default(false) bool hasFetchedState,
|
||||
@Default(false) bool hasFetchedMembers,
|
||||
@Default(IMap.empty()) IMap<String, IMap<String, int>> state,
|
||||
// required IMap<String, AccountData> accountData,
|
||||
@Default(IList.empty()) IList<Event> events,
|
||||
|
||||
@Default(IMap.empty()) IMap<String, IList<ReadReceipt>> receipts,
|
||||
@Default(false) bool dismissNotifications,
|
||||
@Default(true) bool hasMore,
|
||||
|
||||
// required IMap<String, AccountData> accountData,
|
||||
// required IList<Notification> notifications,
|
||||
}) = _Room;
|
||||
|
||||
factory Room.fromJson(Map<String, Object?> json) => _$RoomFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class TimelineRowTuple with _$TimelineRowTuple {
|
||||
const factory TimelineRowTuple({
|
||||
@JsonKey(name: "timeline_rowid") required int timelineRowId,
|
||||
@JsonKey(name: "event_rowid") int? eventRowId,
|
||||
}) = _TimelineRowTuple;
|
||||
|
||||
factory TimelineRowTuple.fromJson(Map<String, Object?> json) =>
|
||||
_$TimelineRowTupleFromJson(json);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,14 +81,15 @@ class RoomChat extends HookConsumerWidget {
|
|||
Future<void> listener() async {
|
||||
if (!scrollController.position.atEdge) return;
|
||||
|
||||
final room = ref.watch(
|
||||
RoomsController.provider.select((value) => value[roomId]),
|
||||
);
|
||||
if (room == null) return;
|
||||
|
||||
if (scrollController.position.pixels == 0) {
|
||||
context.mounted;
|
||||
final room = ref.watch(
|
||||
RoomsController.provider.select((value) => value[roomId]),
|
||||
);
|
||||
if (room != null) client.markRead(room);
|
||||
await client.markRead(room);
|
||||
} else {
|
||||
await notifier.loadOlder();
|
||||
if (room.hasMore) await notifier.loadOlder();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue