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",
|
"blockedUsers": "Usuários bloqueados",
|
||||||
"passwordIsWrong": "A senha digitada está incorreta",
|
"passwordIsWrong": "A senha digitada está incorreta",
|
||||||
"pleaseEnterYourCurrentPassword": "Digite sua senha atual",
|
"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": "Infelizmente, não foi encontrado usuário com \"{query}\". Verifique se digitou corretamente.",
|
||||||
"@noUsersFoundWithQuery": {
|
"@noUsersFoundWithQuery": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ import 'package:collection/collection.dart';
|
|||||||
import 'package:desktop_drop/desktop_drop.dart';
|
import 'package:desktop_drop/desktop_drop.dart';
|
||||||
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
|
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
|
||||||
import 'package:file_picker/file_picker.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/setting_keys.dart';
|
||||||
import 'package:tatchat/config/themes.dart';
|
import 'package:tatchat/config/themes.dart';
|
||||||
import 'package:tatchat/l10n/l10n.dart';
|
import 'package:tatchat/l10n/l10n.dart';
|
||||||
import 'package:tatchat/pages/chat/chat_view.dart';
|
import 'package:tatchat/pages/chat/chat_view.dart';
|
||||||
import 'package:tatchat/pages/chat/event_info_dialog.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/start_poll_bottom_sheet.dart';
|
||||||
import 'package:tatchat/pages/chat/trust_user_key_dialog.dart';
|
import 'package:tatchat/pages/chat/trust_user_key_dialog.dart';
|
||||||
import 'package:tatchat/pages/chat/utils/web_file_to_x_file.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;
|
final timeline = this.timeline;
|
||||||
if (timeline == null) return;
|
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(
|
final client = currentRoomBundle.firstWhere(
|
||||||
(c) => c?.userID == event.senderId,
|
(c) => c?.userID == event.senderId,
|
||||||
orElse: () => null,
|
orElse: () => null,
|
||||||
@@ -1336,6 +1349,33 @@ class ChatController extends State<ChatPageWithRoom>
|
|||||||
inputFocus.requestFocus();
|
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() {
|
void editSelectedEventAction() {
|
||||||
_startEditingEvent(selectedEvents.first, clearSelection: true);
|
_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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
|
||||||
|
|
||||||
var borderRadius =
|
var borderRadius =
|
||||||
this.borderRadius ?? BorderRadius.circular(AppConfig.borderRadius);
|
this.borderRadius ?? BorderRadius.circular(AppConfig.borderRadius);
|
||||||
|
|
||||||
@@ -70,14 +68,7 @@ class ImageBubble extends StatelessWidget {
|
|||||||
Material(
|
Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: borderRadius),
|
||||||
borderRadius: borderRadius,
|
|
||||||
side: BorderSide(
|
|
||||||
color: event.messageType == MessageTypes.Sticker
|
|
||||||
? Colors.transparent
|
|
||||||
: theme.dividerColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import 'package:async/async.dart' show Result;
|
|||||||
import 'package:cross_file/cross_file.dart';
|
import 'package:cross_file/cross_file.dart';
|
||||||
import 'package:cupertino_ui/cupertino_ui.dart';
|
import 'package:cupertino_ui/cupertino_ui.dart';
|
||||||
import 'package:html/parser.dart' show parseFragment;
|
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/app_config.dart';
|
||||||
import 'package:tatchat/config/setting_keys.dart';
|
import 'package:tatchat/config/setting_keys.dart';
|
||||||
import 'package:tatchat/l10n/l10n.dart';
|
import 'package:tatchat/l10n/l10n.dart';
|
||||||
@@ -133,7 +133,10 @@ class SendFileDialogState extends State<SendFileDialog> {
|
|||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
final extra = <String, Object>{'body': label};
|
final extra = <String, Object>{'body': label};
|
||||||
final html = markdownToHtml(label);
|
final html = markdownToHtml(
|
||||||
|
label,
|
||||||
|
extensionSet: ExtensionSet.gitHubFlavored,
|
||||||
|
);
|
||||||
if (parseFragment(html).text != label) {
|
if (parseFragment(html).text != label) {
|
||||||
extra['format'] = 'org.matrix.custom.html';
|
extra['format'] = 'org.matrix.custom.html';
|
||||||
extra['formatted_body'] = html;
|
extra['formatted_body'] = html;
|
||||||
|
|||||||
@@ -1280,10 +1280,11 @@ class _PostViewerState extends State<_PostViewer> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else
|
else ...[
|
||||||
...commentTree.map(
|
for (var i = 0; i < commentTree.length; i++) ...[
|
||||||
(node) => _CommentTile(
|
if (i > 0) const Divider(height: 1),
|
||||||
node: node,
|
_CommentTile(
|
||||||
|
node: commentTree[i],
|
||||||
depth: 0,
|
depth: 0,
|
||||||
timeline: widget.timeline,
|
timeline: widget.timeline,
|
||||||
currentUserId: widget.room.client.userID,
|
currentUserId: widget.room.client.userID,
|
||||||
@@ -1295,7 +1296,8 @@ class _PostViewerState extends State<_PostViewer> {
|
|||||||
onDelete: _deleteComment,
|
onDelete: _deleteComment,
|
||||||
onReport: _reportComment,
|
onReport: _reportComment,
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1598,20 +1600,23 @@ class _CommentTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
...node.replies.map(
|
...[
|
||||||
(reply) => _CommentTile(
|
for (var i = 0; i < node.replies.length; i++) ...[
|
||||||
node: reply,
|
if (i > 0) const Divider(height: 1),
|
||||||
depth: depth + 1,
|
_CommentTile(
|
||||||
timeline: timeline,
|
node: node.replies[i],
|
||||||
currentUserId: currentUserId,
|
depth: depth + 1,
|
||||||
onReply: onReply,
|
timeline: timeline,
|
||||||
onToggleReaction: onToggleReaction,
|
currentUserId: currentUserId,
|
||||||
onShowReactionPicker: onShowReactionPicker,
|
onReply: onReply,
|
||||||
onOpenProfile: onOpenProfile,
|
onToggleReaction: onToggleReaction,
|
||||||
onDelete: onDelete,
|
onShowReactionPicker: onShowReactionPicker,
|
||||||
onReport: onReport,
|
onOpenProfile: onOpenProfile,
|
||||||
),
|
onDelete: onDelete,
|
||||||
),
|
onReport: onReport,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user