Skip to content
Merged
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
30 changes: 25 additions & 5 deletions lib/config/providers/group_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -515,11 +515,13 @@ class GroupsNotifier extends Notifier<GroupsState> {
);

final List<Future<void>> loadTasks = [];
final activePubkey = ref.read(activePubkeyProvider);
if (activePubkey == null || activePubkey.isEmpty) return;

for (final group in groups) {
loadTasks.add(
group
.groupType()
.groupType(accountPubkey: activePubkey)
.then((groupType) {
groupTypes[group.mlsGroupId] = groupType;
})
Expand Down Expand Up @@ -590,7 +592,12 @@ class GroupsNotifier extends Notifier<GroupsState> {
/// Get the appropriate display name for a group
Future<String> _getDisplayNameForGroup(Group group) async {
// For direct messages, use the other member's name
final groupInformation = await getGroupInformation(groupId: group.mlsGroupId);
final activePubkey = ref.read(activePubkeyProvider);
if (activePubkey == null || activePubkey.isEmpty) return '';
final groupInformation = await getGroupInformation(
accountPubkey: activePubkey,
groupId: group.mlsGroupId,
);
if (groupInformation.groupType == GroupType.directMessage) {
try {
final otherMember = getOtherGroupMember(group.mlsGroupId);
Expand Down Expand Up @@ -624,8 +631,13 @@ class GroupsNotifier extends Notifier<GroupsState> {

Future<Map<String, GroupInformation>> _getGroupInformationsMap(List<Group> groups) async {
final groupIds = groups.map((group) => group.mlsGroupId).toList();
final groupInformations = await getGroupsInformations(groupIds: groupIds);
final groupInformationsMap = <String, GroupInformation>{};
final activePubkey = ref.read(activePubkeyProvider);
if (activePubkey == null || activePubkey.isEmpty) return groupInformationsMap;
final groupInformations = await getGroupsInformations(
accountPubkey: activePubkey,
groupIds: groupIds,
);
for (int i = 0; i < groupIds.length && i < groupInformations.length; i++) {
groupInformationsMap[groupIds[i]] = groupInformations[i];
}
Expand Down Expand Up @@ -679,13 +691,21 @@ class GroupsNotifier extends Notifier<GroupsState> {
}

Future<GroupType> getGroupType(Group group) async {
final groupInformation = await getGroupInformation(groupId: group.mlsGroupId);
final activePubkey = ref.read(activePubkeyProvider) ?? '';
final groupInformation = await getGroupInformation(
accountPubkey: activePubkey,
groupId: group.mlsGroupId,
);

return groupInformation.groupType;
}

Future<GroupType> getGroupTypeById(String groupId) async {
final groupInformation = await getGroupInformation(groupId: groupId);
final activePubkey = ref.read(activePubkeyProvider) ?? '';
final groupInformation = await getGroupInformation(
accountPubkey: activePubkey,
groupId: groupId,
);

return groupInformation.groupType;
}
Expand Down
40 changes: 24 additions & 16 deletions lib/src/rust/api/groups.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';

import '../frb_generated.dart';
import '../lib.dart';
import 'error.dart';

// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone`, `clone`, `clone`, `fmt`, `fmt`, `fmt`, `fmt`, `from`, `from`, `from`, `from`
Expand Down Expand Up @@ -65,12 +66,19 @@ Future<void> removeMembersFromGroup({
memberPubkeys: memberPubkeys,
);

Future<GroupInformation> getGroupInformation({required String groupId}) =>
RustLib.instance.api.crateApiGroupsGetGroupInformation(groupId: groupId);
Future<GroupInformation> getGroupInformation({
required String accountPubkey,
required String groupId,
}) => RustLib.instance.api.crateApiGroupsGetGroupInformation(
accountPubkey: accountPubkey,
groupId: groupId,
);

Future<List<GroupInformation>> getGroupsInformations({
required String accountPubkey,
required List<String> groupIds,
}) => RustLib.instance.api.crateApiGroupsGetGroupsInformations(
accountPubkey: accountPubkey,
groupIds: groupIds,
);

Expand All @@ -79,8 +87,8 @@ class Group {
final String nostrGroupId;
final String name;
final String description;
final String? imageUrl;
final Uint8List? imageKey;
final U8Array32? imageHash;
final U8Array32? imageKey;
final List<String> adminPubkeys;
Comment on lines +90 to 92
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Ensure U8Array32 has value semantics (== and hashCode) for stable Group equality.

imageHash/imageKey moved from string/bytes to U8Array32?. If U8Array32 doesn’t override ==/hashCode by contents, Group equality and hashCode will become identity-based for these fields, changing behavior vs prior String equality. Implement content-based equality and hashing in U8Array32 to keep semantics predictable.

Example for lib/src/rust/lib.dart (outside this diff):

import 'dart:typed_data';
import 'package:collection/collection.dart';

class U8Array32 {
  final Uint8List bytes; // length == 32, immutable view
  static const _eq = ListEquality<int>();

  const U8Array32(this.bytes);

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is U8Array32 && _eq.equals(bytes, other.bytes);

  @override
  int get hashCode => _eq.hash(bytes);
}
🤖 Prompt for AI Agents
In lib/src/rust/api/groups.dart around lines 90 to 92 the fields imageHash and
imageKey were changed to U8Array32?, so ensure U8Array32 implements
content-based equality and hashing (not identity) to preserve previous
String/bytes semantics: in the U8Array32 class (e.g., lib/src/rust/lib.dart)
store an immutable Uint8List (length 32), use package:collection ListEquality to
compare bytes, override operator == to compare contents and hashCode to use the
equality's hash function (and also validate length in constructor or create an
immutable view) so Group equality and hashCode remain stable and
value-semantics-based.

final String? lastMessageId;
final DateTime? lastMessageAt;
Expand All @@ -92,7 +100,7 @@ class Group {
required this.nostrGroupId,
required this.name,
required this.description,
this.imageUrl,
this.imageHash,
this.imageKey,
required this.adminPubkeys,
this.lastMessageId,
Expand All @@ -101,25 +109,25 @@ class Group {
required this.state,
});

Future<GroupType> groupType() => RustLib.instance.api.crateApiGroupsGroupGroupType(
that: this,
);
Future<GroupType> groupType({required String accountPubkey}) =>
RustLib.instance.api.crateApiGroupsGroupGroupType(that: this, accountPubkey: accountPubkey);

Future<bool> isDirectMessageType() => RustLib.instance.api.crateApiGroupsGroupIsDirectMessageType(
that: this,
);
Future<bool> isDirectMessageType({required String accountPubkey}) =>
RustLib.instance.api.crateApiGroupsGroupIsDirectMessageType(
that: this,
accountPubkey: accountPubkey,
);

Future<bool> isGroupType() => RustLib.instance.api.crateApiGroupsGroupIsGroupType(
that: this,
);
Future<bool> isGroupType({required String accountPubkey}) =>
RustLib.instance.api.crateApiGroupsGroupIsGroupType(that: this, accountPubkey: accountPubkey);

@override
int get hashCode =>
mlsGroupId.hashCode ^
nostrGroupId.hashCode ^
name.hashCode ^
description.hashCode ^
imageUrl.hashCode ^
imageHash.hashCode ^
imageKey.hashCode ^
adminPubkeys.hashCode ^
lastMessageId.hashCode ^
Expand All @@ -136,7 +144,7 @@ class Group {
nostrGroupId == other.nostrGroupId &&
name == other.name &&
description == other.description &&
imageUrl == other.imageUrl &&
imageHash == other.imageHash &&
imageKey == other.imageKey &&
adminPubkeys == other.adminPubkeys &&
lastMessageId == other.lastMessageId &&
Expand Down
Loading