✨ Chat input auto grow
This commit is contained in:
parent
09ec9d4a0c
commit
93f41bb523
@ -44,7 +44,9 @@ class SnNetworkProvider {
|
|||||||
settings: const TalkerDioLoggerSettings(
|
settings: const TalkerDioLoggerSettings(
|
||||||
printRequestHeaders: false,
|
printRequestHeaders: false,
|
||||||
printResponseHeaders: false,
|
printResponseHeaders: false,
|
||||||
printResponseMessage: true,
|
printResponseMessage: false,
|
||||||
|
printResponseData: false,
|
||||||
|
printRequestData: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -28,7 +28,8 @@ class ChatMessageInput extends StatefulWidget {
|
|||||||
final ChatMessageController controller;
|
final ChatMessageController controller;
|
||||||
final SnChannelMember? otherMember;
|
final SnChannelMember? otherMember;
|
||||||
|
|
||||||
const ChatMessageInput({super.key, required this.controller, this.otherMember});
|
const ChatMessageInput(
|
||||||
|
{super.key, required this.controller, this.otherMember});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChatMessageInput> createState() => ChatMessageInputState();
|
State<ChatMessageInput> createState() => ChatMessageInputState();
|
||||||
@ -45,12 +46,20 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
|
|
||||||
final HotKey _pasteHotKey = HotKey(
|
final HotKey _pasteHotKey = HotKey(
|
||||||
key: PhysicalKeyboardKey.keyV,
|
key: PhysicalKeyboardKey.keyV,
|
||||||
modifiers: [(!kIsWeb && Platform.isMacOS) ? HotKeyModifier.meta : HotKeyModifier.control],
|
modifiers: [
|
||||||
|
(!kIsWeb && Platform.isMacOS)
|
||||||
|
? HotKeyModifier.meta
|
||||||
|
: HotKeyModifier.control
|
||||||
|
],
|
||||||
scope: HotKeyScope.inapp,
|
scope: HotKeyScope.inapp,
|
||||||
);
|
);
|
||||||
final HotKey _newLineHotKey = HotKey(
|
final HotKey _newLineHotKey = HotKey(
|
||||||
key: PhysicalKeyboardKey.enter,
|
key: PhysicalKeyboardKey.enter,
|
||||||
modifiers: [(!kIsWeb && Platform.isMacOS) ? HotKeyModifier.meta : HotKeyModifier.control],
|
modifiers: [
|
||||||
|
(!kIsWeb && Platform.isMacOS)
|
||||||
|
? HotKeyModifier.meta
|
||||||
|
: HotKeyModifier.control
|
||||||
|
],
|
||||||
scope: HotKeyScope.inapp,
|
scope: HotKeyScope.inapp,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -100,7 +109,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
void setEdit(SnChatMessage? value) {
|
void setEdit(SnChatMessage? value) {
|
||||||
_contentController.text = value?.body['text'] ?? '';
|
_contentController.text = value?.body['text'] ?? '';
|
||||||
_attachments.clear();
|
_attachments.clear();
|
||||||
_attachments.addAll(value?.preload?.attachments?.map((e) => PostWriteMedia(e)) ?? []);
|
_attachments.addAll(
|
||||||
|
value?.preload?.attachments?.map((e) => PostWriteMedia(e)) ?? []);
|
||||||
setState(() => _editingMessage = value);
|
setState(() => _editingMessage = value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +149,9 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
media.name,
|
media.name,
|
||||||
'messaging',
|
'messaging',
|
||||||
null,
|
null,
|
||||||
mimetype: media.raw != null && media.type == SnMediaType.image ? 'image/png' : null,
|
mimetype: media.raw != null && media.type == SnMediaType.image
|
||||||
|
? 'image/png'
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
final item = await attach.chunkedUploadParts(
|
final item = await attach.chunkedUploadParts(
|
||||||
@ -171,7 +183,10 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
widget.controller.sendMessage(
|
widget.controller.sendMessage(
|
||||||
_editingMessage != null ? 'messages.edit' : 'messages.new',
|
_editingMessage != null ? 'messages.edit' : 'messages.new',
|
||||||
_contentController.text,
|
_contentController.text,
|
||||||
attachments: _attachments.where((e) => e.attachment != null).map((e) => e.attachment!.rid).toList(),
|
attachments: _attachments
|
||||||
|
.where((e) => e.attachment != null)
|
||||||
|
.map((e) => e.attachment!.rid)
|
||||||
|
.toList(),
|
||||||
relatedId: _editingMessage?.id,
|
relatedId: _editingMessage?.id,
|
||||||
quoteId: _replyingMessage?.id,
|
quoteId: _replyingMessage?.id,
|
||||||
editingMessage: _editingMessage,
|
editingMessage: _editingMessage,
|
||||||
@ -232,12 +247,15 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
TweenAnimationBuilder<double>(
|
TweenAnimationBuilder<double>(
|
||||||
tween: Tween(begin: 0, end: _progress),
|
tween: Tween(begin: 0, end: _progress),
|
||||||
duration: Duration(milliseconds: 300),
|
duration: Duration(milliseconds: 300),
|
||||||
builder: (context, value, _) => LinearProgressIndicator(value: value, minHeight: 2),
|
builder: (context, value, _) =>
|
||||||
|
LinearProgressIndicator(value: value, minHeight: 2),
|
||||||
)
|
)
|
||||||
else if (_isBusy)
|
else if (_isBusy)
|
||||||
const LinearProgressIndicator(value: null, minHeight: 2),
|
const LinearProgressIndicator(value: null, minHeight: 2),
|
||||||
Padding(
|
Padding(
|
||||||
padding: _attachments.isNotEmpty ? const EdgeInsets.only(top: 8) : EdgeInsets.zero,
|
padding: _attachments.isNotEmpty
|
||||||
|
? const EdgeInsets.only(top: 8)
|
||||||
|
: EdgeInsets.zero,
|
||||||
child: PostMediaPendingList(
|
child: PostMediaPendingList(
|
||||||
attachments: _attachments,
|
attachments: _attachments,
|
||||||
isBusy: _isBusy,
|
isBusy: _isBusy,
|
||||||
@ -249,9 +267,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
},
|
},
|
||||||
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)
|
const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
||||||
.animate(const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
child: _replyingMessage != null
|
child: _replyingMessage != null
|
||||||
@ -272,7 +289,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
const Gap(8),
|
const Gap(8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
_replyingMessage?.body['text'] ?? '${_replyingMessage?.sender.nick}',
|
_replyingMessage?.body['text'] ??
|
||||||
|
'${_replyingMessage?.sender.nick}',
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
@ -289,9 +307,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
).padding(vertical: 8),
|
).padding(vertical: 8),
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
)
|
).height(_replyingMessage != null ? 38 : 0, animate: true).animate(
|
||||||
.height(_replyingMessage != null ? 38 : 0, animate: true)
|
const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
||||||
.animate(const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
child: _editingMessage != null
|
child: _editingMessage != null
|
||||||
@ -312,7 +329,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
const Gap(8),
|
const Gap(8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
_editingMessage?.body['text'] ?? '${_editingMessage?.sender.nick}',
|
_editingMessage?.body['text'] ??
|
||||||
|
'${_editingMessage?.sender.nick}',
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
@ -330,12 +348,13 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
).padding(vertical: 8),
|
).padding(vertical: 8),
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
)
|
).height(_editingMessage != null ? 38 : 0, animate: true).animate(
|
||||||
.height(_editingMessage != null ? 38 : 0, animate: true)
|
const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
||||||
.animate(const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
Container(
|
||||||
SizedBox(
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
height: 56,
|
constraints: BoxConstraints(minHeight: 56, maxHeight: 240),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
@ -347,11 +366,14 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
? 'fieldChatMessageDirect'.tr(args: [
|
? 'fieldChatMessageDirect'.tr(args: [
|
||||||
'@${ud.getAccountFromCache(widget.otherMember?.accountId)?.name}',
|
'@${ud.getAccountFromCache(widget.otherMember?.accountId)?.name}',
|
||||||
])
|
])
|
||||||
: 'fieldChatMessage'.tr(args: [widget.controller.channel?.name ?? 'loading'.tr()]),
|
: 'fieldChatMessage'.tr(args: [
|
||||||
|
widget.controller.channel?.name ?? 'loading'.tr()
|
||||||
|
]),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
textInputAction: TextInputAction.send,
|
textInputAction: TextInputAction.send,
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
onSubmitted: (_) {
|
onSubmitted: (_) {
|
||||||
if (_isBusy) return;
|
if (_isBusy) return;
|
||||||
_sendMessage();
|
_sendMessage();
|
||||||
@ -366,7 +388,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
Symbols.mood,
|
Symbols.mood,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
visualDensity:
|
||||||
|
const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
constraints: const BoxConstraints(),
|
constraints: const BoxConstraints(),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -386,13 +409,14 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
Symbols.send,
|
Symbols.send,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
visualDensity:
|
||||||
|
const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
constraints: const BoxConstraints(),
|
constraints: const BoxConstraints(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
).padding(horizontal: 16),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -403,7 +427,8 @@ class _StickerPicker extends StatelessWidget {
|
|||||||
final Function? onDismiss;
|
final Function? onDismiss;
|
||||||
final Function(String)? onInsert;
|
final Function(String)? onInsert;
|
||||||
|
|
||||||
const _StickerPicker({this.onDismiss, required this.originalText, this.onInsert});
|
const _StickerPicker(
|
||||||
|
{this.onDismiss, required this.originalText, this.onInsert});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -413,7 +438,9 @@ class _StickerPicker extends StatelessWidget {
|
|||||||
onDismiss?.call();
|
onDismiss?.call();
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints(maxWidth: min(360, MediaQuery.of(context).size.width), maxHeight: 240),
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: min(360, MediaQuery.of(context).size.width),
|
||||||
|
maxHeight: 240),
|
||||||
child: Material(
|
child: Material(
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
@ -426,8 +453,10 @@ class _StickerPicker extends StatelessWidget {
|
|||||||
return <Widget>[
|
return <Widget>[
|
||||||
Container(
|
Container(
|
||||||
margin: EdgeInsets.only(bottom: 8),
|
margin: EdgeInsets.only(bottom: 8),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding:
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -439,7 +468,8 @@ class _StickerPicker extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
GridView.builder(
|
GridView.builder(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8),
|
padding:
|
||||||
|
const EdgeInsets.only(left: 8, right: 8, bottom: 8),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
maxCrossAxisExtent: 48,
|
maxCrossAxisExtent: 48,
|
||||||
@ -462,7 +492,8 @@ class _StickerPicker extends StatelessWidget {
|
|||||||
richMessage: TextSpan(
|
richMessage: TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: ':${element.pack.prefix}${element.alias}:\n',
|
text:
|
||||||
|
':${element.pack.prefix}${element.alias}:\n',
|
||||||
style: GoogleFonts.robotoMono()),
|
style: GoogleFonts.robotoMono()),
|
||||||
TextSpan(text: element.name).bold(),
|
TextSpan(text: element.name).bold(),
|
||||||
],
|
],
|
||||||
@ -471,11 +502,15 @@ class _StickerPicker extends StatelessWidget {
|
|||||||
width: 48,
|
width: 48,
|
||||||
height: 48,
|
height: 48,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
Radius.circular(8)),
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surfaceContainerHigh,
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(8)),
|
||||||
child: UniversalImage(
|
child: UniversalImage(
|
||||||
sn.getAttachmentUrl(element.attachment.rid),
|
sn.getAttachmentUrl(element.attachment.rid),
|
||||||
width: 48,
|
width: 48,
|
||||||
|
@ -67,7 +67,10 @@ class PostMediaPendingList extends StatelessWidget {
|
|||||||
|
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
|
|
||||||
final rawBytes = (await result.uiImage.toByteData(format: ImageByteFormat.png))!.buffer.asUint8List();
|
final rawBytes =
|
||||||
|
(await result.uiImage.toByteData(format: ImageByteFormat.png))!
|
||||||
|
.buffer
|
||||||
|
.asUint8List();
|
||||||
|
|
||||||
if (onUpdate != null) {
|
if (onUpdate != null) {
|
||||||
final updatedMedia = PostWriteMedia.fromBytes(
|
final updatedMedia = PostWriteMedia.fromBytes(
|
||||||
@ -133,7 +136,8 @@ class PostMediaPendingList extends StatelessWidget {
|
|||||||
|
|
||||||
final result = await showDialog<SnAttachmentBoost?>(
|
final result = await showDialog<SnAttachmentBoost?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => PendingAttachmentBoostDialog(media: attachments[idx]),
|
builder: (context) =>
|
||||||
|
PendingAttachmentBoostDialog(media: attachments[idx]),
|
||||||
);
|
);
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
|
|
||||||
@ -165,11 +169,15 @@ class PostMediaPendingList extends StatelessWidget {
|
|||||||
onUpdate!(idx, PostWriteMedia(result));
|
onUpdate!(idx, PostWriteMedia(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
ContextMenu _createContextMenu(BuildContext context, int idx, PostWriteMedia media) {
|
ContextMenu _createContextMenu(
|
||||||
final canCompressVideo = !kIsWeb && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS);
|
BuildContext context, int idx, PostWriteMedia media) {
|
||||||
|
final canCompressVideo =
|
||||||
|
!kIsWeb && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS);
|
||||||
return ContextMenu(
|
return ContextMenu(
|
||||||
entries: [
|
entries: [
|
||||||
if (media.attachment == null && media.type == SnMediaType.video && canCompressVideo)
|
if (media.attachment == null &&
|
||||||
|
media.type == SnMediaType.video &&
|
||||||
|
canCompressVideo)
|
||||||
MenuItem(
|
MenuItem(
|
||||||
label: 'attachmentCompressVideo'.tr(),
|
label: 'attachmentCompressVideo'.tr(),
|
||||||
icon: Symbols.compress,
|
icon: Symbols.compress,
|
||||||
@ -312,12 +320,15 @@ class _PostMediaPendingItem extends StatelessWidget {
|
|||||||
AspectRatio(
|
AspectRatio(
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
child: switch (media.type) {
|
child: switch (media.type) {
|
||||||
SnMediaType.image => LayoutBuilder(builder: (context, constraints) {
|
SnMediaType.image =>
|
||||||
|
LayoutBuilder(builder: (context, constraints) {
|
||||||
return Image(
|
return Image(
|
||||||
image: media.getImageProvider(
|
image: media.getImageProvider(
|
||||||
context,
|
context,
|
||||||
width: (constraints.maxWidth * devicePixelRatio).round(),
|
width:
|
||||||
height: (constraints.maxHeight * devicePixelRatio).round(),
|
(constraints.maxWidth * devicePixelRatio).round(),
|
||||||
|
height:
|
||||||
|
(constraints.maxHeight * devicePixelRatio).round(),
|
||||||
)!,
|
)!,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
);
|
);
|
||||||
@ -326,28 +337,34 @@ class _PostMediaPendingItem extends StatelessWidget {
|
|||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
if (media.attachment?.thumbnail != null)
|
if (media.attachment?.thumbnail != null)
|
||||||
AutoResizeUniversalImage(sn.getAttachmentUrl(media.attachment!.thumbnail!.rid)),
|
AutoResizeUniversalImage(sn.getAttachmentUrl(
|
||||||
const Icon(Symbols.videocam, color: Colors.white, shadows: [
|
media.attachment!.thumbnail!.rid)),
|
||||||
Shadow(
|
const Icon(Symbols.videocam,
|
||||||
offset: Offset(1, 1),
|
color: Colors.white,
|
||||||
blurRadius: 8.0,
|
shadows: [
|
||||||
color: Color.fromARGB(255, 0, 0, 0),
|
Shadow(
|
||||||
),
|
offset: Offset(1, 1),
|
||||||
]),
|
blurRadius: 8.0,
|
||||||
|
color: Color.fromARGB(255, 0, 0, 0),
|
||||||
|
),
|
||||||
|
]),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SnMediaType.audio => Stack(
|
SnMediaType.audio => Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
if (media.attachment?.thumbnail != null)
|
if (media.attachment?.thumbnail != null)
|
||||||
AutoResizeUniversalImage(sn.getAttachmentUrl(media.attachment!.thumbnail!.rid)),
|
AutoResizeUniversalImage(sn.getAttachmentUrl(
|
||||||
const Icon(Symbols.audio_file, color: Colors.white, shadows: [
|
media.attachment!.thumbnail!.rid)),
|
||||||
Shadow(
|
const Icon(Symbols.audio_file,
|
||||||
offset: Offset(1, 1),
|
color: Colors.white,
|
||||||
blurRadius: 8.0,
|
shadows: [
|
||||||
color: Color.fromARGB(255, 0, 0, 0),
|
Shadow(
|
||||||
),
|
offset: Offset(1, 1),
|
||||||
]),
|
blurRadius: 8.0,
|
||||||
|
color: Color.fromARGB(255, 0, 0, 0),
|
||||||
|
),
|
||||||
|
]),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
_ => Container(
|
_ => Container(
|
||||||
@ -356,7 +373,8 @@ class _PostMediaPendingItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (media.type != SnMediaType.image) const VerticalDivider(width: 1, thickness: 1),
|
if (media.type != SnMediaType.image)
|
||||||
|
const VerticalDivider(width: 1, thickness: 1),
|
||||||
if (media.type != SnMediaType.image)
|
if (media.type != SnMediaType.image)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 160,
|
width: 160,
|
||||||
@ -374,7 +392,8 @@ class _PostMediaPendingItem extends StatelessWidget {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
)
|
)
|
||||||
else if (media.file != null)
|
else if (media.file != null)
|
||||||
Text(media.file!.name, maxLines: 1, overflow: TextOverflow.ellipsis)
|
Text(media.file!.name,
|
||||||
|
maxLines: 1, overflow: TextOverflow.ellipsis)
|
||||||
else
|
else
|
||||||
Text('unknown'.tr()),
|
Text('unknown'.tr()),
|
||||||
if (media.attachment != null)
|
if (media.attachment != null)
|
||||||
@ -387,7 +406,8 @@ class _PostMediaPendingItem extends StatelessWidget {
|
|||||||
FutureBuilder<int?>(
|
FutureBuilder<int?>(
|
||||||
future: media.length(),
|
future: media.length(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) return const SizedBox.shrink();
|
if (!snapshot.hasData)
|
||||||
|
return const SizedBox.shrink();
|
||||||
return Text(
|
return Text(
|
||||||
snapshot.data!.formatBytes(),
|
snapshot.data!.formatBytes(),
|
||||||
style: GoogleFonts.robotoMono(fontSize: 13),
|
style: GoogleFonts.robotoMono(fontSize: 13),
|
||||||
@ -398,7 +418,8 @@ class _PostMediaPendingItem extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (media.attachment != null && media.attachment!.boosts.isNotEmpty)
|
if (media.attachment != null &&
|
||||||
|
media.attachment!.boosts.isNotEmpty)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Symbols.bolt, size: 16),
|
Icon(Symbols.bolt, size: 16),
|
||||||
@ -406,7 +427,8 @@ class _PostMediaPendingItem extends StatelessWidget {
|
|||||||
Text('attachmentGotBoosted').tr().fontSize(13),
|
Text('attachmentGotBoosted').tr().fontSize(13),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (media.attachment != null && media.attachment!.compressedId != null)
|
if (media.attachment != null &&
|
||||||
|
media.attachment!.compressedId != null)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Symbols.compress, size: 16),
|
Icon(Symbols.compress, size: 16),
|
||||||
@ -527,12 +549,20 @@ class AddPostMediaButton extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PopupMenuButton(
|
return PopupMenuButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
style: ButtonStyle(
|
||||||
|
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
||||||
|
),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Symbols.add_photo_alternate,
|
Symbols.add_photo_alternate,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
itemBuilder: (context) => [
|
itemBuilder: (context) => [
|
||||||
if (!kIsWeb && !Platform.isLinux && !Platform.isMacOS && !Platform.isWindows)
|
if (!kIsWeb &&
|
||||||
|
!Platform.isLinux &&
|
||||||
|
!Platform.isMacOS &&
|
||||||
|
!Platform.isWindows)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@ -545,7 +575,10 @@ class AddPostMediaButton extends StatelessWidget {
|
|||||||
_takeMedia(false);
|
_takeMedia(false);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (!kIsWeb && !Platform.isLinux && !Platform.isMacOS && !Platform.isWindows)
|
if (!kIsWeb &&
|
||||||
|
!Platform.isLinux &&
|
||||||
|
!Platform.isMacOS &&
|
||||||
|
!Platform.isWindows)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user