Compare commits
No commits in common. "5032cccf3809010a7d26769429b7e2858cfcb264" and "359cd945328c6476a6a7bde9f9da01f61b236137" have entirely different histories.
5032cccf38
...
359cd94532
@ -119,20 +119,12 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _addUnconfirmedMessage(SnChatMessage message) async {
|
Future<void> _addUnconfirmedMessage(SnChatMessage message) async {
|
||||||
SnChatMessage? quoteEvent;
|
|
||||||
if (message.body['quote_event'] != null) {
|
|
||||||
quoteEvent = await getMessage(message.body['quote_event'] as int);
|
|
||||||
}
|
|
||||||
|
|
||||||
final attachmentRid = List<String>.from(
|
final attachmentRid = List<String>.from(
|
||||||
message.body['attachments']?.cast<String>() ?? [],
|
message.body['attachments']?.cast<String>() ?? [],
|
||||||
);
|
);
|
||||||
final attachments = await _attach.getMultiple(attachmentRid);
|
final attachments = await _attach.getMultiple(attachmentRid);
|
||||||
message = message.copyWith(
|
message = message.copyWith(
|
||||||
preload: SnChatMessagePreload(
|
preload: SnChatMessagePreload(attachments: attachments),
|
||||||
quoteEvent: quoteEvent,
|
|
||||||
attachments: attachments,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
messages.insert(0, message);
|
messages.insert(0, message);
|
||||||
@ -141,20 +133,12 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _addMessage(SnChatMessage message) async {
|
Future<void> _addMessage(SnChatMessage message) async {
|
||||||
SnChatMessage? quoteEvent;
|
|
||||||
if (message.body['quote_event'] != null) {
|
|
||||||
quoteEvent = await getMessage(message.body['quote_event'] as int);
|
|
||||||
}
|
|
||||||
|
|
||||||
final attachmentRid = List<String>.from(
|
final attachmentRid = List<String>.from(
|
||||||
message.body['attachments']?.cast<String>() ?? [],
|
message.body['attachments']?.cast<String>() ?? [],
|
||||||
);
|
);
|
||||||
final attachments = await _attach.getMultiple(attachmentRid);
|
final attachments = await _attach.getMultiple(attachmentRid);
|
||||||
message = message.copyWith(
|
message = message.copyWith(
|
||||||
preload: SnChatMessagePreload(
|
preload: SnChatMessagePreload(attachments: attachments),
|
||||||
quoteEvent: quoteEvent,
|
|
||||||
attachments: attachments,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final idx = messages.indexWhere((e) => e.uuid == message.uuid);
|
final idx = messages.indexWhere((e) => e.uuid == message.uuid);
|
||||||
@ -215,8 +199,8 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
final body = {
|
final body = {
|
||||||
'text': content,
|
'text': content,
|
||||||
'algorithm': 'plain',
|
'algorithm': 'plain',
|
||||||
if (quoteId != null) 'quote_event': quoteId,
|
if (quoteId != null) 'quote_id': quoteId,
|
||||||
if (relatedId != null) 'quote_event': relatedId,
|
if (relatedId != null) 'related_id': relatedId,
|
||||||
if (attachments != null && attachments.isNotEmpty)
|
if (attachments != null && attachments.isNotEmpty)
|
||||||
'attachments': attachments,
|
'attachments': attachments,
|
||||||
};
|
};
|
||||||
@ -285,42 +269,6 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a single event from the current channel
|
|
||||||
/// If it was not found in local storage we will look it up in remote
|
|
||||||
Future<SnChatMessage?> getMessage(int id) async {
|
|
||||||
SnChatMessage? out;
|
|
||||||
if (_box != null && _box!.containsKey(id)) {
|
|
||||||
out = _box!.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (out == null) {
|
|
||||||
try {
|
|
||||||
final resp = await _sn.client
|
|
||||||
.get('/cgi/im/channels/${channel!.keyPath}/events/$id');
|
|
||||||
out = SnChatMessage.fromJson(resp.data);
|
|
||||||
_saveMessageToLocal([out]);
|
|
||||||
} catch (_) {
|
|
||||||
// ignore, maybe not found
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preload some related things if found
|
|
||||||
if (out != null) {
|
|
||||||
await _ud.listAccount([out.sender.accountId]);
|
|
||||||
|
|
||||||
final attachments = await _attach.getMultiple(
|
|
||||||
out.body['attachments']?.cast<String>() ?? [],
|
|
||||||
);
|
|
||||||
out = out.copyWith(
|
|
||||||
preload: SnChatMessagePreload(
|
|
||||||
attachments: attachments,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get message from local storage first, then from the server.
|
/// Get message from local storage first, then from the server.
|
||||||
/// Will not check local storage is up to date with the server.
|
/// Will not check local storage is up to date with the server.
|
||||||
/// If you need to do the sync, do the `checkUpdate` instead.
|
/// If you need to do the sync, do the `checkUpdate` instead.
|
||||||
@ -331,7 +279,7 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
}) async {
|
}) async {
|
||||||
late List<SnChatMessage> out;
|
late List<SnChatMessage> out;
|
||||||
if (_box != null && (_box!.length >= take + offset || forceLocal)) {
|
if (_box != null && (_box!.length >= take + offset || forceLocal)) {
|
||||||
out = _box!.values.skip(offset).take(take).toList().reversed.toList();
|
out = _box!.values.skip(offset).take(take).toList();
|
||||||
} else {
|
} else {
|
||||||
final resp = await _sn.client.get(
|
final resp = await _sn.client.get(
|
||||||
'/cgi/im/channels/${channel!.keyPath}/events',
|
'/cgi/im/channels/${channel!.keyPath}/events',
|
||||||
@ -352,27 +300,18 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
out.expand((e) => (e.body['attachments'] as List<dynamic>?) ?? []),
|
out.expand((e) => (e.body['attachments'] as List<dynamic>?) ?? []),
|
||||||
);
|
);
|
||||||
final attachments = await _attach.getMultiple(attachmentRid);
|
final attachments = await _attach.getMultiple(attachmentRid);
|
||||||
|
out = out.reversed
|
||||||
// Putting preload back to data
|
.map((ele) => ele.copyWith(
|
||||||
for (var i = 0; i < out.length; i++) {
|
|
||||||
// Preload related events (quoted)
|
|
||||||
SnChatMessage? quoteEvent;
|
|
||||||
if (out[i].body['quote_event'] != null) {
|
|
||||||
quoteEvent = await getMessage(out[i].body['quote_event'] as int);
|
|
||||||
}
|
|
||||||
|
|
||||||
out[i] = out[i].copyWith(
|
|
||||||
preload: SnChatMessagePreload(
|
preload: SnChatMessagePreload(
|
||||||
quoteEvent: quoteEvent,
|
|
||||||
attachments: attachments
|
attachments: attachments
|
||||||
.where(
|
.where((e) =>
|
||||||
(ele) =>
|
(ele.body['attachments'] as List<dynamic>?)
|
||||||
out[i].body['attachments']?.contains(ele?.rid) ?? false,
|
?.contains(e) ??
|
||||||
)
|
false)
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
);
|
))
|
||||||
}
|
.toList();
|
||||||
|
|
||||||
// Preload sender accounts
|
// Preload sender accounts
|
||||||
await _ud.listAccount(out.map((ele) => ele.sender.accountId).toSet());
|
await _ud.listAccount(out.map((ele) => ele.sender.accountId).toSet());
|
||||||
|
@ -25,7 +25,6 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
|||||||
|
|
||||||
SnChannel? _channel;
|
SnChannel? _channel;
|
||||||
|
|
||||||
final GlobalKey<ChatMessageInputState> _inputGlobalKey = GlobalKey();
|
|
||||||
late final ChatMessageController _messageController;
|
late final ChatMessageController _messageController;
|
||||||
|
|
||||||
Future<void> _fetchChannel() async {
|
Future<void> _fetchChannel() async {
|
||||||
@ -118,9 +117,6 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
|||||||
hasMerged: canMergePrevious,
|
hasMerged: canMergePrevious,
|
||||||
isPending: _messageController.unconfirmedMessages
|
isPending: _messageController.unconfirmedMessages
|
||||||
.contains(message.uuid),
|
.contains(message.uuid),
|
||||||
onReply: () {
|
|
||||||
_inputGlobalKey.currentState?.setReply(message);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -128,10 +124,8 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
|||||||
if (!_messageController.isPending)
|
if (!_messageController.isPending)
|
||||||
Material(
|
Material(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
child: ChatMessageInput(
|
child: ChatMessageInput(controller: _messageController)
|
||||||
key: _inputGlobalKey,
|
.padding(bottom: MediaQuery.of(context).padding.bottom),
|
||||||
controller: _messageController,
|
|
||||||
).padding(bottom: MediaQuery.of(context).padding.bottom),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -91,9 +91,9 @@ class SnChatMessage with _$SnChatMessage {
|
|||||||
class SnChatMessagePreload with _$SnChatMessagePreload {
|
class SnChatMessagePreload with _$SnChatMessagePreload {
|
||||||
const SnChatMessagePreload._();
|
const SnChatMessagePreload._();
|
||||||
|
|
||||||
|
@HiveType(typeId: 5)
|
||||||
const factory SnChatMessagePreload({
|
const factory SnChatMessagePreload({
|
||||||
List<SnAttachment?>? attachments,
|
@HiveField(0) List<SnAttachment?>? attachments,
|
||||||
SnChatMessage? quoteEvent,
|
|
||||||
}) = _SnChatMessagePreload;
|
}) = _SnChatMessagePreload;
|
||||||
|
|
||||||
factory SnChatMessagePreload.fromJson(Map<String, dynamic> json) =>
|
factory SnChatMessagePreload.fromJson(Map<String, dynamic> json) =>
|
||||||
|
@ -1540,8 +1540,8 @@ SnChatMessagePreload _$SnChatMessagePreloadFromJson(Map<String, dynamic> json) {
|
|||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnChatMessagePreload {
|
mixin _$SnChatMessagePreload {
|
||||||
|
@HiveField(0)
|
||||||
List<SnAttachment?>? get attachments => throw _privateConstructorUsedError;
|
List<SnAttachment?>? get attachments => throw _privateConstructorUsedError;
|
||||||
SnChatMessage? get quoteEvent => throw _privateConstructorUsedError;
|
|
||||||
|
|
||||||
/// Serializes this SnChatMessagePreload to a JSON map.
|
/// Serializes this SnChatMessagePreload to a JSON map.
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@ -1559,9 +1559,7 @@ abstract class $SnChatMessagePreloadCopyWith<$Res> {
|
|||||||
$Res Function(SnChatMessagePreload) then) =
|
$Res Function(SnChatMessagePreload) then) =
|
||||||
_$SnChatMessagePreloadCopyWithImpl<$Res, SnChatMessagePreload>;
|
_$SnChatMessagePreloadCopyWithImpl<$Res, SnChatMessagePreload>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({List<SnAttachment?>? attachments, SnChatMessage? quoteEvent});
|
$Res call({@HiveField(0) List<SnAttachment?>? attachments});
|
||||||
|
|
||||||
$SnChatMessageCopyWith<$Res>? get quoteEvent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -1581,33 +1579,14 @@ class _$SnChatMessagePreloadCopyWithImpl<$Res,
|
|||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? attachments = freezed,
|
Object? attachments = freezed,
|
||||||
Object? quoteEvent = freezed,
|
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
attachments: freezed == attachments
|
attachments: freezed == attachments
|
||||||
? _value.attachments
|
? _value.attachments
|
||||||
: attachments // ignore: cast_nullable_to_non_nullable
|
: attachments // ignore: cast_nullable_to_non_nullable
|
||||||
as List<SnAttachment?>?,
|
as List<SnAttachment?>?,
|
||||||
quoteEvent: freezed == quoteEvent
|
|
||||||
? _value.quoteEvent
|
|
||||||
: quoteEvent // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SnChatMessage?,
|
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a copy of SnChatMessagePreload
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnChatMessageCopyWith<$Res>? get quoteEvent {
|
|
||||||
if (_value.quoteEvent == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $SnChatMessageCopyWith<$Res>(_value.quoteEvent!, (value) {
|
|
||||||
return _then(_value.copyWith(quoteEvent: value) as $Val);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -1618,10 +1597,7 @@ abstract class _$$SnChatMessagePreloadImplCopyWith<$Res>
|
|||||||
__$$SnChatMessagePreloadImplCopyWithImpl<$Res>;
|
__$$SnChatMessagePreloadImplCopyWithImpl<$Res>;
|
||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({List<SnAttachment?>? attachments, SnChatMessage? quoteEvent});
|
$Res call({@HiveField(0) List<SnAttachment?>? attachments});
|
||||||
|
|
||||||
@override
|
|
||||||
$SnChatMessageCopyWith<$Res>? get quoteEvent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -1638,26 +1614,22 @@ class __$$SnChatMessagePreloadImplCopyWithImpl<$Res>
|
|||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? attachments = freezed,
|
Object? attachments = freezed,
|
||||||
Object? quoteEvent = freezed,
|
|
||||||
}) {
|
}) {
|
||||||
return _then(_$SnChatMessagePreloadImpl(
|
return _then(_$SnChatMessagePreloadImpl(
|
||||||
attachments: freezed == attachments
|
attachments: freezed == attachments
|
||||||
? _value._attachments
|
? _value._attachments
|
||||||
: attachments // ignore: cast_nullable_to_non_nullable
|
: attachments // ignore: cast_nullable_to_non_nullable
|
||||||
as List<SnAttachment?>?,
|
as List<SnAttachment?>?,
|
||||||
quoteEvent: freezed == quoteEvent
|
|
||||||
? _value.quoteEvent
|
|
||||||
: quoteEvent // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SnChatMessage?,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
@HiveType(typeId: 5)
|
||||||
class _$SnChatMessagePreloadImpl extends _SnChatMessagePreload {
|
class _$SnChatMessagePreloadImpl extends _SnChatMessagePreload {
|
||||||
const _$SnChatMessagePreloadImpl(
|
const _$SnChatMessagePreloadImpl(
|
||||||
{final List<SnAttachment?>? attachments, this.quoteEvent})
|
{@HiveField(0) final List<SnAttachment?>? attachments})
|
||||||
: _attachments = attachments,
|
: _attachments = attachments,
|
||||||
super._();
|
super._();
|
||||||
|
|
||||||
@ -1666,6 +1638,7 @@ class _$SnChatMessagePreloadImpl extends _SnChatMessagePreload {
|
|||||||
|
|
||||||
final List<SnAttachment?>? _attachments;
|
final List<SnAttachment?>? _attachments;
|
||||||
@override
|
@override
|
||||||
|
@HiveField(0)
|
||||||
List<SnAttachment?>? get attachments {
|
List<SnAttachment?>? get attachments {
|
||||||
final value = _attachments;
|
final value = _attachments;
|
||||||
if (value == null) return null;
|
if (value == null) return null;
|
||||||
@ -1674,12 +1647,9 @@ class _$SnChatMessagePreloadImpl extends _SnChatMessagePreload {
|
|||||||
return EqualUnmodifiableListView(value);
|
return EqualUnmodifiableListView(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
final SnChatMessage? quoteEvent;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnChatMessagePreload(attachments: $attachments, quoteEvent: $quoteEvent)';
|
return 'SnChatMessagePreload(attachments: $attachments)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1688,15 +1658,13 @@ class _$SnChatMessagePreloadImpl extends _SnChatMessagePreload {
|
|||||||
(other.runtimeType == runtimeType &&
|
(other.runtimeType == runtimeType &&
|
||||||
other is _$SnChatMessagePreloadImpl &&
|
other is _$SnChatMessagePreloadImpl &&
|
||||||
const DeepCollectionEquality()
|
const DeepCollectionEquality()
|
||||||
.equals(other._attachments, _attachments) &&
|
.equals(other._attachments, _attachments));
|
||||||
(identical(other.quoteEvent, quoteEvent) ||
|
|
||||||
other.quoteEvent == quoteEvent));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,
|
int get hashCode => Object.hash(
|
||||||
const DeepCollectionEquality().hash(_attachments), quoteEvent);
|
runtimeType, const DeepCollectionEquality().hash(_attachments));
|
||||||
|
|
||||||
/// Create a copy of SnChatMessagePreload
|
/// Create a copy of SnChatMessagePreload
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -1718,17 +1686,16 @@ class _$SnChatMessagePreloadImpl extends _SnChatMessagePreload {
|
|||||||
|
|
||||||
abstract class _SnChatMessagePreload extends SnChatMessagePreload {
|
abstract class _SnChatMessagePreload extends SnChatMessagePreload {
|
||||||
const factory _SnChatMessagePreload(
|
const factory _SnChatMessagePreload(
|
||||||
{final List<SnAttachment?>? attachments,
|
{@HiveField(0) final List<SnAttachment?>? attachments}) =
|
||||||
final SnChatMessage? quoteEvent}) = _$SnChatMessagePreloadImpl;
|
_$SnChatMessagePreloadImpl;
|
||||||
const _SnChatMessagePreload._() : super._();
|
const _SnChatMessagePreload._() : super._();
|
||||||
|
|
||||||
factory _SnChatMessagePreload.fromJson(Map<String, dynamic> json) =
|
factory _SnChatMessagePreload.fromJson(Map<String, dynamic> json) =
|
||||||
_$SnChatMessagePreloadImpl.fromJson;
|
_$SnChatMessagePreloadImpl.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@HiveField(0)
|
||||||
List<SnAttachment?>? get attachments;
|
List<SnAttachment?>? get attachments;
|
||||||
@override
|
|
||||||
SnChatMessage? get quoteEvent;
|
|
||||||
|
|
||||||
/// Create a copy of SnChatMessagePreload
|
/// Create a copy of SnChatMessagePreload
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@ -204,6 +204,41 @@ class SnChatMessageImplAdapter extends TypeAdapter<_$SnChatMessageImpl> {
|
|||||||
typeId == other.typeId;
|
typeId == other.typeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SnChatMessagePreloadImplAdapter
|
||||||
|
extends TypeAdapter<_$SnChatMessagePreloadImpl> {
|
||||||
|
@override
|
||||||
|
final int typeId = 5;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_$SnChatMessagePreloadImpl read(BinaryReader reader) {
|
||||||
|
final numOfFields = reader.readByte();
|
||||||
|
final fields = <int, dynamic>{
|
||||||
|
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||||
|
};
|
||||||
|
return _$SnChatMessagePreloadImpl(
|
||||||
|
attachments: (fields[0] as List?)?.cast<SnAttachment?>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, _$SnChatMessagePreloadImpl obj) {
|
||||||
|
writer
|
||||||
|
..writeByte(1)
|
||||||
|
..writeByte(0)
|
||||||
|
..write(obj.attachments);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is SnChatMessagePreloadImplAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
@ -339,14 +374,10 @@ _$SnChatMessagePreloadImpl _$$SnChatMessagePreloadImplFromJson(
|
|||||||
? null
|
? null
|
||||||
: SnAttachment.fromJson(e as Map<String, dynamic>))
|
: SnAttachment.fromJson(e as Map<String, dynamic>))
|
||||||
.toList(),
|
.toList(),
|
||||||
quoteEvent: json['quote_event'] == null
|
|
||||||
? null
|
|
||||||
: SnChatMessage.fromJson(json['quote_event'] as Map<String, dynamic>),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$SnChatMessagePreloadImplToJson(
|
Map<String, dynamic> _$$SnChatMessagePreloadImplToJson(
|
||||||
_$SnChatMessagePreloadImpl instance) =>
|
_$SnChatMessagePreloadImpl instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'attachments': instance.attachments?.map((e) => e?.toJson()).toList(),
|
'attachments': instance.attachments?.map((e) => e?.toJson()).toList(),
|
||||||
'quote_event': instance.quoteEvent?.toJson(),
|
|
||||||
};
|
};
|
||||||
|
@ -7,15 +7,13 @@ import 'package:surface/widgets/attachment/attachment_item.dart';
|
|||||||
|
|
||||||
class AttachmentList extends StatelessWidget {
|
class AttachmentList extends StatelessWidget {
|
||||||
final List<SnAttachment?> data;
|
final List<SnAttachment?> data;
|
||||||
final bool bordered;
|
final bool? bordered;
|
||||||
final bool noGrow;
|
|
||||||
final double? maxHeight;
|
final double? maxHeight;
|
||||||
final EdgeInsets? listPadding;
|
final EdgeInsets? listPadding;
|
||||||
const AttachmentList({
|
const AttachmentList({
|
||||||
super.key,
|
super.key,
|
||||||
required this.data,
|
required this.data,
|
||||||
this.bordered = false,
|
this.bordered,
|
||||||
this.noGrow = false,
|
|
||||||
this.maxHeight,
|
this.maxHeight,
|
||||||
this.listPadding,
|
this.listPadding,
|
||||||
});
|
});
|
||||||
@ -25,7 +23,7 @@ class AttachmentList extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final borderSide = bordered
|
final borderSide = (bordered ?? false)
|
||||||
? BorderSide(width: 1, color: Theme.of(context).dividerColor)
|
? BorderSide(width: 1, color: Theme.of(context).dividerColor)
|
||||||
: BorderSide.none;
|
: BorderSide.none;
|
||||||
final backgroundColor = Theme.of(context).colorScheme.surfaceContainer;
|
final backgroundColor = Theme.of(context).colorScheme.surfaceContainer;
|
||||||
@ -36,7 +34,7 @@ class AttachmentList extends StatelessWidget {
|
|||||||
|
|
||||||
if (data.isEmpty) return const SizedBox.shrink();
|
if (data.isEmpty) return const SizedBox.shrink();
|
||||||
if (data.length == 1) {
|
if (data.length == 1) {
|
||||||
if (ResponsiveBreakpoints.of(context).largerThan(MOBILE) || noGrow) {
|
if (ResponsiveBreakpoints.of(context).largerThan(MOBILE)) {
|
||||||
return Padding(
|
return Padding(
|
||||||
// Single child list-like displaying
|
// Single child list-like displaying
|
||||||
padding: listPadding ?? EdgeInsets.zero,
|
padding: listPadding ?? EdgeInsets.zero,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/user_directory.dart';
|
import 'package:surface/providers/user_directory.dart';
|
||||||
@ -9,23 +8,18 @@ import 'package:surface/types/chat.dart';
|
|||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/attachment/attachment_list.dart';
|
import 'package:surface/widgets/attachment/attachment_list.dart';
|
||||||
import 'package:surface/widgets/markdown_content.dart';
|
import 'package:surface/widgets/markdown_content.dart';
|
||||||
import 'package:swipe_to/swipe_to.dart';
|
|
||||||
|
|
||||||
class ChatMessage extends StatelessWidget {
|
class ChatMessage extends StatelessWidget {
|
||||||
final SnChatMessage data;
|
final SnChatMessage data;
|
||||||
final bool isCompact;
|
|
||||||
final bool isMerged;
|
final bool isMerged;
|
||||||
final bool hasMerged;
|
final bool hasMerged;
|
||||||
final bool isPending;
|
final bool isPending;
|
||||||
final Function()? onReply;
|
|
||||||
const ChatMessage({
|
const ChatMessage({
|
||||||
super.key,
|
super.key,
|
||||||
required this.data,
|
required this.data,
|
||||||
this.isCompact = false,
|
|
||||||
this.isMerged = false,
|
this.isMerged = false,
|
||||||
this.hasMerged = false,
|
this.hasMerged = false,
|
||||||
this.isPending = false,
|
this.isPending = false,
|
||||||
this.onReply,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -35,22 +29,14 @@ class ChatMessage extends StatelessWidget {
|
|||||||
|
|
||||||
final dateFormatter = DateFormat('MM/dd HH:mm');
|
final dateFormatter = DateFormat('MM/dd HH:mm');
|
||||||
|
|
||||||
return SwipeTo(
|
return Row(
|
||||||
key: Key('chat-message-${data.id}'),
|
|
||||||
iconOnLeftSwipe: Symbols.reply,
|
|
||||||
swipeSensitivity: 20,
|
|
||||||
onLeftSwipe: onReply != null ? (_) => onReply!() : null,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
if (!isMerged)
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (!isMerged && !isCompact)
|
|
||||||
AccountImage(
|
AccountImage(
|
||||||
content: user?.avatar,
|
content: user?.avatar,
|
||||||
)
|
)
|
||||||
else if (isMerged)
|
else
|
||||||
const Gap(40),
|
const Gap(40),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -62,11 +48,6 @@ class ChatMessage extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||||
textBaseline: TextBaseline.alphabetic,
|
textBaseline: TextBaseline.alphabetic,
|
||||||
children: [
|
children: [
|
||||||
if (isCompact)
|
|
||||||
AccountImage(
|
|
||||||
content: user?.avatar,
|
|
||||||
radius: 12,
|
|
||||||
).padding(right: 6),
|
|
||||||
Text(
|
Text(
|
||||||
(data.sender.nick?.isNotEmpty ?? false)
|
(data.sender.nick?.isNotEmpty ?? false)
|
||||||
? data.sender.nick!
|
? data.sender.nick!
|
||||||
@ -78,49 +59,23 @@ class ChatMessage extends StatelessWidget {
|
|||||||
).fontSize(13),
|
).fontSize(13),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (isCompact) const Gap(4),
|
|
||||||
if (data.preload?.quoteEvent != null)
|
|
||||||
StyledWidget(Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius:
|
|
||||||
const BorderRadius.all(Radius.circular(8)),
|
|
||||||
border: Border.all(
|
|
||||||
color: Theme.of(context).dividerColor,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
left: 4,
|
|
||||||
right: 4,
|
|
||||||
top: 8,
|
|
||||||
bottom: 6,
|
|
||||||
),
|
|
||||||
child: ChatMessage(
|
|
||||||
data: data.preload!.quoteEvent!,
|
|
||||||
isCompact: true,
|
|
||||||
),
|
|
||||||
)).padding(bottom: 4, top: isMerged ? 4 : 2),
|
|
||||||
if (data.body['text'] != null)
|
if (data.body['text'] != null)
|
||||||
MarkdownTextContent(
|
MarkdownTextContent(
|
||||||
content: data.body['text'],
|
content: data.body['text'],
|
||||||
isAutoWarp: true,
|
isAutoWarp: true,
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
).opacity(isPending ? 0.5 : 1),
|
|
||||||
if (data.preload?.attachments?.isNotEmpty ?? false)
|
if (data.preload?.attachments?.isNotEmpty ?? false)
|
||||||
AttachmentList(
|
AttachmentList(
|
||||||
data: data.preload!.attachments!,
|
data: data.preload!.attachments!,
|
||||||
bordered: true,
|
bordered: true,
|
||||||
noGrow: true,
|
|
||||||
maxHeight: 520,
|
maxHeight: 520,
|
||||||
listPadding: const EdgeInsets.only(top: 8),
|
listPadding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
),
|
),
|
||||||
if (!hasMerged && !isCompact) const Gap(12),
|
if (!hasMerged) const Gap(8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
)
|
||||||
|
],
|
||||||
|
).opacity(isPending ? 0.5 : 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,7 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
import 'package:surface/controllers/chat_message_controller.dart';
|
import 'package:surface/controllers/chat_message_controller.dart';
|
||||||
import 'package:surface/controllers/post_write_controller.dart';
|
import 'package:surface/controllers/post_write_controller.dart';
|
||||||
import 'package:surface/providers/sn_attachment.dart';
|
import 'package:surface/providers/sn_attachment.dart';
|
||||||
import 'package:surface/types/chat.dart';
|
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/markdown_content.dart';
|
|
||||||
import 'package:surface/widgets/post/post_media_pending_list.dart';
|
import 'package:surface/widgets/post/post_media_pending_list.dart';
|
||||||
|
|
||||||
class ChatMessageInput extends StatefulWidget {
|
class ChatMessageInput extends StatefulWidget {
|
||||||
@ -18,22 +16,16 @@ class ChatMessageInput extends StatefulWidget {
|
|||||||
const ChatMessageInput({super.key, required this.controller});
|
const ChatMessageInput({super.key, required this.controller});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChatMessageInput> createState() => ChatMessageInputState();
|
State<ChatMessageInput> createState() => _ChatMessageInputState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChatMessageInputState extends State<ChatMessageInput> {
|
class _ChatMessageInputState extends State<ChatMessageInput> {
|
||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
double? _progress;
|
double? _progress;
|
||||||
|
|
||||||
SnChatMessage? _replyingMessage;
|
|
||||||
|
|
||||||
final TextEditingController _contentController = TextEditingController();
|
final TextEditingController _contentController = TextEditingController();
|
||||||
final FocusNode _focusNode = FocusNode();
|
final FocusNode _focusNode = FocusNode();
|
||||||
|
|
||||||
void setReply(SnChatMessage? value) {
|
|
||||||
setState(() => _replyingMessage = value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _sendMessage() async {
|
Future<void> _sendMessage() async {
|
||||||
if (_isBusy) return;
|
if (_isBusy) return;
|
||||||
|
|
||||||
@ -77,7 +69,6 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
|
|
||||||
attach.putCache(
|
attach.putCache(
|
||||||
_attachments.where((e) => e.attachment != null).map((e) => e.attachment!),
|
_attachments.where((e) => e.attachment != null).map((e) => e.attachment!),
|
||||||
noCheck: true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Send the message
|
// Send the message
|
||||||
@ -89,11 +80,9 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
.where((e) => e.attachment != null)
|
.where((e) => e.attachment != null)
|
||||||
.map((e) => e.attachment!.rid)
|
.map((e) => e.attachment!.rid)
|
||||||
.toList(),
|
.toList(),
|
||||||
quoteId: _replyingMessage?.id,
|
|
||||||
);
|
);
|
||||||
_contentController.clear();
|
_contentController.clear();
|
||||||
_attachments.clear();
|
_attachments.clear();
|
||||||
_replyingMessage = null;
|
|
||||||
|
|
||||||
setState(() => _isBusy = false);
|
setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
@ -145,45 +134,10 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
setState(() => _attachments.removeAt(idx));
|
setState(() => _attachments.removeAt(idx));
|
||||||
},
|
},
|
||||||
onUpdateBusy: (state) => setState(() => _isBusy = state),
|
onUpdateBusy: (state) => setState(() => _isBusy = state),
|
||||||
),
|
|
||||||
).height(_attachments.isNotEmpty ? 80 + 8 : 0, animate: true).animate(
|
).height(_attachments.isNotEmpty ? 80 + 8 : 0, animate: true).animate(
|
||||||
const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
const Duration(milliseconds: 300),
|
||||||
SingleChildScrollView(
|
Curves.fastEaseInToSlowEaseOut),
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
child: Padding(
|
|
||||||
padding: _replyingMessage != null
|
|
||||||
? const EdgeInsets.only(top: 8)
|
|
||||||
: EdgeInsets.zero,
|
|
||||||
child: _replyingMessage != null
|
|
||||||
? MaterialBanner(
|
|
||||||
padding: const EdgeInsets.only(left: 16.0),
|
|
||||||
leading: const Icon(Symbols.reply),
|
|
||||||
content: SingleChildScrollView(
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (_replyingMessage?.body['text'] != null)
|
|
||||||
MarkdownTextContent(
|
|
||||||
content: _replyingMessage?.body['text'],
|
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
child: Text('cancel'.tr()),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() => _replyingMessage = null);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink(),
|
|
||||||
),
|
|
||||||
).height(_replyingMessage != null ? 54 + 8 : 0, animate: true).animate(
|
|
||||||
const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 56,
|
height: 56,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
12
pubspec.lock
12
pubspec.lock
@ -516,10 +516,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_native_splash
|
name: flutter_native_splash
|
||||||
sha256: "1152ab0067ca5a2ebeb862fe0a762057202cceb22b7e62692dcbabf6483891bb"
|
sha256: ee5c9bd2b74ea8676442fd4ab876b5d41681df49276488854d6c81a5377c0ef1
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.3"
|
version: "2.4.2"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1343,14 +1343,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.1"
|
version: "0.4.1"
|
||||||
swipe_to:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: swipe_to
|
|
||||||
sha256: "58f61031803ece9b0efe09006809e78904c640c6d42d48715d1d1c3c28f8499a"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.6"
|
|
||||||
synchronized:
|
synchronized:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -78,7 +78,6 @@ dependencies:
|
|||||||
isar_flutter_libs: ^3.1.0+1
|
isar_flutter_libs: ^3.1.0+1
|
||||||
hive: ^2.2.3
|
hive: ^2.2.3
|
||||||
hive_flutter: ^1.1.0
|
hive_flutter: ^1.1.0
|
||||||
swipe_to: ^1.0.6
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user