feat: suporte a tabelas markdown nas legendas, remoção de borda de imagens e separação de comentários no mural
This commit is contained in:
@@ -1986,7 +1986,7 @@
|
||||
"blockedUsers": "Usuários bloqueados",
|
||||
"passwordIsWrong": "A senha digitada está incorreta",
|
||||
"pleaseEnterYourCurrentPassword": "Digite sua senha atual",
|
||||
"groupCanBeFoundViaSearch": "Grupos podem ser encontrados por pesquisa",
|
||||
"groupCanBeFoundViaSearch": "Sala pode ser encontrada na pesquisa",
|
||||
"noUsersFoundWithQuery": "Infelizmente, não foi encontrado usuário com \"{query}\". Verifique se digitou corretamente.",
|
||||
"@noUsersFoundWithQuery": {
|
||||
"type": "String",
|
||||
|
||||
@@ -10,11 +10,14 @@ import 'package:collection/collection.dart';
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:html/parser.dart' show parseFragment;
|
||||
import 'package:markdown/markdown.dart' show ExtensionSet, markdownToHtml;
|
||||
import 'package:tatchat/config/setting_keys.dart';
|
||||
import 'package:tatchat/config/themes.dart';
|
||||
import 'package:tatchat/l10n/l10n.dart';
|
||||
import 'package:tatchat/pages/chat/chat_view.dart';
|
||||
import 'package:tatchat/pages/chat/event_info_dialog.dart';
|
||||
import 'package:tatchat/pages/chat/edit_media_caption_dialog.dart';
|
||||
import 'package:tatchat/pages/chat/start_poll_bottom_sheet.dart';
|
||||
import 'package:tatchat/pages/chat/trust_user_key_dialog.dart';
|
||||
import 'package:tatchat/pages/chat/utils/web_file_to_x_file.dart';
|
||||
@@ -1314,6 +1317,16 @@ class ChatController extends State<ChatPageWithRoom>
|
||||
final timeline = this.timeline;
|
||||
if (timeline == null) return;
|
||||
|
||||
if (const {
|
||||
MessageTypes.Image,
|
||||
MessageTypes.Video,
|
||||
MessageTypes.Audio,
|
||||
MessageTypes.File,
|
||||
}.contains(event.messageType)) {
|
||||
_startEditingMediaCaption(event, clearSelection: clearSelection);
|
||||
return;
|
||||
}
|
||||
|
||||
final client = currentRoomBundle.firstWhere(
|
||||
(c) => c?.userID == event.senderId,
|
||||
orElse: () => null,
|
||||
@@ -1336,6 +1349,33 @@ class ChatController extends State<ChatPageWithRoom>
|
||||
inputFocus.requestFocus();
|
||||
}
|
||||
|
||||
Future<void> _startEditingMediaCaption(
|
||||
Event event, {
|
||||
bool clearSelection = false,
|
||||
}) async {
|
||||
final timeline = this.timeline;
|
||||
if (timeline == null) return;
|
||||
if (clearSelection) setState(() => selectedEvents.clear());
|
||||
final caption = event.getDisplayEvent(timeline).body;
|
||||
final result = await showEditMediaCaptionDialog(
|
||||
context,
|
||||
initialCaption: caption,
|
||||
);
|
||||
if (result == null) return;
|
||||
final html = markdownToHtml(
|
||||
result,
|
||||
extensionSet: ExtensionSet.gitHubFlavored,
|
||||
);
|
||||
final content = Map<String, Object?>.of(event.content)
|
||||
..remove('m.relates_to')
|
||||
..['body'] = result;
|
||||
if (parseFragment(html).text != result) {
|
||||
content['format'] = 'org.matrix.custom.html';
|
||||
content['formatted_body'] = html;
|
||||
}
|
||||
await room.sendEvent(content, editEventId: event.eventId);
|
||||
}
|
||||
|
||||
void editSelectedEventAction() {
|
||||
_startEditingEvent(selectedEvents.first, clearSelection: true);
|
||||
}
|
||||
|
||||
86
lib/pages/chat/edit_media_caption_dialog.dart
Normal file
86
lib/pages/chat/edit_media_caption_dialog.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
import 'package:tatchat/config/setting_keys.dart';
|
||||
import 'package:tatchat/l10n/l10n.dart';
|
||||
import 'package:tatchat/utils/platform_infos.dart';
|
||||
import 'package:tatchat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
|
||||
import 'package:tatchat/widgets/adaptive_dialogs/dialog_text_field.dart';
|
||||
|
||||
Future<String?> showEditMediaCaptionDialog(
|
||||
BuildContext context, {
|
||||
required String initialCaption,
|
||||
}) => showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) => EditMediaCaptionDialog(initialCaption: initialCaption),
|
||||
);
|
||||
|
||||
class EditMediaCaptionDialog extends StatefulWidget {
|
||||
final String initialCaption;
|
||||
|
||||
const EditMediaCaptionDialog({super.key, required this.initialCaption});
|
||||
|
||||
@override
|
||||
State<EditMediaCaptionDialog> createState() => _EditMediaCaptionDialogState();
|
||||
}
|
||||
|
||||
class _EditMediaCaptionDialogState extends State<EditMediaCaptionDialog> {
|
||||
late final TextEditingController _controller = TextEditingController(
|
||||
text: widget.initialCaption,
|
||||
);
|
||||
late final FocusNode _focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_focusNode.dispose();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isMobile = PlatformInfos.isMobile;
|
||||
final l10n = L10n.of(context);
|
||||
return AlertDialog.adaptive(
|
||||
title: Text(l10n.editCaption),
|
||||
insetPadding: isMobile
|
||||
? const EdgeInsets.all(8.0)
|
||||
: const EdgeInsets.symmetric(horizontal: 40, vertical: 24),
|
||||
constraints: isMobile
|
||||
? const BoxConstraints(
|
||||
maxWidth: double.infinity,
|
||||
maxHeight: double.infinity,
|
||||
)
|
||||
: null,
|
||||
content: SizedBox(
|
||||
width: 512,
|
||||
height: isMobile ? MediaQuery.sizeOf(context).height * 0.7 : null,
|
||||
child: DialogTextField(
|
||||
controller: _controller,
|
||||
focusNode: _focusNode,
|
||||
hintText: l10n.captionHint,
|
||||
labelText: l10n.optionalMessage,
|
||||
keyboardType: TextInputType.multiline,
|
||||
minLines: isMobile ? 8 : 3,
|
||||
maxLines: isMobile ? null : 8,
|
||||
maxLength: AppSettings.textMessageMaxLength.value,
|
||||
counterText: '',
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
AdaptiveDialogAction(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
AdaptiveDialogAction(
|
||||
autofocus: true,
|
||||
onPressed: () => Navigator.of(context).pop(_controller.text.trim()),
|
||||
child: Text(l10n.save),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -48,8 +48,6 @@ class ImageBubble extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
var borderRadius =
|
||||
this.borderRadius ?? BorderRadius.circular(AppConfig.borderRadius);
|
||||
|
||||
@@ -70,14 +68,7 @@ class ImageBubble extends StatelessWidget {
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: borderRadius,
|
||||
side: BorderSide(
|
||||
color: event.messageType == MessageTypes.Sticker
|
||||
? Colors.transparent
|
||||
: theme.dividerColor,
|
||||
),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: borderRadius),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: borderRadius,
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:async/async.dart' show Result;
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
import 'package:cupertino_ui/cupertino_ui.dart';
|
||||
import 'package:html/parser.dart' show parseFragment;
|
||||
import 'package:markdown/markdown.dart' show markdownToHtml;
|
||||
import 'package:markdown/markdown.dart' show ExtensionSet, markdownToHtml;
|
||||
import 'package:tatchat/config/app_config.dart';
|
||||
import 'package:tatchat/config/setting_keys.dart';
|
||||
import 'package:tatchat/l10n/l10n.dart';
|
||||
@@ -133,7 +133,10 @@ class SendFileDialogState extends State<SendFileDialog> {
|
||||
? null
|
||||
: () {
|
||||
final extra = <String, Object>{'body': label};
|
||||
final html = markdownToHtml(label);
|
||||
final html = markdownToHtml(
|
||||
label,
|
||||
extensionSet: ExtensionSet.gitHubFlavored,
|
||||
);
|
||||
if (parseFragment(html).text != label) {
|
||||
extra['format'] = 'org.matrix.custom.html';
|
||||
extra['formatted_body'] = html;
|
||||
|
||||
@@ -1280,10 +1280,11 @@ class _PostViewerState extends State<_PostViewer> {
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
...commentTree.map(
|
||||
(node) => _CommentTile(
|
||||
node: node,
|
||||
else ...[
|
||||
for (var i = 0; i < commentTree.length; i++) ...[
|
||||
if (i > 0) const Divider(height: 1),
|
||||
_CommentTile(
|
||||
node: commentTree[i],
|
||||
depth: 0,
|
||||
timeline: widget.timeline,
|
||||
currentUserId: widget.room.client.userID,
|
||||
@@ -1295,7 +1296,8 @@ class _PostViewerState extends State<_PostViewer> {
|
||||
onDelete: _deleteComment,
|
||||
onReport: _reportComment,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -1598,20 +1600,23 @@ class _CommentTile extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
...node.replies.map(
|
||||
(reply) => _CommentTile(
|
||||
node: reply,
|
||||
depth: depth + 1,
|
||||
timeline: timeline,
|
||||
currentUserId: currentUserId,
|
||||
onReply: onReply,
|
||||
onToggleReaction: onToggleReaction,
|
||||
onShowReactionPicker: onShowReactionPicker,
|
||||
onOpenProfile: onOpenProfile,
|
||||
onDelete: onDelete,
|
||||
onReport: onReport,
|
||||
),
|
||||
),
|
||||
...[
|
||||
for (var i = 0; i < node.replies.length; i++) ...[
|
||||
if (i > 0) const Divider(height: 1),
|
||||
_CommentTile(
|
||||
node: node.replies[i],
|
||||
depth: depth + 1,
|
||||
timeline: timeline,
|
||||
currentUserId: currentUserId,
|
||||
onReply: onReply,
|
||||
onToggleReaction: onToggleReaction,
|
||||
onShowReactionPicker: onShowReactionPicker,
|
||||
onOpenProfile: onOpenProfile,
|
||||
onDelete: onDelete,
|
||||
onReport: onReport,
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user