Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions commet/lib/client/alert.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ class Alert {
class AlertManager {
final NotifyingList<Alert> _alerts = NotifyingList.empty(growable: true);

Stream<int> get onAlertAdded => _alerts.onAdd;
Stream<Alert> get onAlertAdded => _alerts.onAdd;

Stream<int> get onAlertRemoved => _alerts.onRemove;
Stream<Alert> get onAlertRemoved => _alerts.onRemove;

List<Alert> get alerts => _alerts;

Expand Down
10 changes: 2 additions & 8 deletions commet/lib/client/call_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import 'package:commet/client/components/push_notification/notification_content.
import 'package:commet/client/components/push_notification/notification_manager.dart';
import 'package:commet/client/components/voip/voip_component.dart';
import 'package:commet/client/components/voip/voip_session.dart';
import 'package:commet/client/stale_info.dart';
import 'package:commet/config/platform_utils.dart';
import 'package:commet/utils/notifying_list.dart';
import 'package:intl/intl.dart';
Expand Down Expand Up @@ -37,17 +36,14 @@ class CallManager {
NotifyingList.empty(growable: true);

CallManager(this.clientManager) {
clientManager.onClientAdded.stream.listen(_onClientAdded);
clientManager.onClientRemoved.stream.listen(_onClientRemoved);
clientManager.onClientAdded.listen(_onClientAdded);
}

Player? player;
Player? muteSoundPlayer;
Player? unmuteSoundPlayer;

void _onClientAdded(int index) {
var client = clientManager.clients[index];

void _onClientAdded(Client client) {
var voip = client.getComponent<VoipComponent>();
if (voip == null) {
return;
Expand All @@ -57,8 +53,6 @@ class CallManager {
voip.onSessionEnded.listen(onSessionEnded);
}

void _onClientRemoved(StalePeerInfo event) {}

void onClientSessionStarted(VoipSession event) {
var room = event.client.getRoom(event.roomId);
currentSessions.add(event);
Expand Down
16 changes: 8 additions & 8 deletions commet/lib/client/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,28 +97,28 @@ abstract class Client {
List<Room> get singleRooms;

/// Gets list of all rooms
List<Room> get rooms;
Iterable<Room> get rooms;

/// Gets list of all spaces
List<Space> get spaces;
Iterable<Space> get spaces;

/// Gets list of all currently known users
List<Peer> get peers;
Iterable<Peer> get peers;

/// When a room is added, this will be called with the index of the new room
Stream<int> get onRoomAdded;
Stream<Room> get onRoomAdded;

/// When a space is added, this will be called with the index of the new space
Stream<int> get onSpaceAdded;
Stream<Space> get onSpaceAdded;

/// When a room is removed, this will be called with the index of the room which was removed
Stream<int> get onRoomRemoved;
Stream<Room> get onRoomRemoved;

/// When a space is removed, this will be called with the index of the space which was removed
Stream<int> get onSpaceRemoved;
Stream<Space> get onSpaceRemoved;

/// When a new peer is found, this will be called with the index of the new peer
Stream<int> get onPeerAdded;
Stream<Peer> get onPeerAdded;

/// When the client receives an update from the server, this will be called
Stream<void> get onSync;
Expand Down
93 changes: 36 additions & 57 deletions commet/lib/client/client_manager.dart
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I agree that making ClientManager a singleton is necessarily beneficial. with it being a top level variable, it's already globally scoped but has the addition of being nullable. while in most cases its safe to assume the ClientManager has been initialized, in the instances where it isn't being able to use the ? or ! operators is much nicer than having to check something like ClientManager.isInitialized and I would prefer to hit a null operator error, as opposed to having it silently fail when I've assumed ClientManager is initialized, but it hasn't actually been.

There are conditions where ClientManager should not be null and uninitialized, for example updating notifications in the background

Copy link
Contributor Author

@Lama-Thematique Lama-Thematique Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are conditions where ClientManager should not be null and uninitialized, for example updating notifications in the background

Oups, i expected it was always initialized, my bad on this one, tho i still think it's cleaner as a static element maybe have it as static nullable then.

Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,36 @@ import 'package:commet/client/matrix/matrix_client.dart';
import 'package:commet/client/stale_info.dart';
import 'package:commet/client/tasks/client_connection_status_task.dart';
import 'package:commet/main.dart';
import 'package:commet/utils/notifying_list.dart';
import 'package:commet/utils/notifying_map.dart';
import 'package:commet/utils/notifying_sub_map.dart';

class ClientManager {
final Map<String, Client> _clients = {};
final NotifyingMap<String, BaseRoom> _all_rooms = NotifyingMap();

final NotifyingList<Room> _rooms = NotifyingList.empty(growable: true);
final NotifyingMap<String, Client> _clients = NotifyingMap();

final NotifyingList<Space> _spaces = NotifyingList.empty(growable: true);
late final NotifyingMap<String, Room> _rooms =
NotifyingSubMap(_all_rooms, null);

late final NotifyingMap<String, Space> _spaces =
NotifyingSubMap(_all_rooms, null);

NotifyingMap<String, Room> get motifyingMapRoom => _rooms;
NotifyingMap<String, Space> get motifyingMapSpace => _spaces;

final AlertManager alertManager = AlertManager();
late CallManager callManager;

late final DirectMessagesAggregator directMessages;

ClientManager() {
static final ClientManager instance = ClientManager._();

ClientManager._() {
directMessages = DirectMessagesAggregator(this);
callManager = CallManager(this);
}

List<Room> get rooms => _rooms;
Iterable<Room> get rooms => _rooms.values;

List<Room> singleRooms({Client? filterClient}) {
var result = List<Room>.empty(growable: true);
Expand All @@ -46,7 +56,7 @@ class ClientManager {
}
}

if (client.spaces.any((space) => space.containsRoom(room.identifier))) {
if (client.spaces.any((space) => space.containsRoom(room.roomId))) {
continue;
}

Expand All @@ -57,7 +67,7 @@ class ClientManager {
return result;
}

List<Space> get spaces => _spaces;
Iterable<Space> get spaces => _spaces.values;

final List<Client> _clientsList = List.empty(growable: true);
final Map<Client, List<StreamSubscription>> _clientSubscriptions = {};
Expand All @@ -66,18 +76,17 @@ class ClientManager {

late StreamController<void> onSync = StreamController.broadcast();

Stream<int> get onRoomAdded => _rooms.onAdd;
Stream<Room> get onRoomAdded => _rooms.onAdd.map((e) => e.value);

Stream<int> get onRoomRemoved => _rooms.onRemove;
Stream<Room> get onRoomRemoved => _rooms.onRemove.map((e) => e.value);

Stream<int> get onSpaceAdded => _spaces.onAdd;
Stream<Space> get onSpaceAdded => _spaces.onAdd.map((e) => e.value);

Stream<int> get onSpaceRemoved => _spaces.onRemove;
Stream<Space> get onSpaceRemoved => _spaces.onRemove.map((e) => e.value);

late StreamController<int> onClientAdded = StreamController.broadcast();
Stream<Client> get onClientAdded => _clients.onAdd.map((e) => e.value);

late StreamController<StalePeerInfo> onClientRemoved =
StreamController.broadcast();
Stream<Client> get onClientRemoved => _clients.onRemove.map((e) => e.value);

late StreamController<Space> onSpaceUpdated = StreamController.broadcast();
late StreamController<Space> onSpaceChildUpdated =
Expand All @@ -92,14 +101,12 @@ class ClientManager {
// previousValue + element.displayNotificationCount);

static Future<ClientManager> init({bool isBackgroundService = false}) async {
final newClientManager = ClientManager();

await Future.wait([
MatrixClient.loadFromDB(newClientManager,
MatrixClient.loadFromDB(instance,
isBackgroundService: isBackgroundService),
]);

return newClientManager;
return instance;
}

void addClient(Client client) {
Expand All @@ -108,27 +115,16 @@ class ClientManager {

_clientsList.add(client);

for (int i = 0; i < client.rooms.length; i++) {
_onClientAddedRoom(client, i);
}

for (int i = 0; i < client.spaces.length; i++) {
_addSpace(client, i);
for (final e in client.spaces) {
_addSpace(client, e);
}

_clientSubscriptions[client] = [
client.onSync.listen((_) => _synced()),
client.onRoomAdded.listen((index) => _onClientAddedRoom(client, index)),
client.onRoomRemoved
.listen((index) => _onClientRemovedRoom(client, index)),
client.onSpaceAdded.listen((index) => _addSpace(client, index)),
client.onSpaceRemoved
.listen((index) => _onClientRemovedSpace(client, index)),
client.onSpaceAdded.listen((space) => _addSpace(client, space)),
client.connectionStatusChanged.stream
.listen((event) => _onClientConnectionStatusChanged(client, event)),
];

onClientAdded.add(_clients.length - 1);
} catch (error) {}
}

Expand All @@ -146,25 +142,9 @@ class ClientManager {
}
}

void _onClientAddedRoom(Client client, int index) {
rooms.add(client.rooms[index]);
}

void _onClientRemovedRoom(Client client, int index) {
var room = client.rooms[index];
_rooms.remove(room);
}

void _onClientRemovedSpace(Client client, int index) {
var space = client.spaces[index];
_spaces.remove(space);
}

void _addSpace(Client client, int index) {
var space = client.spaces[index];
void _addSpace(Client client, Space space) {
space.onUpdate.listen((_) => spaceUpdated(space));
space.onChildRoomUpdated.listen((_) => spaceChildUpdated(space));
spaces.add(client.spaces[index]);
}

void spaceUpdated(Space space) {
Expand Down Expand Up @@ -196,20 +176,19 @@ class ClientManager {
identifier: client.self!.identifier,
avatar: client.self!.avatar);

for (int i = rooms.length - 1; i >= 0; i--) {
if (rooms[i].client == client) {
rooms.removeAt(i);
for (final room in rooms) {
if (room.client == client) {
_rooms.remove(room.localId);
}
}

for (int i = spaces.length - 1; i >= 0; i--) {
if (spaces[i].client == client) {
spaces.removeAt(i);
for (final space in spaces) {
if (space.client == client) {
_spaces.remove(space.localId);
}
}

await client.logout();
onClientRemoved.add(clientInfo);
_clients.remove(client.identifier);
_clientsList.removeAt(clientIndex);
}
Expand Down
14 changes: 5 additions & 9 deletions commet/lib/client/components/calendar_room/calendar_sync.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,11 @@ class CalendarSync {
Future<void> syncAllClients() async {
Log.i("Client Manager: ${clientManager}");

if (clientManager == null) {
Timer(Duration(seconds: 30), () => syncAllClients());
} else {
await Future.wait([
for (var client in clientManager!.clients) syncClient(client),
]);
await Future.wait([
for (var client in clientManager.clients) syncClient(client),
]);

Timer(Duration(minutes: 30), () => syncAllClients());
}
Timer(Duration(minutes: 30), () => syncAllClients());
}

Future<void> syncClient(Client client) async {
Expand All @@ -53,7 +49,7 @@ class CalendarSync {

if (calendar?.syncedCalendars.value?.isNotEmpty == true) {
Log.i(
"Syncing room calendar from external sources: ${room.identifier}",
"Syncing room calendar from external sources: ${room.roomId}",
);

await calendar!.runCalendarSync();
Expand Down
2 changes: 1 addition & 1 deletion commet/lib/client/components/component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ abstract class NeedsPostLoginInit {
void postLoginInit();

static void doPostLoginInit() {
for (var client in clientManager!.clients) {
for (var client in clientManager.clients) {
if (!client.isLoggedIn()) continue;

var components = client.getAllComponents()!;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import 'dart:async';

import 'package:commet/client/client.dart';
import 'package:commet/client/client_manager.dart';
import 'package:commet/client/components/direct_messages/direct_message_component.dart';
import 'package:commet/client/room.dart';
import 'package:commet/client/stale_info.dart';

class DirectMessagesAggregator implements DirectMessagesInterface {
ClientManager clientManager;
Expand Down Expand Up @@ -34,8 +33,8 @@ class DirectMessagesAggregator implements DirectMessagesInterface {
comp?.onHighlightedRoomsListUpdated.listen(onHighlightedListUpdated);
}

clientManager.onClientAdded.stream.listen(onClientAdded);
clientManager.onClientRemoved.stream.listen(onClientRemoved);
clientManager.onClientAdded.listen(onClientAdded);
clientManager.onClientRemoved.listen(onClientRemoved);
}

void updateDirectMessageRooms() {
Expand All @@ -61,8 +60,7 @@ class DirectMessagesAggregator implements DirectMessagesInterface {
updateDirectMessageRooms();
}

void onClientAdded(int index) {
var client = clientManager.clients[index];
void onClientAdded(Client client) {
final comp = client.getComponent<DirectMessagesComponent>();
if (comp != null) {
comp.onRoomsListUpdated.listen(onClientUpdatedList);
Expand All @@ -71,7 +69,7 @@ class DirectMessagesAggregator implements DirectMessagesInterface {
}
}

void onClientRemoved(StalePeerInfo event) {
void onClientRemoved(Client client) {
updateDirectMessageRooms();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'package:commet/client/components/photo_album_room/photo.dart';

abstract class PhotoAlbumTimeline {
Stream<int> get onAdded;
Stream<int> get onChanged;
Stream<int> get onRemoved;
Stream<Photo> get onAdded;
Stream<Photo> get onChanged;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A potential issue with not being able to get the index of where the change happened, means the UI always has to rebuild the entire list, and can't just update a single widget. It would make it harder to use things like AnimatedList

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebuilding an entire list is the same time complexity as removing a list element as an index, updating a list using indexes is extremely slow already. At worst (animated list) it's the same complexity as before, at best it's one order faster.
And with this change there is many things that can be made faster by reworking ligthly the components.

Copy link
Contributor Author

@Lama-Thematique Lama-Thematique Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also would recommend talking about the notifying list change in here : #647

They are the same commits.

It make it easy to only include that change, as it is more of a bugfix on synchronization that "code quality" change.

Stream<Photo> get onRemoved;

List<Photo> get photos;

Expand Down
Loading