wip
This commit is contained in:
parent
6afa169af9
commit
f51d773885
12 changed files with 182 additions and 156 deletions
|
|
@ -1,11 +1,12 @@
|
||||||
import "dart:convert";
|
|
||||||
import "dart:developer";
|
import "dart:developer";
|
||||||
import "dart:ffi";
|
import "dart:ffi";
|
||||||
import "dart:isolate";
|
import "dart:isolate";
|
||||||
import "package:ffi/ffi.dart";
|
import "package:ffi/ffi.dart";
|
||||||
import "package:flutter/foundation.dart";
|
import "package:flutter/foundation.dart";
|
||||||
import "package:nexus/controllers/client_state_controller.dart";
|
import "package:nexus/controllers/client_state_controller.dart";
|
||||||
|
import "package:nexus/controllers/rooms_controller.dart";
|
||||||
import "package:nexus/controllers/sync_status_controller.dart";
|
import "package:nexus/controllers/sync_status_controller.dart";
|
||||||
|
import "package:nexus/controllers/top_level_spaces_controller.dart";
|
||||||
import "package:nexus/helpers/extensions/gomuks_buffer.dart";
|
import "package:nexus/helpers/extensions/gomuks_buffer.dart";
|
||||||
import "package:nexus/models/client_state.dart";
|
import "package:nexus/models/client_state.dart";
|
||||||
import "package:nexus/models/login.dart";
|
import "package:nexus/models/login.dart";
|
||||||
|
|
@ -18,10 +19,8 @@ class ClientController extends AsyncNotifier<int> {
|
||||||
@override
|
@override
|
||||||
Future<int> build() async {
|
Future<int> build() async {
|
||||||
final handle = await Isolate.run(GomuksInit);
|
final handle = await Isolate.run(GomuksInit);
|
||||||
ref.onDispose(() => GomuksDestroy(handle));
|
|
||||||
|
|
||||||
final errorCode = GomuksStart(
|
final callable =
|
||||||
handle,
|
|
||||||
NativeCallable<
|
NativeCallable<
|
||||||
Void Function(Pointer<Char>, Int64, GomuksOwnedBuffer)
|
Void Function(Pointer<Char>, Int64, GomuksOwnedBuffer)
|
||||||
>.listener((
|
>.listener((
|
||||||
|
|
@ -47,7 +46,15 @@ class ClientController extends AsyncNotifier<int> {
|
||||||
break;
|
break;
|
||||||
case "sync_complete":
|
case "sync_complete":
|
||||||
final syncData = SyncData.fromJson(decodedMuksEvent);
|
final syncData = SyncData.fromJson(decodedMuksEvent);
|
||||||
debugPrint(jsonEncode(syncData.toJson()));
|
final roomProvider = RoomsController.provider;
|
||||||
|
|
||||||
|
if (syncData.clearState) ref.invalidate(roomProvider);
|
||||||
|
ref
|
||||||
|
.watch(roomProvider.notifier)
|
||||||
|
.update(syncData.rooms, syncData.leftRooms);
|
||||||
|
ref
|
||||||
|
.watch(TopLevelSpacesController.provider.notifier)
|
||||||
|
.set(syncData.topLevelSpaces);
|
||||||
|
|
||||||
// ref
|
// ref
|
||||||
// .watch(SyncStatusController.provider.notifier)
|
// .watch(SyncStatusController.provider.notifier)
|
||||||
|
|
@ -64,9 +71,12 @@ class ClientController extends AsyncNotifier<int> {
|
||||||
debugger();
|
debugger();
|
||||||
debugPrintStack(stackTrace: stackTrace, label: error.toString());
|
debugPrintStack(stackTrace: stackTrace, label: error.toString());
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.nativeFunction,
|
|
||||||
);
|
ref.onDispose(() => GomuksDestroy(handle));
|
||||||
|
ref.onDispose(callable.close);
|
||||||
|
|
||||||
|
final errorCode = GomuksStart(handle, callable.nativeFunction);
|
||||||
|
|
||||||
if (errorCode == 0) return handle;
|
if (errorCode == 0) return handle;
|
||||||
throw Exception("GomuksStart returned error code $errorCode");
|
throw Exception("GomuksStart returned error code $errorCode");
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,57 @@
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:nexus/controllers/client_controller.dart";
|
import "package:nexus/models/read_receipt.dart";
|
||||||
import "package:nexus/helpers/extensions/get_full_room.dart";
|
import "package:nexus/models/room.dart";
|
||||||
import "package:nexus/models/full_room.dart";
|
|
||||||
|
|
||||||
class RoomsController extends AsyncNotifier<IList<FullRoom>> {
|
class RoomsController extends Notifier<IMap<String, Room>> {
|
||||||
@override
|
@override
|
||||||
Future<IList<FullRoom>> build() async {
|
IMap<String, Room> build() => const IMap.empty();
|
||||||
final client = await ref.watch(ClientController.provider.future);
|
|
||||||
|
|
||||||
ref.onDispose(
|
void update(IMap<String, Room> rooms, ISet<String> leftRooms) {
|
||||||
client.onSync.stream.listen((_) => ref.invalidateSelf()).cancel,
|
final merged = rooms.entries.fold(state, (acc, entry) {
|
||||||
|
final roomId = entry.key;
|
||||||
|
final incoming = entry.value;
|
||||||
|
final existing = acc[roomId];
|
||||||
|
|
||||||
|
return acc.add(
|
||||||
|
roomId,
|
||||||
|
existing?.copyWith(
|
||||||
|
metadata: incoming.metadata ?? existing.metadata,
|
||||||
|
events: existing.events.addAll(incoming.events),
|
||||||
|
state: incoming.state.entries.fold(
|
||||||
|
existing.state,
|
||||||
|
(stateAcc, event) => stateAcc.add(
|
||||||
|
event.key,
|
||||||
|
(stateAcc[event.key] ?? IMap<dynamic, dynamic>()).addAll(
|
||||||
|
event.value,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
timeline: incoming.reset
|
||||||
|
? incoming.timeline
|
||||||
|
: existing.timeline.addAll(incoming.timeline),
|
||||||
|
receipts: incoming.receipts.entries.fold(
|
||||||
|
existing.receipts,
|
||||||
|
(receiptAcc, event) => receiptAcc.add(
|
||||||
|
event.key,
|
||||||
|
(receiptAcc[event.key] ?? IList<ReadReceipt>()).addAll(
|
||||||
|
event.value,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
) ??
|
||||||
|
incoming,
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return IList(await Future.wait(client.rooms.map((room) => room.fullRoom)));
|
final prunedList = leftRooms.fold(
|
||||||
|
merged,
|
||||||
|
(acc, roomId) => acc.remove(roomId),
|
||||||
|
);
|
||||||
|
state = prunedList;
|
||||||
}
|
}
|
||||||
|
|
||||||
static final provider =
|
static final provider = NotifierProvider<RoomsController, IMap<String, Room>>(
|
||||||
AsyncNotifierProvider<RoomsController, IList<FullRoom>>(
|
|
||||||
RoomsController.new,
|
RoomsController.new,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,72 +2,50 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:nexus/controllers/client_controller.dart";
|
import "package:nexus/controllers/client_controller.dart";
|
||||||
import "package:nexus/helpers/extensions/get_full_room.dart";
|
import "package:nexus/controllers/rooms_controller.dart";
|
||||||
import "package:nexus/helpers/extensions/room_to_children.dart";
|
import "package:nexus/controllers/top_level_spaces_controller.dart";
|
||||||
import "package:nexus/models/space.dart";
|
import "package:nexus/models/space.dart";
|
||||||
|
|
||||||
class SpacesController extends AsyncNotifier<IList<Space>> {
|
class SpacesController extends AsyncNotifier<IList<Space>> {
|
||||||
@override
|
@override
|
||||||
Future<IList<Space>> build() async {
|
Future<IList<Space>> build() async {
|
||||||
final client = await ref.watch(ClientController.provider.future);
|
final topLevelSpaceIds = ref.watch(TopLevelSpacesController.provider);
|
||||||
|
final rooms = ref.watch(RoomsController.provider);
|
||||||
|
|
||||||
ref.onDispose(
|
final topLevelSpaces = topLevelSpaceIds
|
||||||
client.onSync.stream.listen((_) => ref.invalidateSelf()).cancel,
|
.map((id) => rooms[id])
|
||||||
);
|
.nonNulls
|
||||||
|
.toIList();
|
||||||
|
|
||||||
final topLevel = IList(
|
final dmRooms = rooms.values
|
||||||
await Future.wait(
|
.where((room) => room.metadata?.dmUserId != null)
|
||||||
client.rooms
|
.toIList();
|
||||||
.where((room) => !room.isDirectChat)
|
|
||||||
|
final topLevelRooms = rooms.values
|
||||||
|
.where((room) => room.metadata?.dmUserId == null)
|
||||||
.where(
|
.where(
|
||||||
(room) => client.rooms
|
(room) => spaceRooms.every(
|
||||||
.where((room) => room.isSpace)
|
(space) =>
|
||||||
.every(
|
space.spaceChildren.every((child) => child.roomId != room.id),
|
||||||
(match) => match.spaceChildren.every(
|
|
||||||
(child) => child.roomId != room.id,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.map((room) => room.fullRoom),
|
.toIList();
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final topLevelSpaces = topLevel.where((r) => r.roomData.isSpace).toIList();
|
|
||||||
final topLevelRooms = topLevel.where((r) => !r.roomData.isSpace).toIList();
|
|
||||||
|
|
||||||
|
// 4️⃣ Combine all into a single IList
|
||||||
return IList([
|
return IList([
|
||||||
Space(
|
Space(
|
||||||
client: client,
|
|
||||||
title: "Home",
|
|
||||||
id: "home",
|
id: "home",
|
||||||
|
title: "Home",
|
||||||
children: topLevelRooms,
|
children: topLevelRooms,
|
||||||
icon: Icons.home,
|
icon: Icons.home,
|
||||||
),
|
),
|
||||||
Space(
|
Space(
|
||||||
client: client,
|
|
||||||
title: "Direct Messages",
|
|
||||||
id: "dms",
|
id: "dms",
|
||||||
children: IList(
|
title: "Direct Messages",
|
||||||
await Future.wait(
|
children: dmRooms,
|
||||||
client.rooms
|
|
||||||
.where((room) => room.isDirectChat)
|
|
||||||
.map((room) => room.fullRoom),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
icon: Icons.person,
|
icon: Icons.person,
|
||||||
),
|
),
|
||||||
...(await Future.wait(
|
...topLevelSpaces,
|
||||||
topLevelSpaces.map(
|
|
||||||
(space) async => Space(
|
|
||||||
client: client,
|
|
||||||
title: space.title,
|
|
||||||
avatar: space.avatar,
|
|
||||||
id: space.roomData.id,
|
|
||||||
roomData: space.roomData,
|
|
||||||
children: IList(await space.roomData.getAllChildren()),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
14
lib/controllers/top_level_spaces_controller.dart
Normal file
14
lib/controllers/top_level_spaces_controller.dart
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
|
||||||
|
class TopLevelSpacesController extends Notifier<IList<String>> {
|
||||||
|
@override
|
||||||
|
IList<String> build() => const IList.empty();
|
||||||
|
|
||||||
|
void set(IList<String> newSpaces) => state = newSpaces;
|
||||||
|
|
||||||
|
static final provider =
|
||||||
|
NotifierProvider<TopLevelSpacesController, IList<String>>(
|
||||||
|
TopLevelSpacesController.new,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import "package:matrix/matrix.dart";
|
|
||||||
|
|
||||||
extension GetHeaders on Client {
|
|
||||||
Map<String, String> get headers => {"authorization": "Bearer $accessToken"};
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import "package:collection/collection.dart";
|
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
|
||||||
import "package:matrix/matrix.dart";
|
|
||||||
import "package:nexus/helpers/extensions/get_full_room.dart";
|
|
||||||
import "package:nexus/models/full_room.dart";
|
|
||||||
|
|
||||||
extension RoomToChildren on Room {
|
|
||||||
Future<IList<FullRoom>> getAllChildren() async {
|
|
||||||
final direct = await Future.wait(
|
|
||||||
spaceChildren
|
|
||||||
.map(
|
|
||||||
(child) => client.rooms
|
|
||||||
.firstWhereOrNull((r) => r.id == child.roomId)
|
|
||||||
?.fullRoom,
|
|
||||||
)
|
|
||||||
.nonNulls,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (await Future.wait(
|
|
||||||
direct.map(
|
|
||||||
(child) async => child.roomData.isSpace
|
|
||||||
? await child.roomData.getAllChildren()
|
|
||||||
: [child],
|
|
||||||
),
|
|
||||||
)).expand((list) => list).toIList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:freezed_annotation/freezed_annotation.dart";
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
import "package:nexus/models/epoch_date_time_converter.dart";
|
import "package:nexus/models/epoch_date_time_converter.dart";
|
||||||
part "event.freezed.dart";
|
part "event.freezed.dart";
|
||||||
|
|
@ -14,10 +15,10 @@ abstract class Event with _$Event {
|
||||||
required String type,
|
required String type,
|
||||||
String? stateKey,
|
String? stateKey,
|
||||||
@EpochDateTimeConverter() required DateTime timestamp,
|
@EpochDateTimeConverter() required DateTime timestamp,
|
||||||
required Map<String, dynamic> content,
|
required IMap<String, dynamic> content,
|
||||||
Map<String, dynamic>? decrypted,
|
IMap<String, dynamic>? decrypted,
|
||||||
String? decryptedType,
|
String? decryptedType,
|
||||||
@Default({}) Map<String, dynamic> unsigned,
|
@Default(IMap.empty()) IMap<String, dynamic> unsigned,
|
||||||
LocalContent? localContent,
|
LocalContent? localContent,
|
||||||
String? transactionId,
|
String? transactionId,
|
||||||
String? redactedBy,
|
String? redactedBy,
|
||||||
|
|
@ -25,7 +26,7 @@ abstract class Event with _$Event {
|
||||||
String? relatesType,
|
String? relatesType,
|
||||||
String? decryptionError,
|
String? decryptionError,
|
||||||
String? sendError,
|
String? sendError,
|
||||||
@Default({}) Map<String, int> reactions,
|
@Default(IMap.empty()) IMap<String, int> reactions,
|
||||||
int? lastEditRowId,
|
int? lastEditRowId,
|
||||||
@UnreadTypeConverter() UnreadType? unreadType,
|
@UnreadTypeConverter() UnreadType? unreadType,
|
||||||
}) = _Event;
|
}) = _Event;
|
||||||
|
|
@ -57,6 +58,7 @@ class UnreadTypeConverter implements JsonConverter<UnreadType?, int?> {
|
||||||
int? toJson(UnreadType? object) => object?.value;
|
int? toJson(UnreadType? object) => object?.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// I think this is correct but I'm not sure, its some type of bitmask.
|
||||||
@immutable
|
@immutable
|
||||||
class UnreadType {
|
class UnreadType {
|
||||||
final int value;
|
final int value;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:freezed_annotation/freezed_annotation.dart";
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
part "lazy_load_summary.freezed.dart";
|
part "lazy_load_summary.freezed.dart";
|
||||||
part "lazy_load_summary.g.dart";
|
part "lazy_load_summary.g.dart";
|
||||||
|
|
@ -5,7 +6,7 @@ part "lazy_load_summary.g.dart";
|
||||||
@freezed
|
@freezed
|
||||||
abstract class LazyLoadSummary with _$LazyLoadSummary {
|
abstract class LazyLoadSummary with _$LazyLoadSummary {
|
||||||
const factory LazyLoadSummary({
|
const factory LazyLoadSummary({
|
||||||
required List<String>? heroes,
|
required IList<String>? heroes,
|
||||||
required int? joinedMemberCount,
|
required int? joinedMemberCount,
|
||||||
required int? invitedMemberCount,
|
required int? invitedMemberCount,
|
||||||
}) = _LazyLoadSummary;
|
}) = _LazyLoadSummary;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:freezed_annotation/freezed_annotation.dart";
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
import "package:nexus/models/event.dart";
|
import "package:nexus/models/event.dart";
|
||||||
import "package:nexus/models/read_receipt.dart";
|
import "package:nexus/models/read_receipt.dart";
|
||||||
|
|
@ -9,14 +10,14 @@ part "room.g.dart";
|
||||||
abstract class Room with _$Room {
|
abstract class Room with _$Room {
|
||||||
const factory Room({
|
const factory Room({
|
||||||
@JsonKey(name: "meta") RoomMetadata? metadata,
|
@JsonKey(name: "meta") RoomMetadata? metadata,
|
||||||
@Default([]) List<TimelineRowTuple> timeline,
|
@Default(IList.empty()) IList<TimelineRowTuple> timeline,
|
||||||
required bool reset,
|
required bool reset,
|
||||||
required Map<String, Map> state,
|
required IMap<String, IMap> state,
|
||||||
// required Map<String, AccountData> accountData,
|
// required IMap<String, AccountData> accountData,
|
||||||
required List<Event> events,
|
required IList<Event> events,
|
||||||
@Default({}) Map<String, List<ReadReceipt>> receipts,
|
@Default(IMap.empty()) IMap<String, IList<ReadReceipt>> receipts,
|
||||||
required bool dismissNotifications,
|
required bool dismissNotifications,
|
||||||
// required List<Notification> notifications,
|
// required IList<Notification> notifications,
|
||||||
}) = _Room;
|
}) = _Room;
|
||||||
|
|
||||||
factory Room.fromJson(Map<String, Object?> json) => _$RoomFromJson(json);
|
factory Room.fromJson(Map<String, Object?> json) => _$RoomFromJson(json);
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,12 @@ part "room_metadata.g.dart";
|
||||||
abstract class RoomMetadata with _$RoomMetadata {
|
abstract class RoomMetadata with _$RoomMetadata {
|
||||||
const factory RoomMetadata({
|
const factory RoomMetadata({
|
||||||
@JsonKey(name: "room_id") required String id,
|
@JsonKey(name: "room_id") required String id,
|
||||||
|
|
||||||
// required CreateEventContent creationContent,
|
// required CreateEventContent creationContent,
|
||||||
// required TombstoneEventContent tombstoneEventContent,
|
// required TombstoneEventContent tombstoneEventContent,
|
||||||
String? name,
|
String? name,
|
||||||
Uri? avatar,
|
Uri? avatar,
|
||||||
|
String? dmUserId,
|
||||||
String? topic,
|
String? topic,
|
||||||
String? canonicalAlias,
|
String? canonicalAlias,
|
||||||
LazyLoadSummary? lazyLoadSummary,
|
LazyLoadSummary? lazyLoadSummary,
|
||||||
|
|
|
||||||
15
lib/models/space.dart
Normal file
15
lib/models/space.dart
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import "package:flutter/widgets.dart";
|
||||||
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
|
import "package:nexus/models/room.dart";
|
||||||
|
part "space.freezed.dart";
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class Space with _$Space {
|
||||||
|
const factory Space({
|
||||||
|
required String id,
|
||||||
|
required String title,
|
||||||
|
IconData? icon,
|
||||||
|
Room? room,
|
||||||
|
required List<Room> children,
|
||||||
|
}) = _Space;
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:freezed_annotation/freezed_annotation.dart";
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
import "package:nexus/models/room.dart";
|
import "package:nexus/models/room.dart";
|
||||||
part "sync_data.freezed.dart";
|
part "sync_data.freezed.dart";
|
||||||
|
|
@ -7,12 +8,12 @@ part "sync_data.g.dart";
|
||||||
abstract class SyncData with _$SyncData {
|
abstract class SyncData with _$SyncData {
|
||||||
const factory SyncData({
|
const factory SyncData({
|
||||||
@Default(false) bool clearState,
|
@Default(false) bool clearState,
|
||||||
// required Map<String, AccountData> accountData,
|
// required IMap<String, AccountData> accountData,
|
||||||
@Default({}) Map<String, Room> rooms,
|
@Default(IMap.empty()) IMap<String, Room> rooms,
|
||||||
@Default([]) List<String> leftRooms,
|
@Default(ISet.empty()) ISet<String> leftRooms,
|
||||||
// required List<InvitedRoom> invitedRooms,
|
// required IList<InvitedRoom> invitedRooms,
|
||||||
// required List<SpaceEdge> spaceEdges,
|
// required IList<SpaceEdge> spaceEdges,
|
||||||
@Default([]) List<String> topLevelSpaces,
|
@Default(IList.empty()) IList<String> topLevelSpaces,
|
||||||
}) = _SyncData;
|
}) = _SyncData;
|
||||||
|
|
||||||
factory SyncData.fromJson(Map<String, Object?> json) =>
|
factory SyncData.fromJson(Map<String, Object?> json) =>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue