building, but not yet working
Still a lot to re-implement
This commit is contained in:
parent
1fa050e7ae
commit
cf5d1ad5d9
25 changed files with 255 additions and 466 deletions
|
|
@ -1,4 +1,3 @@
|
||||||
import "dart:developer";
|
|
||||||
import "dart:ffi";
|
import "dart:ffi";
|
||||||
import "dart:io";
|
import "dart:io";
|
||||||
import "dart:isolate";
|
import "dart:isolate";
|
||||||
|
|
@ -123,9 +122,12 @@ class ClientController extends AsyncNotifier<int> {
|
||||||
}
|
}
|
||||||
debugPrint("Finished handling $muksEventType...");
|
debugPrint("Finished handling $muksEventType...");
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
debugPrintStack(stackTrace: stackTrace, label: error.toString());
|
if (kDebugMode) {
|
||||||
debugger();
|
debugPrintStack(stackTrace: stackTrace, label: error.toString());
|
||||||
showError(error, stackTrace);
|
rethrow;
|
||||||
|
} else {
|
||||||
|
showError(error, stackTrace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,14 +39,13 @@ class RoomChatController extends AsyncNotifier<IList<Event>> {
|
||||||
state: state.fold(
|
state: state.fold(
|
||||||
const IMap.empty(),
|
const IMap.empty(),
|
||||||
(previousValue, stateEvent) => previousValue.add(
|
(previousValue, stateEvent) => previousValue.add(
|
||||||
stateEvent.type.type,
|
stateEvent.type,
|
||||||
(previousValue[stateEvent.type.type] ?? const IMap.empty())
|
(previousValue[stateEvent.type] ?? const IMap.empty()).addAll(
|
||||||
.addAll(
|
IMap({
|
||||||
IMap({
|
if (stateEvent.stateKey != null)
|
||||||
if (stateEvent.stateKey != null)
|
stateEvent.stateKey!: stateEvent.rowId,
|
||||||
stateEvent.stateKey!: stateEvent.rowId,
|
}),
|
||||||
}),
|
),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ part "avatar.g.dart";
|
||||||
@freezed
|
@freezed
|
||||||
abstract class AvatarContent extends Content with _$AvatarContent {
|
abstract class AvatarContent extends Content with _$AvatarContent {
|
||||||
AvatarContent._();
|
AvatarContent._();
|
||||||
const factory AvatarContent({ImageInfo? info, Uri? url}) = _AvatarContent;
|
factory AvatarContent({ImageInfo? info, Uri? url}) = _AvatarContent;
|
||||||
|
|
||||||
factory AvatarContent.fromJson(Map<String, Object?> json) =>
|
factory AvatarContent.fromJson(Map<String, Object?> json) =>
|
||||||
_$AvatarContentFromJson(json);
|
_$AvatarContentFromJson(json);
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,8 @@ part "canonical_alias.g.dart";
|
||||||
abstract class CanonicalAliasContent extends Content
|
abstract class CanonicalAliasContent extends Content
|
||||||
with _$CanonicalAliasContent {
|
with _$CanonicalAliasContent {
|
||||||
CanonicalAliasContent._();
|
CanonicalAliasContent._();
|
||||||
const factory CanonicalAliasContent({
|
factory CanonicalAliasContent({String? alias, @Default([]) altAliases}) =
|
||||||
String? alias,
|
_CanonicalAliasContent;
|
||||||
@Default([]) altAliases,
|
|
||||||
}) = _CanonicalAliasContent;
|
|
||||||
|
|
||||||
factory CanonicalAliasContent.fromJson(Map<String, Object?> json) =>
|
factory CanonicalAliasContent.fromJson(Map<String, Object?> json) =>
|
||||||
_$CanonicalAliasContentFromJson(json);
|
_$CanonicalAliasContentFromJson(json);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import "package:collection/collection.dart";
|
import "package:collection/collection.dart";
|
||||||
import "package:freezed_annotation/freezed_annotation.dart";
|
|
||||||
import "package:nexus/models/content/avatar.dart";
|
import "package:nexus/models/content/avatar.dart";
|
||||||
import "package:nexus/models/content/canonical_alias.dart";
|
import "package:nexus/models/content/canonical_alias.dart";
|
||||||
import "package:nexus/models/content/create.dart";
|
import "package:nexus/models/content/create.dart";
|
||||||
|
|
@ -28,7 +27,6 @@ class Content {
|
||||||
Content.fromJson)(eventJson);
|
Content.fromJson)(eventJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonEnum(valueField: "type")
|
|
||||||
enum EventType {
|
enum EventType {
|
||||||
encrypted("m.room.encrypted", Content.fromJson),
|
encrypted("m.room.encrypted", Content.fromJson),
|
||||||
redaction("m.room.redaction", RedactionContent.fromJson),
|
redaction("m.room.redaction", RedactionContent.fromJson),
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ part "create.g.dart";
|
||||||
@freezed
|
@freezed
|
||||||
abstract class CreateContent extends Content with _$CreateContent {
|
abstract class CreateContent extends Content with _$CreateContent {
|
||||||
CreateContent._();
|
CreateContent._();
|
||||||
const factory CreateContent({
|
factory CreateContent({
|
||||||
@JsonKey(name: "creator") String? creatorId,
|
@JsonKey(name: "creator") String? creatorId,
|
||||||
|
|
||||||
@JsonKey(name: "additional_creators")
|
@JsonKey(name: "additional_creators")
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ part "encryption.g.dart";
|
||||||
@freezed
|
@freezed
|
||||||
abstract class EncryptionContent extends Content with _$EncryptionContent {
|
abstract class EncryptionContent extends Content with _$EncryptionContent {
|
||||||
EncryptionContent._();
|
EncryptionContent._();
|
||||||
const factory EncryptionContent({
|
factory EncryptionContent({
|
||||||
required String algorithm,
|
required String algorithm,
|
||||||
|
|
||||||
@JsonKey(name: "rotation_period_ms")
|
@JsonKey(name: "rotation_period_ms")
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ part "join_rules.g.dart";
|
||||||
@freezed
|
@freezed
|
||||||
abstract class JoinRulesContent extends Content with _$JoinRulesContent {
|
abstract class JoinRulesContent extends Content with _$JoinRulesContent {
|
||||||
JoinRulesContent._();
|
JoinRulesContent._();
|
||||||
const factory JoinRulesContent({
|
factory JoinRulesContent({
|
||||||
required JoinRule joinRule,
|
required JoinRule joinRule,
|
||||||
@Default(IList.empty()) IList<AllowCondition> allow,
|
@Default(IList.empty()) IList<AllowCondition> allow,
|
||||||
}) = _JoinRulesContent;
|
}) = _JoinRulesContent;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ part "membership.g.dart";
|
||||||
@freezed
|
@freezed
|
||||||
abstract class MembershipContent extends Content with _$MembershipContent {
|
abstract class MembershipContent extends Content with _$MembershipContent {
|
||||||
MembershipContent._();
|
MembershipContent._();
|
||||||
const factory MembershipContent({
|
factory MembershipContent({
|
||||||
@JsonKey(name: "displayname") required String displayName,
|
@JsonKey(name: "displayname") required String displayName,
|
||||||
@JsonKey(name: "membership") required MembershipStatus status,
|
@JsonKey(name: "membership") required MembershipStatus status,
|
||||||
Uri? avatarUrl,
|
Uri? avatarUrl,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ part "message.g.dart";
|
||||||
@Freezed(unionKey: "msgtype", fallbackUnion: "default")
|
@Freezed(unionKey: "msgtype", fallbackUnion: "default")
|
||||||
abstract class MessageContent extends Content with _$MessageContent {
|
abstract class MessageContent extends Content with _$MessageContent {
|
||||||
MessageContent._();
|
MessageContent._();
|
||||||
const factory MessageContent({
|
factory MessageContent({
|
||||||
required String msgtype,
|
required String msgtype,
|
||||||
required String body,
|
required String body,
|
||||||
String? format,
|
String? format,
|
||||||
|
|
@ -17,7 +17,7 @@ abstract class MessageContent extends Content with _$MessageContent {
|
||||||
}) = TextMessageContent;
|
}) = TextMessageContent;
|
||||||
|
|
||||||
@FreezedUnionValue("m.image")
|
@FreezedUnionValue("m.image")
|
||||||
const factory MessageContent.image({
|
factory MessageContent.image({
|
||||||
required String body,
|
required String body,
|
||||||
String? format,
|
String? format,
|
||||||
String? formattedBody,
|
String? formattedBody,
|
||||||
|
|
@ -28,7 +28,7 @@ abstract class MessageContent extends Content with _$MessageContent {
|
||||||
}) = ImageMessageContent;
|
}) = ImageMessageContent;
|
||||||
|
|
||||||
@FreezedUnionValue("m.file")
|
@FreezedUnionValue("m.file")
|
||||||
const factory MessageContent.file({
|
factory MessageContent.file({
|
||||||
required String body,
|
required String body,
|
||||||
String? format,
|
String? format,
|
||||||
String? formattedBody,
|
String? formattedBody,
|
||||||
|
|
@ -39,7 +39,7 @@ abstract class MessageContent extends Content with _$MessageContent {
|
||||||
}) = FileMessageContent;
|
}) = FileMessageContent;
|
||||||
|
|
||||||
@FreezedUnionValue("m.audio")
|
@FreezedUnionValue("m.audio")
|
||||||
const factory MessageContent.audio({
|
factory MessageContent.audio({
|
||||||
required String body,
|
required String body,
|
||||||
String? format,
|
String? format,
|
||||||
String? formattedBody,
|
String? formattedBody,
|
||||||
|
|
@ -50,7 +50,7 @@ abstract class MessageContent extends Content with _$MessageContent {
|
||||||
}) = AudioMessageContent;
|
}) = AudioMessageContent;
|
||||||
|
|
||||||
@FreezedUnionValue("m.video")
|
@FreezedUnionValue("m.video")
|
||||||
const factory MessageContent.video({
|
factory MessageContent.video({
|
||||||
required String body,
|
required String body,
|
||||||
String? format,
|
String? format,
|
||||||
String? formattedBody,
|
String? formattedBody,
|
||||||
|
|
@ -58,13 +58,11 @@ abstract class MessageContent extends Content with _$MessageContent {
|
||||||
String? filename,
|
String? filename,
|
||||||
AudioInfo? info,
|
AudioInfo? info,
|
||||||
String? url,
|
String? url,
|
||||||
}) = AudioMessageContent;
|
}) = VideoMessageContent;
|
||||||
|
|
||||||
@FreezedUnionValue("m.location")
|
@FreezedUnionValue("m.location")
|
||||||
const factory MessageContent.location({
|
factory MessageContent.location({required String body, required Uri geoUri}) =
|
||||||
required String body,
|
LocationMessageContent;
|
||||||
required Uri geoUri,
|
|
||||||
}) = LocationMessageContent;
|
|
||||||
|
|
||||||
factory MessageContent.fromJson(Map<String, Object?> json) =>
|
factory MessageContent.fromJson(Map<String, Object?> json) =>
|
||||||
_$MessageContentFromJson(json);
|
_$MessageContentFromJson(json);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ part "name.g.dart";
|
||||||
@freezed
|
@freezed
|
||||||
abstract class NameContent extends Content with _$NameContent {
|
abstract class NameContent extends Content with _$NameContent {
|
||||||
NameContent._();
|
NameContent._();
|
||||||
const factory NameContent({required String name}) = _NameContent;
|
factory NameContent({required String name}) = _NameContent;
|
||||||
|
|
||||||
factory NameContent.fromJson(Map<String, Object?> json) =>
|
factory NameContent.fromJson(Map<String, Object?> json) =>
|
||||||
_$NameContentFromJson(json);
|
_$NameContentFromJson(json);
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,8 @@ part "pinned_events.g.dart";
|
||||||
@freezed
|
@freezed
|
||||||
abstract class PinnedEventsContent extends Content with _$PinnedEventsContent {
|
abstract class PinnedEventsContent extends Content with _$PinnedEventsContent {
|
||||||
PinnedEventsContent._();
|
PinnedEventsContent._();
|
||||||
const factory PinnedEventsContent({
|
factory PinnedEventsContent({@Default(IList.empty()) IList<String> pinned}) =
|
||||||
@Default(IList.empty()) IList<String> pinned,
|
_PinnedEventsContent;
|
||||||
}) = _PinnedEventsContent;
|
|
||||||
|
|
||||||
factory PinnedEventsContent.fromJson(Map<String, Object?> json) =>
|
factory PinnedEventsContent.fromJson(Map<String, Object?> json) =>
|
||||||
_$PinnedEventsContentFromJson(json);
|
_$PinnedEventsContentFromJson(json);
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ part "power_levels.g.dart";
|
||||||
abstract class PowerLevelsContent extends Content with _$PowerLevelsContent {
|
abstract class PowerLevelsContent extends Content with _$PowerLevelsContent {
|
||||||
PowerLevelsContent._();
|
PowerLevelsContent._();
|
||||||
|
|
||||||
const factory PowerLevelsContent({
|
factory PowerLevelsContent({
|
||||||
@Default(IMap.empty()) IMap<String, int> events,
|
@Default(IMap.empty()) IMap<String, int> events,
|
||||||
@Default(IMap.empty()) IMap<String, int> users,
|
@Default(IMap.empty()) IMap<String, int> users,
|
||||||
Notifications? notifications,
|
Notifications? notifications,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ String? keyFromJson(Map<String, dynamic> json) => json["m.relates_to"]?["key"];
|
||||||
@freezed
|
@freezed
|
||||||
abstract class ReactionContent extends Content with _$ReactionContent {
|
abstract class ReactionContent extends Content with _$ReactionContent {
|
||||||
ReactionContent._();
|
ReactionContent._();
|
||||||
const factory ReactionContent({@JsonKey(fromJson: keyFromJson) String? key}) =
|
factory ReactionContent({@JsonKey(fromJson: keyFromJson) String? key}) =
|
||||||
_ReactionContent;
|
_ReactionContent;
|
||||||
|
|
||||||
factory ReactionContent.fromJson(Map<String, Object?> json) =>
|
factory ReactionContent.fromJson(Map<String, Object?> json) =>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ part "redaction.g.dart";
|
||||||
@freezed
|
@freezed
|
||||||
abstract class RedactionContent extends Content with _$RedactionContent {
|
abstract class RedactionContent extends Content with _$RedactionContent {
|
||||||
RedactionContent._();
|
RedactionContent._();
|
||||||
const factory RedactionContent({String? reason, String? redacts}) =
|
factory RedactionContent({String? reason, String? redacts}) =
|
||||||
_RedactionContent;
|
_RedactionContent;
|
||||||
|
|
||||||
factory RedactionContent.fromJson(Map<String, Object?> json) =>
|
factory RedactionContent.fromJson(Map<String, Object?> json) =>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ part "server_acl.g.dart";
|
||||||
@freezed
|
@freezed
|
||||||
abstract class ServerACLContent extends Content with _$ServerACLContent {
|
abstract class ServerACLContent extends Content with _$ServerACLContent {
|
||||||
ServerACLContent._();
|
ServerACLContent._();
|
||||||
const factory ServerACLContent({
|
factory ServerACLContent({
|
||||||
@Default(IList.empty()) IList<String> allow,
|
@Default(IList.empty()) IList<String> allow,
|
||||||
@Default(IList.empty()) IList<String> deny,
|
@Default(IList.empty()) IList<String> deny,
|
||||||
@Default(true) allowIpLiterals,
|
@Default(true) allowIpLiterals,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ part "topic.g.dart";
|
||||||
@freezed
|
@freezed
|
||||||
abstract class TopicContent extends Content with _$TopicContent {
|
abstract class TopicContent extends Content with _$TopicContent {
|
||||||
TopicContent._();
|
TopicContent._();
|
||||||
const factory TopicContent({
|
factory TopicContent({
|
||||||
required String topic,
|
required String topic,
|
||||||
@JsonKey(name: "m.topic") TopicContentBlock? content,
|
@JsonKey(name: "m.topic") TopicContentBlock? content,
|
||||||
}) = _TopicContent;
|
}) = _TopicContent;
|
||||||
|
|
@ -18,7 +18,7 @@ abstract class TopicContent extends Content with _$TopicContent {
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
abstract class TopicContentBlock with _$TopicContentBlock {
|
abstract class TopicContentBlock with _$TopicContentBlock {
|
||||||
const factory TopicContentBlock({
|
factory TopicContentBlock({
|
||||||
@Default(IList.empty())
|
@Default(IList.empty())
|
||||||
@JsonKey(name: "m.text")
|
@JsonKey(name: "m.text")
|
||||||
IList<TextualRepresentation> representations,
|
IList<TextualRepresentation> representations,
|
||||||
|
|
@ -30,7 +30,7 @@ abstract class TopicContentBlock with _$TopicContentBlock {
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
abstract class TextualRepresentation with _$TextualRepresentation {
|
abstract class TextualRepresentation with _$TextualRepresentation {
|
||||||
const factory TextualRepresentation({
|
factory TextualRepresentation({
|
||||||
required String body,
|
required String body,
|
||||||
@Default("text/plain") String mimetype,
|
@Default("text/plain") String mimetype,
|
||||||
}) = _TextualRepresentation;
|
}) = _TextualRepresentation;
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import "package:nexus/models/profile.dart";
|
||||||
part "event.freezed.dart";
|
part "event.freezed.dart";
|
||||||
part "event.g.dart";
|
part "event.g.dart";
|
||||||
|
|
||||||
Profile? pmpFromJson(Map<String, dynamic> json) {
|
Profile? pmpFromJson(Map<String, dynamic>? json) {
|
||||||
final pmp = json["content"]?["com.beeper.per_message_profile"];
|
final pmp = json?["content"]?["com.beeper.per_message_profile"];
|
||||||
return pmp == null ? null : Profile.fromJson(pmp);
|
return pmp == null ? null : Profile.fromJson(pmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@ abstract class Event with _$Event {
|
||||||
required String roomId,
|
required String roomId,
|
||||||
required String eventId,
|
required String eventId,
|
||||||
required String sender,
|
required String sender,
|
||||||
required EventType type,
|
required String type,
|
||||||
String? stateKey,
|
String? stateKey,
|
||||||
@EpochDateTimeConverter() required DateTime timestamp,
|
@EpochDateTimeConverter() required DateTime timestamp,
|
||||||
IMap<String, dynamic>? decrypted,
|
IMap<String, dynamic>? decrypted,
|
||||||
|
|
@ -36,7 +36,7 @@ abstract class Event with _$Event {
|
||||||
@JsonKey(name: "last_edit_rowid") int? lastEditRowId,
|
@JsonKey(name: "last_edit_rowid") int? lastEditRowId,
|
||||||
@UnreadTypeConverter() UnreadType? unreadType,
|
@UnreadTypeConverter() UnreadType? unreadType,
|
||||||
@JsonKey(fromJson: pmpFromJson) Profile? pmp,
|
@JsonKey(fromJson: pmpFromJson) Profile? pmp,
|
||||||
@JsonKey(fromJson: Content.fromJson) required Content content,
|
@JsonKey(fromJson: Content.fromEventJson) required Content content,
|
||||||
}) = _Event;
|
}) = _Event;
|
||||||
|
|
||||||
factory Event.fromJson(Map<String, Object?> json) => _$EventFromJson(json);
|
factory Event.fromJson(Map<String, Object?> json) => _$EventFromJson(json);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:nexus/models/event.dart";
|
import "package:nexus/models/event.dart";
|
||||||
|
|
||||||
|
|
@ -5,10 +6,14 @@ class EventText extends StatelessWidget {
|
||||||
final Event event;
|
final Event event;
|
||||||
final bool textOnly;
|
final bool textOnly;
|
||||||
final int? maxLines;
|
final int? maxLines;
|
||||||
|
final VoidCallback? onTapReply;
|
||||||
|
final IList<PopupMenuEntry> Function(Event event)? getEventOptions;
|
||||||
const EventText(
|
const EventText(
|
||||||
this.event, {
|
this.event, {
|
||||||
|
this.onTapReply,
|
||||||
this.textOnly = false,
|
this.textOnly = false,
|
||||||
this.maxLines,
|
this.maxLines,
|
||||||
|
this.getEventOptions,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
import "package:flutter/material.dart";
|
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
|
||||||
import "package:nexus/controllers/event_controller.dart";
|
|
||||||
import "package:nexus/controllers/message_controller.dart";
|
|
||||||
import "package:nexus/controllers/selected_room_controller.dart";
|
|
||||||
import "package:nexus/helpers/extensions/better_when.dart";
|
|
||||||
import "package:nexus/models/configs/message_config.dart";
|
|
||||||
import "package:nexus/models/requests/get_event_request.dart";
|
|
||||||
import "package:nexus/widgets/chat_page/html/quoted.dart";
|
|
||||||
import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart";
|
|
||||||
import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart";
|
|
||||||
|
|
||||||
typedef OnTapReply = void Function(Message message)?;
|
|
||||||
|
|
||||||
class ReplyWidget extends ConsumerWidget {
|
|
||||||
final Message message;
|
|
||||||
final bool alwaysShow;
|
|
||||||
final MessageGroupStatus? groupStatus;
|
|
||||||
final OnTapReply onTapReply;
|
|
||||||
const ReplyWidget(
|
|
||||||
this.message, {
|
|
||||||
required this.groupStatus,
|
|
||||||
this.onTapReply,
|
|
||||||
this.alwaysShow = false,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final room = ref.watch(SelectedRoomController.provider);
|
|
||||||
return message.replyToMessageId == null || room == null
|
|
||||||
? SizedBox.shrink()
|
|
||||||
: Padding(
|
|
||||||
padding: EdgeInsets.only(bottom: 12),
|
|
||||||
child: Quoted(
|
|
||||||
ref
|
|
||||||
.watch(
|
|
||||||
EventController.provider(
|
|
||||||
GetEventRequest(
|
|
||||||
room: room,
|
|
||||||
eventId: message.replyToMessageId!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.betterWhen(
|
|
||||||
loading: () => Text("Fetching event..."),
|
|
||||||
data: (event) => event == null
|
|
||||||
? SizedBox.shrink()
|
|
||||||
: ref
|
|
||||||
.watch(
|
|
||||||
MessageController.provider(
|
|
||||||
MessageConfig(room: room, event: event),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.betterWhen(
|
|
||||||
loading: () => Text("Parsing message..."),
|
|
||||||
data: (replyMessage) {
|
|
||||||
if (replyMessage == null) {
|
|
||||||
return SizedBox.shrink();
|
|
||||||
}
|
|
||||||
|
|
||||||
return InkWell(
|
|
||||||
onTap: () => onTapReply?.call(replyMessage),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
spacing: 8,
|
|
||||||
children: [
|
|
||||||
MessageAvatar(replyMessage),
|
|
||||||
Flexible(
|
|
||||||
child: MessageDisplayname(
|
|
||||||
replyMessage,
|
|
||||||
clickable: false,
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelMedium
|
|
||||||
?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Flexible(
|
|
||||||
child: Text(
|
|
||||||
replyMessage.metadata!["body"],
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: Theme.of(
|
|
||||||
context,
|
|
||||||
).textTheme.labelMedium,
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -11,21 +11,21 @@ import "package:nexus/controllers/selected_room_controller.dart";
|
||||||
import "package:nexus/controllers/room_chat_controller.dart";
|
import "package:nexus/controllers/room_chat_controller.dart";
|
||||||
import "package:nexus/controllers/via_controller.dart";
|
import "package:nexus/controllers/via_controller.dart";
|
||||||
import "package:nexus/helpers/extensions/better_when.dart";
|
import "package:nexus/helpers/extensions/better_when.dart";
|
||||||
import "package:nexus/helpers/extensions/show_context_menu.dart";
|
|
||||||
import "package:nexus/models/configs/power_level_config.dart";
|
import "package:nexus/models/configs/power_level_config.dart";
|
||||||
import "package:nexus/models/content/content.dart";
|
import "package:nexus/models/content/content.dart";
|
||||||
|
import "package:nexus/models/content/message.dart";
|
||||||
|
import "package:nexus/models/event.dart";
|
||||||
import "package:nexus/models/relation_type.dart";
|
import "package:nexus/models/relation_type.dart";
|
||||||
import "package:nexus/models/requests/report_request.dart";
|
import "package:nexus/models/requests/report_request.dart";
|
||||||
import "package:nexus/widgets/chat_page/composer/chat_box.dart";
|
import "package:nexus/widgets/chat_page/composer/chat_box.dart";
|
||||||
import "package:nexus/widgets/chat_page/emoji_picker_button.dart";
|
import "package:nexus/widgets/chat_page/emoji_picker_button.dart";
|
||||||
import "package:nexus/widgets/chat_page/expandable_image_message.dart";
|
import "package:nexus/widgets/chat_page/event_text.dart";
|
||||||
import "package:nexus/widgets/chat_page/member_list.dart";
|
import "package:nexus/widgets/chat_page/member_list.dart";
|
||||||
import "package:nexus/widgets/chat_page/wrappers/message_wrapper.dart";
|
|
||||||
import "package:nexus/widgets/chat_page/room_appbar.dart";
|
import "package:nexus/widgets/chat_page/room_appbar.dart";
|
||||||
import "package:nexus/widgets/chat_page/wrappers/text_message_wrapper.dart";
|
import "package:nexus/widgets/chat_page/wrappers/message_wrapper.dart";
|
||||||
import "package:nexus/widgets/chat_page/reply_widget.dart";
|
|
||||||
import "package:nexus/widgets/form_text_input.dart";
|
import "package:nexus/widgets/form_text_input.dart";
|
||||||
import "package:nexus/main.dart";
|
import "package:nexus/main.dart";
|
||||||
|
import "package:super_sliver_list/super_sliver_list.dart";
|
||||||
|
|
||||||
class RoomChat extends HookConsumerWidget {
|
class RoomChat extends HookConsumerWidget {
|
||||||
final bool isDesktop;
|
final bool isDesktop;
|
||||||
|
|
@ -38,17 +38,21 @@ class RoomChat extends HookConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final client = ref.watch(ClientController.provider.notifier);
|
final relatedEvent = useState<Event?>(null);
|
||||||
final relatedMessage = useState<Message?>(null);
|
|
||||||
final memberListOpened = useState<bool>(showMembersByDefault);
|
|
||||||
final relationType = useState(RelationType.reply);
|
final relationType = useState(RelationType.reply);
|
||||||
|
|
||||||
|
final memberListOpened = useState<bool>(showMembersByDefault);
|
||||||
|
|
||||||
|
final listController = useRef(ListController());
|
||||||
|
final scrollController = useScrollController();
|
||||||
|
// TODO: Do things on scroll to top or bottom
|
||||||
|
|
||||||
final userId = ref.watch(ClientStateController.provider)?.userId;
|
final userId = ref.watch(ClientStateController.provider)?.userId;
|
||||||
final roomId = ref.watch(
|
final roomId = ref.watch(
|
||||||
SelectedRoomController.provider.select((value) => value?.metadata?.id),
|
SelectedRoomController.provider.select((value) => value?.metadata?.id),
|
||||||
);
|
);
|
||||||
|
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final danger = theme.colorScheme.error;
|
|
||||||
|
|
||||||
if (roomId == null || userId == null) {
|
if (roomId == null || userId == null) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|
@ -73,7 +77,7 @@ class RoomChat extends HookConsumerWidget {
|
||||||
onKeyEvent: (_, event) {
|
onKeyEvent: (_, event) {
|
||||||
if (event is KeyDownEvent &&
|
if (event is KeyDownEvent &&
|
||||||
event.logicalKey == LogicalKeyboardKey.escape) {
|
event.logicalKey == LogicalKeyboardKey.escape) {
|
||||||
relatedMessage.value = null;
|
relatedEvent.value = null;
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,8 +85,9 @@ class RoomChat extends HookConsumerWidget {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
List<PopupMenuEntry> getMessageOptions(Message message) {
|
IList<PopupMenuEntry> getEventOptions(Event event) {
|
||||||
final isSentByMe = message.sender == userId;
|
final danger = theme.colorScheme.error;
|
||||||
|
final isSentByMe = event.sender == userId;
|
||||||
return [
|
return [
|
||||||
if (ref.watch(
|
if (ref.watch(
|
||||||
PowerLevelController.provider(
|
PowerLevelController.provider(
|
||||||
|
|
@ -113,7 +118,7 @@ class RoomChat extends HookConsumerWidget {
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
await notifier
|
await notifier
|
||||||
.sendReaction(emoji, message)
|
.sendReaction(emoji, event)
|
||||||
.onError(showError);
|
.onError(showError);
|
||||||
},
|
},
|
||||||
icon: Text(emoji),
|
icon: Text(emoji),
|
||||||
|
|
@ -123,7 +128,7 @@ class RoomChat extends HookConsumerWidget {
|
||||||
context: context,
|
context: context,
|
||||||
onPressed: Navigator.of(context).pop,
|
onPressed: Navigator.of(context).pop,
|
||||||
onSelection: (emoji) =>
|
onSelection: (emoji) =>
|
||||||
notifier.sendReaction(emoji, message).onError(showError),
|
notifier.sendReaction(emoji, event).onError(showError),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -135,16 +140,16 @@ class RoomChat extends HookConsumerWidget {
|
||||||
))
|
))
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
relatedMessage.value = message;
|
relatedEvent.value = event;
|
||||||
relationType.value = RelationType.reply;
|
relationType.value = RelationType.reply;
|
||||||
composerNode.requestFocus();
|
composerNode.requestFocus();
|
||||||
},
|
},
|
||||||
child: ListTile(leading: Icon(Icons.reply), title: Text("Reply")),
|
child: ListTile(leading: Icon(Icons.reply), title: Text("Reply")),
|
||||||
),
|
),
|
||||||
if (message is TextMessage && isSentByMe)
|
if (event.content is MessageContent && isSentByMe)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
relatedMessage.value = message;
|
relatedEvent.value = event;
|
||||||
relationType.value = RelationType.edit;
|
relationType.value = RelationType.edit;
|
||||||
composerNode.requestFocus();
|
composerNode.requestFocus();
|
||||||
},
|
},
|
||||||
|
|
@ -160,7 +165,7 @@ class RoomChat extends HookConsumerWidget {
|
||||||
await Clipboard.setData(
|
await Clipboard.setData(
|
||||||
ClipboardData(
|
ClipboardData(
|
||||||
text:
|
text:
|
||||||
"matrix:roomid/${room.metadata?.id.substring(1)}/e/${message.id}$vias)",
|
"matrix:roomid/${room.metadata?.id.substring(1)}/e/${event.eventId}$vias)",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -168,7 +173,7 @@ class RoomChat extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
if (ref.watch(
|
if (ref.watch(
|
||||||
PowerLevelController.provider(
|
PowerLevelController.provider(
|
||||||
PowerLevelConfig.redaction(targetUser: message.authorId),
|
PowerLevelConfig.redaction(targetUser: event.sender),
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
|
|
@ -205,7 +210,7 @@ class RoomChat extends HookConsumerWidget {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
await notifier
|
await notifier
|
||||||
.deleteMessage(
|
.deleteMessage(
|
||||||
message,
|
event,
|
||||||
reason: deleteReasonController.text,
|
reason: deleteReasonController.text,
|
||||||
)
|
)
|
||||||
.onError(showError);
|
.onError(showError);
|
||||||
|
|
@ -254,15 +259,17 @@ class RoomChat extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
client.reportEvent(
|
ref
|
||||||
ReportRequest(
|
.watch(ClientController.provider.notifier)
|
||||||
roomId: roomId,
|
.reportEvent(
|
||||||
eventId: message.id,
|
ReportRequest(
|
||||||
reason: reasonController.text.isEmpty
|
roomId: roomId,
|
||||||
? null
|
eventId: event.eventId,
|
||||||
: reasonController.text,
|
reason: reasonController.text.isEmpty
|
||||||
),
|
? null
|
||||||
);
|
: reasonController.text,
|
||||||
|
),
|
||||||
|
);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
child: Text("Report"),
|
child: Text("Report"),
|
||||||
|
|
@ -277,16 +284,9 @@ class RoomChat extends HookConsumerWidget {
|
||||||
title: Text("Report", style: TextStyle(color: danger)),
|
title: Text("Report", style: TextStyle(color: danger)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
].toIList();
|
||||||
}
|
}
|
||||||
|
|
||||||
final chatTheme = ChatTheme.fromThemeData(theme).copyWith(
|
|
||||||
colors: ChatColors.fromThemeData(theme).copyWith(
|
|
||||||
primary: theme.colorScheme.primaryContainer,
|
|
||||||
onPrimary: theme.colorScheme.onPrimaryContainer,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: RoomAppbar(
|
appBar: RoomAppbar(
|
||||||
isDesktop: isDesktop,
|
isDesktop: isDesktop,
|
||||||
|
|
@ -299,178 +299,55 @@ class RoomChat extends HookConsumerWidget {
|
||||||
body: Row(
|
body: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Positioned.fill(
|
||||||
child: ref
|
child: ref
|
||||||
.watch(controllerProvider)
|
.watch(controllerProvider)
|
||||||
.betterWhen(
|
.betterWhen(
|
||||||
data: (controller) => Chat(
|
data: (events) => SuperListView.builder(
|
||||||
currentUserId: userId,
|
controller: scrollController,
|
||||||
theme: chatTheme,
|
listController: listController.value,
|
||||||
onMessageSecondaryTap:
|
itemCount: events.length,
|
||||||
(
|
itemBuilder: (_, index) => MessageWrapper(
|
||||||
context,
|
events[index],
|
||||||
message, {
|
EventText(
|
||||||
required index,
|
events[index],
|
||||||
TapUpDetails? details,
|
onTapReply: () =>
|
||||||
}) => details?.globalPosition == null
|
listController.value.animateToItem(
|
||||||
? null
|
index: index,
|
||||||
: context.showContextMenu(
|
scrollController: scrollController,
|
||||||
globalPosition: details!.globalPosition,
|
alignment: 0.5,
|
||||||
children: getMessageOptions(message),
|
duration: (_) =>
|
||||||
),
|
Duration(milliseconds: 250),
|
||||||
onMessageLongPress:
|
curve: (_) => Curves.easeInOut,
|
||||||
(
|
),
|
||||||
context,
|
getEventOptions: getEventOptions,
|
||||||
message, {
|
|
||||||
required details,
|
|
||||||
required index,
|
|
||||||
}) => context.showContextMenu(
|
|
||||||
globalPosition: details.globalPosition,
|
|
||||||
children: getMessageOptions(message),
|
|
||||||
),
|
|
||||||
builders: Builders(
|
|
||||||
loadMoreBuilder: (_) => SizedBox.shrink(),
|
|
||||||
|
|
||||||
chatAnimatedListBuilder: (_, itemBuilder) =>
|
|
||||||
ChatAnimatedList(
|
|
||||||
itemBuilder: itemBuilder,
|
|
||||||
onEndReached:
|
|
||||||
ref.watch(
|
|
||||||
SelectedRoomController.provider.select(
|
|
||||||
(room) => room?.hasMore == true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
? notifier.loadOlder
|
|
||||||
: null,
|
|
||||||
onStartReached: () async {
|
|
||||||
final room = ref.watch(
|
|
||||||
SelectedRoomController.provider,
|
|
||||||
);
|
|
||||||
return room == null
|
|
||||||
? null
|
|
||||||
: await client.markRead(room);
|
|
||||||
},
|
|
||||||
bottomPadding: 72,
|
|
||||||
),
|
|
||||||
|
|
||||||
composerBuilder: (_) => ChatBox(
|
|
||||||
node: composerNode,
|
|
||||||
onSend:
|
|
||||||
(
|
|
||||||
text, {
|
|
||||||
required shouldMention,
|
|
||||||
required tags,
|
|
||||||
}) => notifier
|
|
||||||
.send(
|
|
||||||
text,
|
|
||||||
tags: tags,
|
|
||||||
relationType: relationType.value,
|
|
||||||
shouldMention: shouldMention,
|
|
||||||
relation: relatedMessage.value,
|
|
||||||
)
|
|
||||||
.onError(showError),
|
|
||||||
relationType: relationType.value,
|
|
||||||
relatedEvent: relatedMessage.value,
|
|
||||||
onDismiss: () => relatedMessage.value = null,
|
|
||||||
),
|
),
|
||||||
|
// TODO: Reimplement grouping
|
||||||
textMessageBuilder:
|
isGrouped: false,
|
||||||
(
|
// TODO: Reimplement flashing
|
||||||
context,
|
isFlashing: false,
|
||||||
message,
|
|
||||||
index, {
|
|
||||||
required bool isSentByMe,
|
|
||||||
MessageGroupStatus? groupStatus,
|
|
||||||
}) => TextMessageWrapper(
|
|
||||||
message,
|
|
||||||
content: message.text,
|
|
||||||
groupStatus: groupStatus,
|
|
||||||
onTapReply: notifier.scrollToEvent,
|
|
||||||
updateMessage: controller.updateMessage,
|
|
||||||
isSentByMe: isSentByMe,
|
|
||||||
),
|
|
||||||
|
|
||||||
imageMessageBuilder:
|
|
||||||
(
|
|
||||||
context,
|
|
||||||
message,
|
|
||||||
index, {
|
|
||||||
required bool isSentByMe,
|
|
||||||
MessageGroupStatus? groupStatus,
|
|
||||||
}) => TextMessageWrapper(
|
|
||||||
message,
|
|
||||||
content: message.text,
|
|
||||||
groupStatus: groupStatus,
|
|
||||||
onTapReply: notifier.scrollToEvent,
|
|
||||||
updateMessage: controller.updateMessage,
|
|
||||||
isSentByMe: isSentByMe,
|
|
||||||
extra: ExpandableImageMessage(message),
|
|
||||||
),
|
|
||||||
|
|
||||||
fileMessageBuilder:
|
|
||||||
(
|
|
||||||
_,
|
|
||||||
message,
|
|
||||||
index, {
|
|
||||||
required bool isSentByMe,
|
|
||||||
MessageGroupStatus? groupStatus,
|
|
||||||
}) => MessageWrapper(
|
|
||||||
message,
|
|
||||||
InkWell(
|
|
||||||
onTap: () => showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => Dialog(
|
|
||||||
child: Text(
|
|
||||||
"TODO: Download Attachments",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: FlyerChatFileMessage(
|
|
||||||
topWidget: ReplyWidget(
|
|
||||||
message,
|
|
||||||
onTapReply: notifier.scrollToEvent,
|
|
||||||
groupStatus: groupStatus,
|
|
||||||
),
|
|
||||||
message: message,
|
|
||||||
index: index,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
groupStatus,
|
|
||||||
),
|
|
||||||
|
|
||||||
systemMessageBuilder:
|
|
||||||
(
|
|
||||||
_,
|
|
||||||
message,
|
|
||||||
index, {
|
|
||||||
required bool isSentByMe,
|
|
||||||
MessageGroupStatus? groupStatus,
|
|
||||||
}) => FlyerChatSystemMessage(
|
|
||||||
message: message,
|
|
||||||
index: index,
|
|
||||||
),
|
|
||||||
|
|
||||||
unsupportedMessageBuilder:
|
|
||||||
(
|
|
||||||
_,
|
|
||||||
message,
|
|
||||||
index, {
|
|
||||||
required bool isSentByMe,
|
|
||||||
MessageGroupStatus? groupStatus,
|
|
||||||
}) => Text(
|
|
||||||
"${message.sender} sent ${message.metadata?["eventType"]}",
|
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
resolveUser: (_) async => null,
|
|
||||||
chatController: controller,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
ChatBox(
|
||||||
|
node: composerNode,
|
||||||
|
onSend: (text, {required shouldMention, required tags}) =>
|
||||||
|
notifier
|
||||||
|
.send(
|
||||||
|
text,
|
||||||
|
tags: tags,
|
||||||
|
relationType: relationType.value,
|
||||||
|
shouldMention: shouldMention,
|
||||||
|
relation: relatedEvent.value,
|
||||||
|
)
|
||||||
|
.onError(showError),
|
||||||
|
relationType: relationType.value,
|
||||||
|
relatedEvent: relatedEvent.value,
|
||||||
|
onDismiss: () => relatedEvent.value = null,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,32 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:nexus/models/event.dart";
|
||||||
import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart";
|
import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart";
|
||||||
import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart";
|
import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart";
|
||||||
import "package:nexus/widgets/chat_page/wrappers/reaction_row.dart";
|
import "package:nexus/widgets/chat_page/wrappers/reaction_row.dart";
|
||||||
import "package:timeago/timeago.dart";
|
import "package:timeago/timeago.dart";
|
||||||
|
|
||||||
class MessageWrapper extends StatelessWidget {
|
class MessageWrapper extends StatelessWidget {
|
||||||
final Message message;
|
final Event event;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final MessageGroupStatus? groupStatus;
|
final bool isGrouped;
|
||||||
const MessageWrapper(this.message, this.child, this.groupStatus, {super.key});
|
final bool isFlashing;
|
||||||
|
const MessageWrapper(
|
||||||
|
this.event,
|
||||||
|
this.child, {
|
||||||
|
this.isGrouped = false,
|
||||||
|
this.isFlashing = false,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final error = message.metadata?["error"];
|
|
||||||
|
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
padding: message.metadata?["flashing"] == true
|
padding: isFlashing ? EdgeInsets.all(8) : EdgeInsets.all(0),
|
||||||
? EdgeInsets.all(8)
|
color: isFlashing
|
||||||
: EdgeInsets.all(0),
|
|
||||||
color: message.metadata?["flashing"] == true
|
|
||||||
? Theme.of(context).colorScheme.onSurface.withAlpha(50)
|
? Theme.of(context).colorScheme.onSurface.withAlpha(50)
|
||||||
: Colors.transparent,
|
: Colors.transparent,
|
||||||
duration: Duration(milliseconds: 250),
|
duration: Duration(milliseconds: 250),
|
||||||
|
|
@ -29,48 +34,44 @@ class MessageWrapper extends StatelessWidget {
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
groupStatus?.isFirst != false
|
isGrouped ? SizedBox(width: 40) : MessageAvatar(event, height: 40),
|
||||||
? MessageAvatar(message, height: 40)
|
|
||||||
: SizedBox(width: 40),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
spacing: 4,
|
spacing: 4,
|
||||||
children: [
|
children: [
|
||||||
if (groupStatus?.isFirst != false)
|
if (!isGrouped)
|
||||||
Row(
|
Row(
|
||||||
spacing: 4,
|
spacing: 4,
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
child: MessageDisplayname(
|
child: MessageDisplayname(
|
||||||
message,
|
event,
|
||||||
style: theme.textTheme.titleMedium?.copyWith(
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (message.deliveredAt != null &&
|
Tooltip(
|
||||||
groupStatus?.isFirst != false)
|
message: event.timestamp.toString(),
|
||||||
Tooltip(
|
child: Text(
|
||||||
message: message.deliveredAt!.toString(),
|
format(event.timestamp),
|
||||||
child: Text(
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
format(message.deliveredAt!),
|
color: Colors.grey,
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child,
|
child,
|
||||||
if (error != null && error != "not sent")
|
if (event.sendError != null && event.sendError != "not sent")
|
||||||
Text(
|
Text(
|
||||||
error,
|
event.sendError!,
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
color: theme.colorScheme.error,
|
color: theme.colorScheme.error,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ReactionRow(message),
|
ReactionRow(event),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import "package:cross_cache/cross_cache.dart";
|
import "package:cross_cache/cross_cache.dart";
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_hooks/flutter_hooks.dart";
|
import "package:flutter_hooks/flutter_hooks.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
|
@ -10,106 +9,110 @@ import "package:nexus/controllers/selected_room_controller.dart";
|
||||||
import "package:nexus/helpers/extensions/get_headers.dart";
|
import "package:nexus/helpers/extensions/get_headers.dart";
|
||||||
import "package:nexus/helpers/extensions/mxc_to_https.dart";
|
import "package:nexus/helpers/extensions/mxc_to_https.dart";
|
||||||
import "package:nexus/main.dart";
|
import "package:nexus/main.dart";
|
||||||
|
import "package:nexus/models/event.dart";
|
||||||
|
|
||||||
class ReactionRow extends ConsumerWidget {
|
class ReactionRow extends ConsumerWidget {
|
||||||
final Message message;
|
final Event event;
|
||||||
const ReactionRow(this.message, {super.key});
|
const ReactionRow(this.event, {super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final clientState = ref.watch(ClientStateController.provider);
|
final clientState = ref.watch(ClientStateController.provider);
|
||||||
|
|
||||||
return Wrap(
|
return SizedBox.shrink();
|
||||||
spacing: 4,
|
|
||||||
runSpacing: 4,
|
|
||||||
children: clientState?.homeserverUrl == null || message.reactions == null
|
|
||||||
? []
|
|
||||||
: message.reactions!
|
|
||||||
.mapTo(
|
|
||||||
(reaction, reactors) => HookBuilder(
|
|
||||||
builder: (context) {
|
|
||||||
final enabled = useState(true);
|
|
||||||
final selected = reactors.contains(clientState!.userId);
|
|
||||||
return Tooltip(
|
|
||||||
message: reactors.join(", "),
|
|
||||||
child: ChoiceChip(
|
|
||||||
showCheckmark: false,
|
|
||||||
selected: selected,
|
|
||||||
label: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
spacing: 8,
|
|
||||||
children: [
|
|
||||||
Flexible(
|
|
||||||
child: reaction.startsWith("mxc://")
|
|
||||||
? Image(
|
|
||||||
height: 20,
|
|
||||||
image: CachedNetworkImage(
|
|
||||||
headers: ref.headers,
|
|
||||||
Uri.parse(reaction)
|
|
||||||
.mxcToHttps(
|
|
||||||
clientState.homeserverUrl!,
|
|
||||||
)
|
|
||||||
.toString(),
|
|
||||||
ref.watch(
|
|
||||||
CrossCacheController.provider,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Text(
|
|
||||||
reaction,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
reactors.length.toString(),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onSelected: enabled.value
|
|
||||||
? (value) async {
|
|
||||||
enabled.value = false;
|
|
||||||
try {
|
|
||||||
final roomId = ref.watch(
|
|
||||||
SelectedRoomController.provider.select(
|
|
||||||
(value) => value?.metadata?.id,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (roomId == null ||
|
|
||||||
clientState.userId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final controller = ref.watch(
|
// TODO: IMPL
|
||||||
RoomChatController.provider(
|
// return Wrap(
|
||||||
roomId,
|
// spacing: 4,
|
||||||
).notifier,
|
// runSpacing: 4,
|
||||||
);
|
// children: clientState?.homeserverUrl == null
|
||||||
|
// ? []
|
||||||
|
// : event.reactions
|
||||||
|
// .mapTo(
|
||||||
|
// (reaction, reactors) => HookBuilder(
|
||||||
|
// builder: (context) {
|
||||||
|
// final enabled = useState(true);
|
||||||
|
// final selected = reactors.contains(clientState!.userId);
|
||||||
|
// return Tooltip(
|
||||||
|
// message: reactors.join(", "),
|
||||||
|
// child: ChoiceChip(
|
||||||
|
// showCheckmark: false,
|
||||||
|
// selected: selected,
|
||||||
|
// label: Row(
|
||||||
|
// mainAxisSize: MainAxisSize.min,
|
||||||
|
// spacing: 8,
|
||||||
|
// children: [
|
||||||
|
// Flexible(
|
||||||
|
// child: reaction.startsWith("mxc://")
|
||||||
|
// ? Image(
|
||||||
|
// height: 20,
|
||||||
|
// image: CachedNetworkImage(
|
||||||
|
// headers: ref.headers,
|
||||||
|
// Uri.parse(reaction)
|
||||||
|
// .mxcToHttps(
|
||||||
|
// clientState.homeserverUrl!,
|
||||||
|
// )
|
||||||
|
// .toString(),
|
||||||
|
// ref.watch(
|
||||||
|
// CrossCacheController.provider,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// : Text(
|
||||||
|
// reaction,
|
||||||
|
// overflow: TextOverflow.ellipsis,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// Text(
|
||||||
|
// reactors.length.toString(),
|
||||||
|
// overflow: TextOverflow.ellipsis,
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// onSelected: enabled.value
|
||||||
|
// ? (value) async {
|
||||||
|
// enabled.value = false;
|
||||||
|
// try {
|
||||||
|
// final roomId = ref.watch(
|
||||||
|
// SelectedRoomController.provider.select(
|
||||||
|
// (value) => value?.metadata?.id,
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// if (roomId == null ||
|
||||||
|
// clientState.userId == null) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
if (selected) {
|
// final controller = ref.watch(
|
||||||
await controller
|
// RoomChatController.provider(
|
||||||
.removeReaction(
|
// roomId,
|
||||||
reaction,
|
// ).notifier,
|
||||||
message,
|
// );
|
||||||
clientState.userId!,
|
|
||||||
)
|
// if (selected) {
|
||||||
.onError(showError);
|
// await controller
|
||||||
} else {
|
// .removeReaction(
|
||||||
await controller
|
// reaction,
|
||||||
.sendReaction(reaction, message)
|
// event,
|
||||||
.onError(showError);
|
// clientState.userId!,
|
||||||
}
|
// )
|
||||||
} finally {
|
// .onError(showError);
|
||||||
enabled.value = true;
|
// } else {
|
||||||
}
|
// await controller
|
||||||
}
|
// .sendReaction(reaction, event)
|
||||||
: null,
|
// .onError(showError);
|
||||||
),
|
// }
|
||||||
);
|
// } finally {
|
||||||
},
|
// enabled.value = true;
|
||||||
),
|
// }
|
||||||
)
|
// }
|
||||||
.toList(),
|
// : null,
|
||||||
);
|
// ),
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// .toList(),
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1196,6 +1196,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.1"
|
||||||
|
super_sliver_list:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: super_sliver_list
|
||||||
|
sha256: b1e1e64d08ce40e459b9bb5d9f8e361617c26b8c9f3bb967760b0f436b6e3f56
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.1"
|
||||||
synchronized:
|
synchronized:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ dependencies:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Henry-Hiles/emoji_text_field
|
url: https://github.com/Henry-Hiles/emoji_text_field
|
||||||
flutter_blurhash: ^0.9.1
|
flutter_blurhash: ^0.9.1
|
||||||
|
super_sliver_list: ^0.4.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: 2.15.0
|
build_runner: 2.15.0
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue