✨ Chat quote and reply
This commit is contained in:
parent
9f7a3082cb
commit
5032cccf38
@ -119,12 +119,20 @@ class ChatMessageController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
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(
|
||||
message.body['attachments']?.cast<String>() ?? [],
|
||||
);
|
||||
final attachments = await _attach.getMultiple(attachmentRid);
|
||||
message = message.copyWith(
|
||||
preload: SnChatMessagePreload(attachments: attachments),
|
||||
preload: SnChatMessagePreload(
|
||||
quoteEvent: quoteEvent,
|
||||
attachments: attachments,
|
||||
),
|
||||
);
|
||||
|
||||
messages.insert(0, message);
|
||||
@ -133,12 +141,20 @@ class ChatMessageController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
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(
|
||||
message.body['attachments']?.cast<String>() ?? [],
|
||||
);
|
||||
final attachments = await _attach.getMultiple(attachmentRid);
|
||||
message = message.copyWith(
|
||||
preload: SnChatMessagePreload(attachments: attachments),
|
||||
preload: SnChatMessagePreload(
|
||||
quoteEvent: quoteEvent,
|
||||
attachments: attachments,
|
||||
),
|
||||
);
|
||||
|
||||
final idx = messages.indexWhere((e) => e.uuid == message.uuid);
|
||||
@ -199,8 +215,8 @@ class ChatMessageController extends ChangeNotifier {
|
||||
final body = {
|
||||
'text': content,
|
||||
'algorithm': 'plain',
|
||||
if (quoteId != null) 'quote_id': quoteId,
|
||||
if (relatedId != null) 'related_id': relatedId,
|
||||
if (quoteId != null) 'quote_event': quoteId,
|
||||
if (relatedId != null) 'quote_event': relatedId,
|
||||
if (attachments != null && attachments.isNotEmpty)
|
||||
'attachments': attachments,
|
||||
};
|
||||
@ -269,6 +285,42 @@ 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.
|
||||
/// Will not check local storage is up to date with the server.
|
||||
/// If you need to do the sync, do the `checkUpdate` instead.
|
||||
@ -300,9 +352,18 @@ class ChatMessageController extends ChangeNotifier {
|
||||
out.expand((e) => (e.body['attachments'] as List<dynamic>?) ?? []),
|
||||
);
|
||||
final attachments = await _attach.getMultiple(attachmentRid);
|
||||
|
||||
// Putting preload back to data
|
||||
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(
|
||||
quoteEvent: quoteEvent,
|
||||
attachments: attachments
|
||||
.where(
|
||||
(ele) =>
|
||||
|
@ -25,6 +25,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
||||
|
||||
SnChannel? _channel;
|
||||
|
||||
final GlobalKey<ChatMessageInputState> _inputGlobalKey = GlobalKey();
|
||||
late final ChatMessageController _messageController;
|
||||
|
||||
Future<void> _fetchChannel() async {
|
||||
@ -117,6 +118,9 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
||||
hasMerged: canMergePrevious,
|
||||
isPending: _messageController.unconfirmedMessages
|
||||
.contains(message.uuid),
|
||||
onReply: () {
|
||||
_inputGlobalKey.currentState?.setReply(message);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -124,8 +128,10 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
||||
if (!_messageController.isPending)
|
||||
Material(
|
||||
elevation: 2,
|
||||
child: ChatMessageInput(controller: _messageController)
|
||||
.padding(bottom: MediaQuery.of(context).padding.bottom),
|
||||
child: ChatMessageInput(
|
||||
key: _inputGlobalKey,
|
||||
controller: _messageController,
|
||||
).padding(bottom: MediaQuery.of(context).padding.bottom),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -91,9 +91,9 @@ class SnChatMessage with _$SnChatMessage {
|
||||
class SnChatMessagePreload with _$SnChatMessagePreload {
|
||||
const SnChatMessagePreload._();
|
||||
|
||||
@HiveType(typeId: 5)
|
||||
const factory SnChatMessagePreload({
|
||||
@HiveField(0) List<SnAttachment?>? attachments,
|
||||
List<SnAttachment?>? attachments,
|
||||
SnChatMessage? quoteEvent,
|
||||
}) = _SnChatMessagePreload;
|
||||
|
||||
factory SnChatMessagePreload.fromJson(Map<String, dynamic> json) =>
|
||||
|
@ -1540,8 +1540,8 @@ SnChatMessagePreload _$SnChatMessagePreloadFromJson(Map<String, dynamic> json) {
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnChatMessagePreload {
|
||||
@HiveField(0)
|
||||
List<SnAttachment?>? get attachments => throw _privateConstructorUsedError;
|
||||
SnChatMessage? get quoteEvent => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this SnChatMessagePreload to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@ -1559,7 +1559,9 @@ abstract class $SnChatMessagePreloadCopyWith<$Res> {
|
||||
$Res Function(SnChatMessagePreload) then) =
|
||||
_$SnChatMessagePreloadCopyWithImpl<$Res, SnChatMessagePreload>;
|
||||
@useResult
|
||||
$Res call({@HiveField(0) List<SnAttachment?>? attachments});
|
||||
$Res call({List<SnAttachment?>? attachments, SnChatMessage? quoteEvent});
|
||||
|
||||
$SnChatMessageCopyWith<$Res>? get quoteEvent;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -1579,14 +1581,33 @@ class _$SnChatMessagePreloadCopyWithImpl<$Res,
|
||||
@override
|
||||
$Res call({
|
||||
Object? attachments = freezed,
|
||||
Object? quoteEvent = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
attachments: freezed == attachments
|
||||
? _value.attachments
|
||||
: attachments // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnAttachment?>?,
|
||||
quoteEvent: freezed == quoteEvent
|
||||
? _value.quoteEvent
|
||||
: quoteEvent // ignore: cast_nullable_to_non_nullable
|
||||
as SnChatMessage?,
|
||||
) 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
|
||||
@ -1597,7 +1618,10 @@ abstract class _$$SnChatMessagePreloadImplCopyWith<$Res>
|
||||
__$$SnChatMessagePreloadImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({@HiveField(0) List<SnAttachment?>? attachments});
|
||||
$Res call({List<SnAttachment?>? attachments, SnChatMessage? quoteEvent});
|
||||
|
||||
@override
|
||||
$SnChatMessageCopyWith<$Res>? get quoteEvent;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -1614,22 +1638,26 @@ class __$$SnChatMessagePreloadImplCopyWithImpl<$Res>
|
||||
@override
|
||||
$Res call({
|
||||
Object? attachments = freezed,
|
||||
Object? quoteEvent = freezed,
|
||||
}) {
|
||||
return _then(_$SnChatMessagePreloadImpl(
|
||||
attachments: freezed == attachments
|
||||
? _value._attachments
|
||||
: attachments // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnAttachment?>?,
|
||||
quoteEvent: freezed == quoteEvent
|
||||
? _value.quoteEvent
|
||||
: quoteEvent // ignore: cast_nullable_to_non_nullable
|
||||
as SnChatMessage?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
@HiveType(typeId: 5)
|
||||
class _$SnChatMessagePreloadImpl extends _SnChatMessagePreload {
|
||||
const _$SnChatMessagePreloadImpl(
|
||||
{@HiveField(0) final List<SnAttachment?>? attachments})
|
||||
{final List<SnAttachment?>? attachments, this.quoteEvent})
|
||||
: _attachments = attachments,
|
||||
super._();
|
||||
|
||||
@ -1638,7 +1666,6 @@ class _$SnChatMessagePreloadImpl extends _SnChatMessagePreload {
|
||||
|
||||
final List<SnAttachment?>? _attachments;
|
||||
@override
|
||||
@HiveField(0)
|
||||
List<SnAttachment?>? get attachments {
|
||||
final value = _attachments;
|
||||
if (value == null) return null;
|
||||
@ -1647,9 +1674,12 @@ class _$SnChatMessagePreloadImpl extends _SnChatMessagePreload {
|
||||
return EqualUnmodifiableListView(value);
|
||||
}
|
||||
|
||||
@override
|
||||
final SnChatMessage? quoteEvent;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnChatMessagePreload(attachments: $attachments)';
|
||||
return 'SnChatMessagePreload(attachments: $attachments, quoteEvent: $quoteEvent)';
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1658,13 +1688,15 @@ class _$SnChatMessagePreloadImpl extends _SnChatMessagePreload {
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$SnChatMessagePreloadImpl &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._attachments, _attachments));
|
||||
.equals(other._attachments, _attachments) &&
|
||||
(identical(other.quoteEvent, quoteEvent) ||
|
||||
other.quoteEvent == quoteEvent));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType, const DeepCollectionEquality().hash(_attachments));
|
||||
int get hashCode => Object.hash(runtimeType,
|
||||
const DeepCollectionEquality().hash(_attachments), quoteEvent);
|
||||
|
||||
/// Create a copy of SnChatMessagePreload
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@ -1686,16 +1718,17 @@ class _$SnChatMessagePreloadImpl extends _SnChatMessagePreload {
|
||||
|
||||
abstract class _SnChatMessagePreload extends SnChatMessagePreload {
|
||||
const factory _SnChatMessagePreload(
|
||||
{@HiveField(0) final List<SnAttachment?>? attachments}) =
|
||||
_$SnChatMessagePreloadImpl;
|
||||
{final List<SnAttachment?>? attachments,
|
||||
final SnChatMessage? quoteEvent}) = _$SnChatMessagePreloadImpl;
|
||||
const _SnChatMessagePreload._() : super._();
|
||||
|
||||
factory _SnChatMessagePreload.fromJson(Map<String, dynamic> json) =
|
||||
_$SnChatMessagePreloadImpl.fromJson;
|
||||
|
||||
@override
|
||||
@HiveField(0)
|
||||
List<SnAttachment?>? get attachments;
|
||||
@override
|
||||
SnChatMessage? get quoteEvent;
|
||||
|
||||
/// Create a copy of SnChatMessagePreload
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
@ -204,41 +204,6 @@ class SnChatMessageImplAdapter extends TypeAdapter<_$SnChatMessageImpl> {
|
||||
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
|
||||
// **************************************************************************
|
||||
@ -374,10 +339,14 @@ _$SnChatMessagePreloadImpl _$$SnChatMessagePreloadImplFromJson(
|
||||
? null
|
||||
: SnAttachment.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
quoteEvent: json['quote_event'] == null
|
||||
? null
|
||||
: SnChatMessage.fromJson(json['quote_event'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$SnChatMessagePreloadImplToJson(
|
||||
_$SnChatMessagePreloadImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'attachments': instance.attachments?.map((e) => e?.toJson()).toList(),
|
||||
'quote_event': instance.quoteEvent?.toJson(),
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/user_directory.dart';
|
||||
@ -8,18 +9,23 @@ import 'package:surface/types/chat.dart';
|
||||
import 'package:surface/widgets/account/account_image.dart';
|
||||
import 'package:surface/widgets/attachment/attachment_list.dart';
|
||||
import 'package:surface/widgets/markdown_content.dart';
|
||||
import 'package:swipe_to/swipe_to.dart';
|
||||
|
||||
class ChatMessage extends StatelessWidget {
|
||||
final SnChatMessage data;
|
||||
final bool isCompact;
|
||||
final bool isMerged;
|
||||
final bool hasMerged;
|
||||
final bool isPending;
|
||||
final Function()? onReply;
|
||||
const ChatMessage({
|
||||
super.key,
|
||||
required this.data,
|
||||
this.isCompact = false,
|
||||
this.isMerged = false,
|
||||
this.hasMerged = false,
|
||||
this.isPending = false,
|
||||
this.onReply,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -29,17 +35,22 @@ class ChatMessage extends StatelessWidget {
|
||||
|
||||
final dateFormatter = DateFormat('MM/dd HH:mm');
|
||||
|
||||
return Column(
|
||||
return SwipeTo(
|
||||
key: Key('chat-message-${data.id}'),
|
||||
iconOnLeftSwipe: Symbols.reply,
|
||||
swipeSensitivity: 20,
|
||||
onLeftSwipe: onReply != null ? (_) => onReply!() : null,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!isMerged)
|
||||
if (!isMerged && !isCompact)
|
||||
AccountImage(
|
||||
content: user?.avatar,
|
||||
)
|
||||
else
|
||||
else if (isMerged)
|
||||
const Gap(40),
|
||||
const Gap(8),
|
||||
Expanded(
|
||||
@ -51,6 +62,11 @@ class ChatMessage extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
if (isCompact)
|
||||
AccountImage(
|
||||
content: user?.avatar,
|
||||
radius: 12,
|
||||
).padding(right: 6),
|
||||
Text(
|
||||
(data.sender.nick?.isNotEmpty ?? false)
|
||||
? data.sender.nick!
|
||||
@ -62,6 +78,28 @@ class ChatMessage extends StatelessWidget {
|
||||
).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)
|
||||
MarkdownTextContent(
|
||||
content: data.body['text'],
|
||||
@ -80,8 +118,9 @@ class ChatMessage extends StatelessWidget {
|
||||
maxHeight: 520,
|
||||
listPadding: const EdgeInsets.only(top: 8),
|
||||
),
|
||||
if (!hasMerged) const Gap(8),
|
||||
if (!hasMerged && !isCompact) const Gap(12),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,9 @@ import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/controllers/chat_message_controller.dart';
|
||||
import 'package:surface/controllers/post_write_controller.dart';
|
||||
import 'package:surface/providers/sn_attachment.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/markdown_content.dart';
|
||||
import 'package:surface/widgets/post/post_media_pending_list.dart';
|
||||
|
||||
class ChatMessageInput extends StatefulWidget {
|
||||
@ -16,16 +18,22 @@ class ChatMessageInput extends StatefulWidget {
|
||||
const ChatMessageInput({super.key, required this.controller});
|
||||
|
||||
@override
|
||||
State<ChatMessageInput> createState() => _ChatMessageInputState();
|
||||
State<ChatMessageInput> createState() => ChatMessageInputState();
|
||||
}
|
||||
|
||||
class _ChatMessageInputState extends State<ChatMessageInput> {
|
||||
class ChatMessageInputState extends State<ChatMessageInput> {
|
||||
bool _isBusy = false;
|
||||
double? _progress;
|
||||
|
||||
SnChatMessage? _replyingMessage;
|
||||
|
||||
final TextEditingController _contentController = TextEditingController();
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
|
||||
void setReply(SnChatMessage? value) {
|
||||
setState(() => _replyingMessage = value);
|
||||
}
|
||||
|
||||
Future<void> _sendMessage() async {
|
||||
if (_isBusy) return;
|
||||
|
||||
@ -69,6 +77,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
||||
|
||||
attach.putCache(
|
||||
_attachments.where((e) => e.attachment != null).map((e) => e.attachment!),
|
||||
noCheck: true,
|
||||
);
|
||||
|
||||
// Send the message
|
||||
@ -80,9 +89,11 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
||||
.where((e) => e.attachment != null)
|
||||
.map((e) => e.attachment!.rid)
|
||||
.toList(),
|
||||
quoteId: _replyingMessage?.id,
|
||||
);
|
||||
_contentController.clear();
|
||||
_attachments.clear();
|
||||
_replyingMessage = null;
|
||||
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
@ -134,10 +145,45 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
||||
setState(() => _attachments.removeAt(idx));
|
||||
},
|
||||
onUpdateBusy: (state) => setState(() => _isBusy = state),
|
||||
).height(_attachments.isNotEmpty ? 80 + 8 : 0, animate: true).animate(
|
||||
const Duration(milliseconds: 300),
|
||||
Curves.fastEaseInToSlowEaseOut),
|
||||
),
|
||||
).height(_attachments.isNotEmpty ? 80 + 8 : 0, animate: true).animate(
|
||||
const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
||||
SingleChildScrollView(
|
||||
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(
|
||||
height: 56,
|
||||
child: Row(
|
||||
|
12
pubspec.lock
12
pubspec.lock
@ -516,10 +516,10 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_native_splash
|
||||
sha256: ee5c9bd2b74ea8676442fd4ab876b5d41681df49276488854d6c81a5377c0ef1
|
||||
sha256: "1152ab0067ca5a2ebeb862fe0a762057202cceb22b7e62692dcbabf6483891bb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.4.3"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1343,6 +1343,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -78,6 +78,7 @@ dependencies:
|
||||
isar_flutter_libs: ^3.1.0+1
|
||||
hive: ^2.2.3
|
||||
hive_flutter: ^1.1.0
|
||||
swipe_to: ^1.0.6
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Loading…
Reference in New Issue
Block a user