add invite support

Closes #82
This commit is contained in:
Henry Hiles 2026-10-15 00:35:38 -04:00
commit d37ec2878b
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
21 changed files with 561 additions and 147 deletions

View file

@ -20,6 +20,7 @@ sealed class MembershipContent extends Content with _$MembershipContent {
@JsonKey(name: "membership") required MembershipStatus status,
Uri? avatarUrl,
String? reason,
@Default(false) bool isDirect,
}) = _MembershipContent;
factory MembershipContent.fromJson(Map<String, Object?> json) =>

View file

@ -0,0 +1,16 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:freezed_annotation/freezed_annotation.dart";
import "package:nexus/models/matrix_event.dart";
part "invited_room.freezed.dart";
part "invited_room.g.dart";
@Freezed(toJson: false, fromJson: false)
@JsonSerializable(createToJson: false)
class const InvitedRoom({
required final String roomId,
final IList<MatrixEvent> inviteState = const .empty(),
}) with _$InvitedRoom {
factory InvitedRoom.fromJson(Map<String, Object?> json) =>
_$InvitedRoomFromJson(json);
}

View file

@ -0,0 +1,28 @@
import "package:freezed_annotation/freezed_annotation.dart";
import "package:nexus/models/content/content.dart";
import "package:nexus/models/epoch_date_time_converter.dart";
part "matrix_event.freezed.dart";
part "matrix_event.g.dart";
/// A plain Matrix event, as sent by the server (e.g. stripped invite state).
///
/// Unlike [Event], this isn't stored in the gomuks database, so it has no row
/// IDs or local content.
@Freezed(toJson: false, fromJson: false)
@JsonSerializable(createToJson: false)
class const MatrixEvent({
required final String type,
final String? stateKey,
final String? sender,
final String? eventId,
final String? roomId,
@JsonKey(name: "origin_server_ts")
@EpochDateTimeConverter()
final DateTime? timestamp,
required final Content content,
}) with _$MatrixEvent {
factory MatrixEvent.fromJson(Map<String, dynamic> json) =>
_$MatrixEventFromJson(json)
.copyWith(content: .fromEventJson(json["content"], json["type"]));
}

View file

@ -4,5 +4,6 @@ import "package:flutter_riverpod/flutter_riverpod.dart";
abstract class const NavPage({super.key}) extends Widget {
String get title;
IconData get icon;
Future<int>? badgeBuilder(WidgetRef ref) => null;
int? badgeCount(WidgetRef ref) => null;
}

View file

@ -9,6 +9,9 @@ part "join_room.g.dart";
class const JoinRoomRequest({
required final String roomIdOrAlias,
final IList<String> via = const IList.empty(),
/// Set when accepting an invite, so gomuks can mark DMs as such.
final bool fromInvite = false,
}) with _$JoinRoomRequest {
Map<String, Object?> toJson() => _$JoinRoomRequestToJson(this);

View file

@ -1,12 +1,14 @@
import "package:freezed_annotation/freezed_annotation.dart";
import "package:nexus/models/content/create.dart";
import "package:nexus/models/content/membership.dart";
import "package:nexus/models/join_rule.dart";
import "package:nexus/models/membership_status.dart";
part "room_summary.freezed.dart";
part "room_summary.g.dart";
@Freezed(toJson: false, fromJson: false)
@JsonSerializable()
@JsonSerializable(createToJson: false)
class const RoomSummary({
required final String roomId,
@JsonKey(name: "num_joined_members") final int? joinedMembers,
@ -17,9 +19,24 @@ class const RoomSummary({
final String? topic,
final String? roomVersion,
@JsonKey(unknownEnumValue: RoomType.room) final RoomType? roomType,
}) with _$RoomSummary {
Map<String, Object?> toJson() => _$RoomSummaryToJson(this);
/// The encryption algorithm, if the room is encrypted.
final String? encryption,
final bool? worldReadable,
/// The current user's membership in the room, if any.
@JsonKey(unknownEnumValue: JsonKey.nullForUndefinedEnumValue)
final MembershipStatus? membership,
/// Who invited the current user, for invites.
@JsonKey(includeFromJson: false) final String? invitedBy,
/// The inviter's membership, for their display name and avatar.
@JsonKey(includeFromJson: false) final MembershipContent? inviterProfile,
/// Whether this is an invite to a direct message.
@JsonKey(includeFromJson: false) final bool isDirect = false,
}) with _$RoomSummary {
factory RoomSummary.fromJson(Map<String, Object?> json) =>
_$RoomSummaryFromJson(json);
}

View file

@ -0,0 +1 @@
enum SidebarPage { rooms, notifications, invites }

View file

@ -1,5 +1,6 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:freezed_annotation/freezed_annotation.dart";
import "package:nexus/models/invited_room.dart";
import "package:nexus/models/room.dart";
import "package:nexus/models/space_edge.dart";
@ -7,17 +8,16 @@ part "sync_data.freezed.dart";
part "sync_data.g.dart";
@Freezed(toJson: false, fromJson: false)
@JsonSerializable()
@JsonSerializable(createToJson: false)
class const SyncData({
final bool clearState = false,
final IMap<String, IMap<String, dynamic>> accountData = const IMap.empty(),
final IMap<String, Room> rooms = const IMap.empty(),
final ISet<String> leftRooms = const ISet.empty(),
final IList<InvitedRoom> invitedRooms = const IList.empty(),
final IMap<String, IList<SpaceEdge>>? spaceEdges,
final IList<String>? topLevelSpaces,
}) with _$SyncData {
Map<String, Object?> toJson() => _$SyncDataToJson(this);
factory SyncData.fromJson(Map<String, Object?> json) =>
_$SyncDataFromJson(json);
}