parent
40f5d0f459
commit
f6aaf49164
9 changed files with 1041 additions and 144 deletions
16
lib/models/configs/event.dart
Normal file
16
lib/models/configs/event.dart
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import "package:freezed_annotation/freezed_annotation.dart";
|
||||
|
||||
part "event.freezed.dart";
|
||||
part "event.g.dart";
|
||||
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
@JsonSerializable()
|
||||
class const EventConfig({
|
||||
required final String roomId,
|
||||
required final int eventRowId,
|
||||
}) with _$EventConfig {
|
||||
Map<String, Object?> toJson() => _$EventConfigToJson(this);
|
||||
|
||||
factory EventConfig.fromJson(Map<String, Object?> json) =>
|
||||
_$EventConfigFromJson(json);
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import "package:freezed_annotation/freezed_annotation.dart";
|
||||
|
||||
part "reactions.freezed.dart";
|
||||
part "reactions.g.dart";
|
||||
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
@JsonSerializable()
|
||||
class const ReactionsConfig({
|
||||
required final String roomId,
|
||||
required final int eventRowId,
|
||||
}) with _$ReactionsConfig {
|
||||
Map<String, Object?> toJson() => _$ReactionsConfigToJson(this);
|
||||
|
||||
factory ReactionsConfig.fromJson(Map<String, Object?> json) =>
|
||||
_$ReactionsConfigFromJson(json);
|
||||
}
|
||||
|
|
@ -17,6 +17,9 @@ import "package:nexus/models/content/topic.dart";
|
|||
import "package:nexus/models/content/sticker.dart";
|
||||
import "package:nexus/models/content/history_visibility.dart";
|
||||
import "package:nexus/models/content/image_pack.dart";
|
||||
import "package:nexus/models/content/poll_end.dart";
|
||||
import "package:nexus/models/content/poll_response.dart";
|
||||
import "package:nexus/models/content/poll_start.dart";
|
||||
|
||||
class const Content({final String? parseError, final StackTrace? stackTrace}) {
|
||||
factory Content.fromJson(Map<String, dynamic> json) => const Content();
|
||||
|
|
@ -64,5 +67,11 @@ enum EventType(
|
|||
pinnedEvents("m.room.pinned_events", PinnedEventsContent.fromJson),
|
||||
imagePack("m.room.image_pack", ImagePackContent.fromJson),
|
||||
legacyImagePack("im.ponies.room_emotes", ImagePackContent.fromJson),
|
||||
pollStart("org.matrix.msc3381.poll.start", PollStartContent.fromJson),
|
||||
pollResponse(
|
||||
"org.matrix.msc3381.poll.response",
|
||||
PollResponseContent.fromJson,
|
||||
),
|
||||
pollEnd("org.matrix.msc3381.poll.end", PollEndContent.fromJson),
|
||||
message("m.room.message", MessageContent.fromJson),
|
||||
}
|
||||
|
|
|
|||
12
lib/models/content/poll_end.dart
Normal file
12
lib/models/content/poll_end.dart
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import "package:nexus/models/content/content.dart";
|
||||
|
||||
class const PollEndContent() extends Content {
|
||||
factory PollEndContent.fromJson(Map<String, dynamic> json) =>
|
||||
const PollEndContent();
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {
|
||||
"org.matrix.msc3381.poll.end": {},
|
||||
"org.matrix.msc1767.text": "Ended the poll",
|
||||
};
|
||||
}
|
||||
23
lib/models/content/poll_response.dart
Normal file
23
lib/models/content/poll_response.dart
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:freezed_annotation/freezed_annotation.dart";
|
||||
import "package:nexus/models/content/content.dart";
|
||||
part "poll_response.freezed.dart";
|
||||
part "poll_response.g.dart";
|
||||
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
@JsonSerializable(createToJson: false)
|
||||
class const PollResponseContent({
|
||||
@JsonKey(readValue: PollResponseContent.answersFromJson)
|
||||
final IList<String> answers = const .empty(),
|
||||
}) extends Content with _$PollResponseContent {
|
||||
static List<dynamic>? answersFromJson(Map<dynamic, dynamic> json, _) =>
|
||||
json["org.matrix.msc3381.poll.response"]?["answers"];
|
||||
|
||||
@override
|
||||
Map<String, Object?> toJson() => {
|
||||
"org.matrix.msc3381.poll.response": {"answers": answers.toList()},
|
||||
};
|
||||
|
||||
factory PollResponseContent.fromJson(Map<String, Object?> json) =>
|
||||
_$PollResponseContentFromJson(json);
|
||||
}
|
||||
90
lib/models/content/poll_start.dart
Normal file
90
lib/models/content/poll_start.dart
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import "dart:math";
|
||||
|
||||
import "package:collection/collection.dart";
|
||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:freezed_annotation/freezed_annotation.dart";
|
||||
import "package:nexus/models/content/content.dart";
|
||||
part "poll_start.freezed.dart";
|
||||
part "poll_start.g.dart";
|
||||
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
@JsonSerializable(createToJson: false)
|
||||
class const PollStartContent({
|
||||
@JsonKey(name: "org.matrix.msc3381.poll.start") required final PollStart poll,
|
||||
}) extends Content with _$PollStartContent {
|
||||
@override
|
||||
Map<String, Object?> toJson() => {
|
||||
"org.matrix.msc3381.poll.start": poll.toJson(),
|
||||
// Shown by clients without poll support
|
||||
"org.matrix.msc1767.text": [
|
||||
poll.question,
|
||||
...poll.answers.mapIndexed(
|
||||
(index, answer) => "${index + 1}. ${answer.text}",
|
||||
),
|
||||
].join("\n"),
|
||||
};
|
||||
|
||||
factory PollStartContent.fromJson(Map<String, Object?> json) =>
|
||||
_$PollStartContentFromJson(json);
|
||||
}
|
||||
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
@JsonSerializable()
|
||||
class const PollStart({
|
||||
@JsonKey(readValue: PollStart.questionFromJson) final String question = "",
|
||||
final int? maxSelections,
|
||||
@JsonKey(readValue: PollStart.answersFromJson)
|
||||
final IList<PollAnswer> answers = const .empty(),
|
||||
}) with _$PollStart {
|
||||
static const maxAnswers = 20;
|
||||
|
||||
static String? extensibleTextFromJson(Object? json) => switch (json) {
|
||||
{"org.matrix.msc1767.text": final String text} => text,
|
||||
{"body": final String body} => body,
|
||||
{"org.matrix.msc1767.message": final List message} =>
|
||||
message
|
||||
.whereType<Map>()
|
||||
.map((part) => part["body"])
|
||||
.whereType<String>()
|
||||
.firstOrNull,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
static String? questionFromJson(Map<dynamic, dynamic> json, String key) =>
|
||||
PollStart.extensibleTextFromJson(json[key]);
|
||||
|
||||
static List<dynamic>? answersFromJson(
|
||||
Map<dynamic, dynamic> json,
|
||||
String key,
|
||||
) => (json[key] as List?)?.take(maxAnswers).toList();
|
||||
|
||||
/// [maxSelections], defaulting to 1, and at most the number of answers
|
||||
int get selectionLimit =>
|
||||
(maxSelections ?? 1).clamp(1, max(answers.length, 1));
|
||||
|
||||
Map<String, Object?> toJson() => {
|
||||
..._$PollStartToJson(this),
|
||||
// We don't allow undisclosed polls as they make no sense
|
||||
"kind": "org.matrix.msc3381.poll.disclosed",
|
||||
"question": {"org.matrix.msc1767.text": question},
|
||||
"answers": answers.map((answer) => answer.toJson()).toList(),
|
||||
};
|
||||
|
||||
factory PollStart.fromJson(Map<String, Object?> json) =>
|
||||
_$PollStartFromJson(json);
|
||||
}
|
||||
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
@JsonSerializable(createToJson: false)
|
||||
class const PollAnswer({
|
||||
required final String id,
|
||||
@JsonKey(readValue: PollAnswer.textFromJson) final String text = "",
|
||||
}) with _$PollAnswer {
|
||||
static String? textFromJson(Map<dynamic, dynamic> json, _) =>
|
||||
PollStart.extensibleTextFromJson(json);
|
||||
|
||||
Map<String, Object?> toJson() => {"id": id, "org.matrix.msc1767.text": text};
|
||||
|
||||
factory PollAnswer.fromJson(Map<String, Object?> json) =>
|
||||
_$PollAnswerFromJson(json);
|
||||
}
|
||||
25
lib/models/poll_votes.dart
Normal file
25
lib/models/poll_votes.dart
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
|
||||
class const PollVotes({
|
||||
/// Each user's answers, by user ID
|
||||
final IMap<String, IList<String>> votes = const .empty(),
|
||||
final String? endEventId,
|
||||
final int undecryptable = 0,
|
||||
}) {
|
||||
bool get hasEnded => endEventId != null;
|
||||
|
||||
IMap<String, IList<String>> get votersByAnswer => votes.entries.fold(
|
||||
const .empty(),
|
||||
(acc, entry) => entry.value.fold(
|
||||
acc,
|
||||
(acc, answer) => acc.update(
|
||||
answer,
|
||||
(voters) => voters.add(entry.key),
|
||||
ifAbsent: () => .new([entry.key]),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
int get voterCount =>
|
||||
votes.values.where((answers) => answers.isNotEmpty).length;
|
||||
}
|
||||
|
|
@ -24,6 +24,14 @@ class const Room({
|
|||
@JsonKey(fromJson: Room.eventsJsonToIMap)
|
||||
final IMap<int, Event> events = const .empty(),
|
||||
|
||||
/// [relations] is an IMap of event ID to the eventRowIds relating to it
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
final IMap<String, ISet<int>> relations = const .empty(),
|
||||
|
||||
/// [eventIds] is an IMap of event ID to eventRowId
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
final IMap<String, int> eventIds = const .empty(),
|
||||
|
||||
final bool reset = false,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
final bool resetClientSticky = false,
|
||||
|
|
|
|||
|
|
@ -8,4 +8,7 @@ class const RoomPowerLevels({
|
|||
double levelOf(String userId) => creators.contains(userId)
|
||||
? double.infinity
|
||||
: (content.users[userId] ?? content.usersDefault).toDouble();
|
||||
|
||||
bool canEndPoll(String userId, {required String pollSender}) =>
|
||||
userId == pollSender || levelOf(userId) >= content.redact;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue