diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 77be7b987..5e46c9bd0 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3203,5 +3203,6 @@ "crossVerifiedDevices": "Cross verified devices", "verifiedDevicesOnly": "Verified devices only", "takeAPhoto": "Take a photo", - "recordAVideo": "Record a video" + "recordAVideo": "Record a video", + "optionalMessage": "(Optional) message..." } diff --git a/lib/pages/chat/send_file_dialog.dart b/lib/pages/chat/send_file_dialog.dart index 42fad69bc..4e4faadd2 100644 --- a/lib/pages/chat/send_file_dialog.dart +++ b/lib/pages/chat/send_file_dialog.dart @@ -13,6 +13,7 @@ import 'package:fluffychat/utils/other_party_can_receive.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/size_string.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart'; +import 'package:fluffychat/widgets/adaptive_dialogs/dialog_text_field.dart'; import '../../utils/resize_video.dart'; class SendFileDialog extends StatefulWidget { @@ -37,6 +38,8 @@ class SendFileDialogState extends State { /// Images smaller than 20kb don't need compression. static const int minSizeToCompress = 20 * 1000; + final TextEditingController _labelTextController = TextEditingController(); + Future _send() async { final scaffoldMessenger = ScaffoldMessenger.of(widget.outerContext); final l10n = L10n.of(context); @@ -93,11 +96,14 @@ class SendFileDialogState extends State { scaffoldMessenger.clearSnackBars(); } + final label = _labelTextController.text.trim(); + try { await widget.room.sendFileEvent( file, thumbnail: thumbnail, shrinkImageMaxDimension: compress ? 1600 : null, + extraContent: label.isEmpty ? null : {'body': label}, ); } on MatrixException catch (e) { final retryAfterMs = e.retryAfterMs; @@ -121,7 +127,8 @@ class SendFileDialogState extends State { await widget.room.sendFileEvent( file, thumbnail: thumbnail, - shrinkImageMaxDimension: compress ? null : 1600, + shrinkImageMaxDimension: compress ? 1600 : null, + extraContent: label.isEmpty ? null : {'body': label}, ); } } @@ -306,6 +313,18 @@ class SendFileDialogState extends State { ], ), ), + if (widget.files.length == 1) + Padding( + padding: const EdgeInsets.symmetric(vertical: 12.0), + child: DialogTextField( + controller: _labelTextController, + labelText: L10n.of(context).optionalMessage, + minLines: 1, + maxLines: 3, + maxLength: 255, + counterText: '', + ), + ), // Workaround for SwitchListTile.adaptive crashes in CupertinoDialog if ({'image', 'video'}.contains(uniqueFileType)) Row( diff --git a/lib/widgets/adaptive_dialogs/dialog_text_field.dart b/lib/widgets/adaptive_dialogs/dialog_text_field.dart new file mode 100644 index 000000000..c80147878 --- /dev/null +++ b/lib/widgets/adaptive_dialogs/dialog_text_field.dart @@ -0,0 +1,95 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class DialogTextField extends StatelessWidget { + final TextEditingController? controller; + final String? hintText; + final String? labelText; + final String? initialText; + final String? counterText; + final String? prefixText; + final String? suffixText; + final String? errorText; + final bool obscureText = false; + final bool isDestructive = false; + final int? minLines; + final int? maxLines; + final TextInputType? keyboardType; + final int? maxLength; + final bool autocorrect = true; + + const DialogTextField({ + super.key, + this.hintText, + this.labelText, + this.initialText, + this.prefixText, + this.suffixText, + this.minLines, + this.maxLines, + this.keyboardType, + this.maxLength, + this.controller, + this.counterText, + this.errorText, + }); + + @override + Widget build(BuildContext context) { + final prefixText = this.prefixText; + final suffixText = this.suffixText; + final errorText = this.errorText; + final theme = Theme.of(context); + switch (theme.platform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + return TextField( + controller: controller, + obscureText: obscureText, + minLines: minLines, + maxLines: maxLines, + maxLength: maxLength, + keyboardType: keyboardType, + autocorrect: autocorrect, + decoration: InputDecoration( + errorText: errorText, + hintText: hintText, + labelText: labelText, + prefixText: prefixText, + suffixText: suffixText, + counterText: counterText, + ), + ); + case TargetPlatform.iOS: + case TargetPlatform.macOS: + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + CupertinoTextField( + controller: controller, + obscureText: obscureText, + minLines: minLines, + maxLines: maxLines, + maxLength: maxLength, + keyboardType: keyboardType, + autocorrect: autocorrect, + prefix: prefixText != null ? Text(prefixText) : null, + suffix: suffixText != null ? Text(suffixText) : null, + placeholder: labelText ?? hintText, + ), + if (errorText != null) + Text( + errorText, + style: TextStyle( + fontSize: 11, + color: theme.colorScheme.error, + ), + textAlign: TextAlign.left, + ), + ], + ); + } + } +} diff --git a/lib/widgets/adaptive_dialogs/show_text_input_dialog.dart b/lib/widgets/adaptive_dialogs/show_text_input_dialog.dart index dd3d8f06a..4ad5ea3f0 100644 --- a/lib/widgets/adaptive_dialogs/show_text_input_dialog.dart +++ b/lib/widgets/adaptive_dialogs/show_text_input_dialog.dart @@ -1,4 +1,3 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -6,6 +5,7 @@ import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:fluffychat/utils/url_launcher.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart'; +import 'package:fluffychat/widgets/adaptive_dialogs/dialog_text_field.dart'; Future showTextInputDialog({ required BuildContext context, @@ -28,7 +28,6 @@ Future showTextInputDialog({ int? maxLength, bool autocorrect = true, }) { - final theme = Theme.of(context); return showAdaptiveDialog( context: context, useRootNavigator: useRootNavigator, @@ -61,58 +60,19 @@ Future showTextInputDialog({ ValueListenableBuilder( valueListenable: error, builder: (context, error, _) { - switch (theme.platform) { - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - return TextField( - controller: controller, - obscureText: obscureText, - minLines: minLines, - maxLines: maxLines, - maxLength: maxLength, - keyboardType: keyboardType, - autocorrect: autocorrect, - decoration: InputDecoration( - errorText: error, - hintText: hintText, - labelText: labelText, - prefixText: prefixText, - suffixText: suffixText, - ), - ); - case TargetPlatform.iOS: - case TargetPlatform.macOS: - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - CupertinoTextField( - controller: controller, - obscureText: obscureText, - minLines: minLines, - maxLines: maxLines, - maxLength: maxLength, - keyboardType: keyboardType, - autocorrect: autocorrect, - prefix: - prefixText != null ? Text(prefixText) : null, - suffix: - suffixText != null ? Text(suffixText) : null, - placeholder: labelText ?? hintText, - ), - if (error != null) - Text( - error, - style: TextStyle( - fontSize: 11, - color: theme.colorScheme.error, - ), - textAlign: TextAlign.left, - ), - ], - ); - } + return DialogTextField( + hintText: hintText, + errorText: error, + labelText: labelText, + controller: controller, + initialText: initialText, + prefixText: prefixText, + suffixText: suffixText, + minLines: minLines, + maxLines: maxLines, + maxLength: maxLength, + keyboardType: keyboardType, + ); }, ), ],