diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 2592f560c3..8def2114ed 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -31,6 +31,7 @@ import 'package:fluffychat/utils/error_reporter.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/utils/text_direction.dart'; import 'package:fluffychat/widgets/app_lock.dart'; import 'package:fluffychat/widgets/matrix.dart'; import '../../utils/account_bundles.dart'; @@ -1058,7 +1059,8 @@ class ChatController extends State message: room .getState(EventTypes.RoomTombstone)! .parsedTombstoneContent - .body, + .body + .bidiFormatted, okLabel: L10n.of(context)!.ok, cancelLabel: L10n.of(context)!.cancel, )) { diff --git a/lib/pages/chat/chat_app_bar_list_tile.dart b/lib/pages/chat/chat_app_bar_list_tile.dart index 1e0ec82599..1678660c7a 100644 --- a/lib/pages/chat/chat_app_bar_list_tile.dart +++ b/lib/pages/chat/chat_app_bar_list_tile.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/utils/text_direction.dart'; import 'package:fluffychat/utils/url_launcher.dart'; class ChatAppBarListTile extends StatelessWidget { @@ -33,7 +34,7 @@ class ChatAppBarListTile extends StatelessWidget { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 4.0), child: Linkify( - text: title, + text: title.bidiFormatted, options: const LinkifyOptions(humanize: false), maxLines: 1, overflow: TextOverflow.ellipsis, diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index 4d5bcdc8c3..d00013ecca 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -11,6 +11,7 @@ import 'package:linkify/linkify.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/utils/text_direction.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/mxc_image.dart'; import '../../../utils/url_launcher.dart'; @@ -73,87 +74,92 @@ class HtmlMessage extends StatelessWidget { padding: HtmlPaddings.only(left: 6, bottom: 0), ); + final direction = html.textDirectionHtml ?? Directionality.of(context); + final element = _linkifyHtml(HtmlParser.parseHTML(html)); // there is no need to pre-validate the html, as we validate it while rendering - return Html.fromElement( - documentElement: element as dom.Element, - style: { - '*': Style( - color: textColor, - margin: Margins.all(0), - fontSize: FontSize(fontSize), - ), - 'a': Style(color: linkColor, textDecorationColor: linkColor), - 'h1': Style( - fontSize: FontSize(fontSize * 2), - lineHeight: LineHeight.number(1.5), - fontWeight: FontWeight.w600, - ), - 'h2': Style( - fontSize: FontSize(fontSize * 1.75), - lineHeight: LineHeight.number(1.5), - fontWeight: FontWeight.w500, - ), - 'h3': Style( - fontSize: FontSize(fontSize * 1.5), - lineHeight: LineHeight.number(1.5), - ), - 'h4': Style( - fontSize: FontSize(fontSize * 1.25), - lineHeight: LineHeight.number(1.5), - ), - 'h5': Style( - fontSize: FontSize(fontSize * 1.25), - lineHeight: LineHeight.number(1.5), - ), - 'h6': Style( - fontSize: FontSize(fontSize), - lineHeight: LineHeight.number(1.5), - ), - 'blockquote': blockquoteStyle, - 'tg-forward': blockquoteStyle, - 'hr': Style( - border: Border.all(color: textColor, width: 0.5), - ), - 'table': Style( - border: Border.all(color: textColor, width: 0.5), - ), - 'tr': Style( - border: Border.all(color: textColor, width: 0.5), - ), - 'td': Style( - border: Border.all(color: textColor, width: 0.5), - padding: HtmlPaddings.all(2), - ), - 'th': Style( - border: Border.all(color: textColor, width: 0.5), - ), - }, - extensions: [ - RoomPillExtension(context, room, fontSize, linkColor), - CodeExtension(fontSize: fontSize), - MatrixMathExtension( - style: TextStyle(fontSize: fontSize, color: textColor), - ), - const TableHtmlExtension(), - SpoilerExtension(textColor: textColor), - const ImageExtension(), - FontColorExtension(), - FallbackTextExtension(fontSize: fontSize), - ], - onLinkTap: (url, _, element) => UrlLauncher( - context, - url, - element?.text, - ).launchUrl(), - onlyRenderTheseTags: const { - ...allowedHtmlTags, - // Needed to make it work properly - 'body', - 'html', - }, - shrinkWrap: true, + return Directionality( + textDirection: direction, + child: Html.fromElement( + documentElement: element as dom.Element, + style: { + '*': Style( + color: textColor, + margin: Margins.all(0), + fontSize: FontSize(fontSize), + ), + 'a': Style(color: linkColor, textDecorationColor: linkColor), + 'h1': Style( + fontSize: FontSize(fontSize * 2), + lineHeight: LineHeight.number(1.5), + fontWeight: FontWeight.w600, + ), + 'h2': Style( + fontSize: FontSize(fontSize * 1.75), + lineHeight: LineHeight.number(1.5), + fontWeight: FontWeight.w500, + ), + 'h3': Style( + fontSize: FontSize(fontSize * 1.5), + lineHeight: LineHeight.number(1.5), + ), + 'h4': Style( + fontSize: FontSize(fontSize * 1.25), + lineHeight: LineHeight.number(1.5), + ), + 'h5': Style( + fontSize: FontSize(fontSize * 1.25), + lineHeight: LineHeight.number(1.5), + ), + 'h6': Style( + fontSize: FontSize(fontSize), + lineHeight: LineHeight.number(1.5), + ), + 'blockquote': blockquoteStyle, + 'tg-forward': blockquoteStyle, + 'hr': Style( + border: Border.all(color: textColor, width: 0.5), + ), + 'table': Style( + border: Border.all(color: textColor, width: 0.5), + ), + 'tr': Style( + border: Border.all(color: textColor, width: 0.5), + ), + 'td': Style( + border: Border.all(color: textColor, width: 0.5), + padding: HtmlPaddings.all(2), + ), + 'th': Style( + border: Border.all(color: textColor, width: 0.5), + ), + }, + extensions: [ + RoomPillExtension(context, room, fontSize, linkColor), + CodeExtension(fontSize: fontSize), + MatrixMathExtension( + style: TextStyle(fontSize: fontSize, color: textColor), + ), + const TableHtmlExtension(), + SpoilerExtension(textColor: textColor), + const ImageExtension(), + FontColorExtension(), + FallbackTextExtension(fontSize: fontSize), + ], + onLinkTap: (url, _, element) => UrlLauncher( + context, + url, + element?.text, + ).launchUrl(), + onlyRenderTheseTags: const { + ...allowedHtmlTags, + // Needed to make it work properly + 'body', + 'html', + }, + shrinkWrap: true, + ), ); } diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 5aeb650a47..1a4e0cf4b3 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -10,6 +10,7 @@ import 'package:fluffychat/pages/chat/events/video_player.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; +import 'package:fluffychat/utils/text_direction.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; import '../../../config/app_config.dart'; @@ -86,9 +87,11 @@ class MessageContent extends StatelessWidget { ), const Divider(), Text( - event.calcLocalizedBodyFallback( - MatrixLocals(l10n), - ), + event + .calcLocalizedBodyFallback( + MatrixLocals(l10n), + ) + .bidiFormatted, ), ], ), @@ -252,10 +255,12 @@ class MessageContent extends StatelessWidget { event.numberEmotes > 0 && event.numberEmotes <= 10; return Linkify( - text: event.calcLocalizedBodyFallback( - MatrixLocals(L10n.of(context)!), - hideReply: true, - ), + text: event + .calcLocalizedBodyFallback( + MatrixLocals(L10n.of(context)!), + hideReply: true, + ) + .bidiFormatted, style: TextStyle( color: textColor, fontSize: bigEmotes ? fontSize * 3 : fontSize, diff --git a/lib/pages/chat/events/reply_content.dart b/lib/pages/chat/events/reply_content.dart index 945ae22ac3..78af70f680 100644 --- a/lib/pages/chat/events/reply_content.dart +++ b/lib/pages/chat/events/reply_content.dart @@ -4,6 +4,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; +import 'package:fluffychat/utils/text_direction.dart'; import '../../../config/app_config.dart'; class ReplyContent extends StatelessWidget { @@ -72,11 +73,13 @@ class ReplyContent extends StatelessWidget { }, ), Text( - displayEvent.calcLocalizedBodyFallback( - MatrixLocals(L10n.of(context)!), - withSenderNamePrefix: false, - hideReply: true, - ), + displayEvent + .calcLocalizedBodyFallback( + MatrixLocals(L10n.of(context)!), + withSenderNamePrefix: false, + hideReply: true, + ) + .bidiFormatted, overflow: TextOverflow.ellipsis, maxLines: 1, style: TextStyle( diff --git a/lib/pages/chat/events/state_message.dart b/lib/pages/chat/events/state_message.dart index 0aa5d9dc23..313867b82d 100644 --- a/lib/pages/chat/events/state_message.dart +++ b/lib/pages/chat/events/state_message.dart @@ -4,6 +4,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; +import 'package:fluffychat/utils/text_direction.dart'; import '../../../config/app_config.dart'; class StateMessage extends StatelessWidget { @@ -22,9 +23,11 @@ class StateMessage extends StatelessWidget { borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), ), child: Text( - event.calcLocalizedBodyFallback( - MatrixLocals(L10n.of(context)!), - ), + event + .calcLocalizedBodyFallback( + MatrixLocals(L10n.of(context)!), + ) + .bidiFormatted, textAlign: TextAlign.center, style: TextStyle( fontSize: 12 * AppConfig.fontSizeFactor, diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index d4917c1dc9..21b374f23c 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -10,6 +10,7 @@ import 'package:slugify/slugify.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/utils/text_direction.dart'; import 'package:fluffychat/widgets/mxc_image.dart'; import '../../widgets/avatar.dart'; import '../../widgets/matrix.dart'; @@ -452,45 +453,48 @@ class InputBar extends StatelessWidget { hideOnSelect: false, debounceDuration: const Duration(milliseconds: 50), // show suggestions after 50ms idle time (default is 300) - builder: (context, controller, focusNode) => TextField( - controller: controller, - focusNode: focusNode, - contentInsertionConfiguration: ContentInsertionConfiguration( - onContentInserted: (KeyboardInsertedContent content) { - final data = content.data; - if (data == null) return; + builder: (context, controller, focusNode) => AutoDirection( + text: controller.text, + child: TextField( + controller: controller, + focusNode: focusNode, + contentInsertionConfiguration: ContentInsertionConfiguration( + onContentInserted: (KeyboardInsertedContent content) { + final data = content.data; + if (data == null) return; - final file = MatrixFile( - mimeType: content.mimeType, - bytes: data, - name: content.uri.split('/').last, - ); - room.sendFileEvent( - file, - shrinkImageMaxDimension: 1600, - ); + final file = MatrixFile( + mimeType: content.mimeType, + bytes: data, + name: content.uri.split('/').last, + ); + room.sendFileEvent( + file, + shrinkImageMaxDimension: 1600, + ); + }, + ), + minLines: minLines, + maxLines: maxLines, + keyboardType: keyboardType!, + textInputAction: textInputAction, + autofocus: autofocus!, + inputFormatters: [ + LengthLimitingTextInputFormatter((maxPDUSize / 3).floor()), + ], + onSubmitted: (text) { + // fix for library for now + // it sets the types for the callback incorrectly + onSubmitted!(text); + }, + decoration: decoration!, + onChanged: (text) { + // fix for the library for now + // it sets the types for the callback incorrectly + onChanged!(text); }, + textCapitalization: TextCapitalization.sentences, ), - minLines: minLines, - maxLines: maxLines, - keyboardType: keyboardType!, - textInputAction: textInputAction, - autofocus: autofocus!, - inputFormatters: [ - LengthLimitingTextInputFormatter((maxPDUSize / 3).floor()), - ], - onSubmitted: (text) { - // fix for library for now - // it sets the types for the callback incorrectly - onSubmitted!(text); - }, - decoration: decoration!, - onChanged: (text) { - // fix for the library for now - // it sets the types for the callback incorrectly - onChanged!(text); - }, - textCapitalization: TextCapitalization.sentences, ), suggestionsCallback: getSuggestions, itemBuilder: (c, s) => diff --git a/lib/pages/chat/pinned_events.dart b/lib/pages/chat/pinned_events.dart index 0940a786f3..5da5a3a750 100644 --- a/lib/pages/chat/pinned_events.dart +++ b/lib/pages/chat/pinned_events.dart @@ -10,6 +10,7 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/chat_app_bar_list_tile.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; +import 'package:fluffychat/utils/text_direction.dart'; class PinnedEvents extends StatelessWidget { final ChatController controller; @@ -37,11 +38,13 @@ class PinnedEvents extends StatelessWidget { .map( (event) => AlertDialogAction( key: event?.eventId ?? '', - label: event?.calcLocalizedBodyFallback( - MatrixLocals(L10n.of(context)!), - withSenderNamePrefix: true, - hideReply: true, - ) ?? + label: event + ?.calcLocalizedBodyFallback( + MatrixLocals(L10n.of(context)!), + withSenderNamePrefix: true, + hideReply: true, + ) + .bidiFormatted ?? 'UNKNOWN', ), ) @@ -64,11 +67,13 @@ class PinnedEvents extends StatelessWidget { builder: (context, snapshot) { final event = snapshot.data; return ChatAppBarListTile( - title: event?.calcLocalizedBodyFallback( - MatrixLocals(L10n.of(context)!), - withSenderNamePrefix: true, - hideReply: true, - ) ?? + title: event + ?.calcLocalizedBodyFallback( + MatrixLocals(L10n.of(context)!), + withSenderNamePrefix: true, + hideReply: true, + ) + .bidiFormatted ?? L10n.of(context)!.loadingPleaseWait, leading: IconButton( splashRadius: 20, diff --git a/lib/pages/chat/reply_display.dart b/lib/pages/chat/reply_display.dart index 03acd269e4..65de6d4bbe 100644 --- a/lib/pages/chat/reply_display.dart +++ b/lib/pages/chat/reply_display.dart @@ -4,6 +4,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; +import 'package:fluffychat/utils/text_direction.dart'; import '../../config/themes.dart'; import 'chat.dart'; import 'events/reply_content.dart'; @@ -67,11 +68,13 @@ class _EditContent extends StatelessWidget { ), Container(width: 15.0), Text( - event.calcLocalizedBodyFallback( - MatrixLocals(L10n.of(context)!), - withSenderNamePrefix: false, - hideReply: true, - ), + event + .calcLocalizedBodyFallback( + MatrixLocals(L10n.of(context)!), + withSenderNamePrefix: false, + hideReply: true, + ) + .bidiFormatted, overflow: TextOverflow.ellipsis, maxLines: 1, style: TextStyle( diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index 4e19e43cab..7a9bef60d5 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -10,6 +10,7 @@ import 'package:fluffychat/pages/chat_details/chat_details.dart'; import 'package:fluffychat/pages/chat_details/participant_list_item.dart'; import 'package:fluffychat/utils/fluffy_share.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; +import 'package:fluffychat/utils/text_direction.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/chat_settings_popup_menu.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; @@ -240,7 +241,7 @@ class ChatDetailsView extends StatelessWidget { child: SelectableLinkify( text: room.topic.isEmpty ? L10n.of(context)!.noChatDescriptionYet - : room.topic, + : room.topic.bidiFormatted, options: const LinkifyOptions(humanize: false), linkStyle: const TextStyle( color: Colors.blueAccent, diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 1ba515e29b..6c1d5e5965 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -8,6 +8,7 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/room_status_extension.dart'; +import 'package:fluffychat/utils/text_direction.dart'; import 'package:fluffychat/widgets/hover_builder.dart'; import '../../config/themes.dart'; import '../../utils/date_time_extension.dart'; @@ -290,28 +291,32 @@ class ChatListItem extends StatelessWidget { '${lastEvent?.eventId}_${lastEvent?.type}', ), future: needLastEventSender - ? lastEvent.calcLocalizedBody( - MatrixLocals(L10n.of(context)!), - hideReply: true, - hideEdit: true, - plaintextBody: true, - removeMarkdown: true, - withSenderNamePrefix: (!isDirectChat || - directChatMatrixId != - room.lastEvent?.senderId), - ) + ? lastEvent + .calcLocalizedBody( + MatrixLocals(L10n.of(context)!), + hideReply: true, + hideEdit: true, + plaintextBody: true, + removeMarkdown: true, + withSenderNamePrefix: + (!isDirectChat || + directChatMatrixId != + room.lastEvent?.senderId), + ) + .then((e) => e.bidiFormatted) : null, - initialData: - lastEvent?.calcLocalizedBodyFallback( - MatrixLocals(L10n.of(context)!), - hideReply: true, - hideEdit: true, - plaintextBody: true, - removeMarkdown: true, - withSenderNamePrefix: (!isDirectChat || - directChatMatrixId != - room.lastEvent?.senderId), - ), + initialData: lastEvent + ?.calcLocalizedBodyFallback( + MatrixLocals(L10n.of(context)!), + hideReply: true, + hideEdit: true, + plaintextBody: true, + removeMarkdown: true, + withSenderNamePrefix: (!isDirectChat || + directChatMatrixId != + room.lastEvent?.senderId), + ) + .bidiFormatted, builder: (context, snapshot) => Text( room.membership == Membership.invite ? isDirectChat diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 3401aa1f1e..65229062ba 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -12,6 +12,7 @@ import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; import 'package:fluffychat/pages/chat_list/search_title.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/stream_extension.dart'; +import 'package:fluffychat/utils/text_direction.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -95,7 +96,7 @@ class _SpaceViewState extends State { final consent = await showOkCancelAlertDialog( context: context, title: item.name ?? item.canonicalAlias ?? L10n.of(context)!.emptyChat, - message: item.topic, + message: item.topic?.bidiFormatted, okLabel: L10n.of(context)!.joinRoom, cancelLabel: L10n.of(context)!.cancel, ); diff --git a/lib/pages/chat_search/chat_search_message_tab.dart b/lib/pages/chat_search/chat_search_message_tab.dart index 7542d6ae9e..9b5cb07185 100644 --- a/lib/pages/chat_search/chat_search_message_tab.dart +++ b/lib/pages/chat_search/chat_search_message_tab.dart @@ -7,6 +7,7 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; +import 'package:fluffychat/utils/text_direction.dart'; import 'package:fluffychat/utils/url_launcher.dart'; import 'package:fluffychat/widgets/avatar.dart'; @@ -166,7 +167,8 @@ class _MessageSearchResultListTile extends StatelessWidget { L10n.of(context)!, ), ) - .trim(), + .trim() + .bidiFormatted, maxLines: 7, overflow: TextOverflow.ellipsis, ), diff --git a/lib/utils/push_helper.dart b/lib/utils/push_helper.dart index edfdb49418..a100932b50 100644 --- a/lib/utils/push_helper.dart +++ b/lib/utils/push_helper.dart @@ -16,6 +16,7 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/utils/client_manager.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/utils/text_direction.dart'; import 'package:fluffychat/utils/voip/callkeep_manager.dart'; Future pushHelper( @@ -167,14 +168,16 @@ Future _tryPushHelper( // Calculate the body final body = event.type == EventTypes.Encrypted ? l10n.newMessageInFluffyChat - : await event.calcLocalizedBody( - matrixLocals, - plaintextBody: true, - withSenderNamePrefix: false, - hideReply: true, - hideEdit: true, - removeMarkdown: true, - ); + : await event + .calcLocalizedBody( + matrixLocals, + plaintextBody: true, + withSenderNamePrefix: false, + hideReply: true, + hideEdit: true, + removeMarkdown: true, + ) + .then((e) => e.bidiFormatted); // The person object for the android message style notification final avatar = event.room.avatar @@ -280,14 +283,16 @@ Future _tryPushHelper( groupConversation: !event.room.isDirectChat, messages: [newMessage], ), - ticker: event.calcLocalizedBodyFallback( - matrixLocals, - plaintextBody: true, - withSenderNamePrefix: true, - hideReply: true, - hideEdit: true, - removeMarkdown: true, - ), + ticker: event + .calcLocalizedBodyFallback( + matrixLocals, + plaintextBody: true, + withSenderNamePrefix: true, + hideReply: true, + hideEdit: true, + removeMarkdown: true, + ) + .bidiFormatted, importance: Importance.high, priority: Priority.max, groupKey: notificationGroupId, diff --git a/lib/utils/text_direction.dart b/lib/utils/text_direction.dart new file mode 100644 index 0000000000..5dc57f4a54 --- /dev/null +++ b/lib/utils/text_direction.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; + +import 'package:intl/intl.dart' as intl; + +extension BidiFormatter on String { + String get bidiFormatted { + return intl.BidiFormatter.UNKNOWN().wrapWithUnicode(this); + } + + TextDirection? get textDirectionHtml { + switch ( + intl.BidiFormatter.UNKNOWN().estimateDirection(this, isHtml: true)) { + case intl.TextDirection.LTR: + return TextDirection.ltr; + case intl.TextDirection.RTL: + return TextDirection.rtl; + case intl.TextDirection.UNKNOWN: + default: + return null; + } + } +} + +class AutoDirection extends StatefulWidget { + final String text; + final Widget child; + final void Function(bool isRTL)? onDirectionChange; + + const AutoDirection({ + super.key, + required this.text, + required this.child, + this.onDirectionChange, + }); + + @override + AutoDirectionState createState() => AutoDirectionState(); +} + +class AutoDirectionState extends State { + late String text; + late Widget childWidget; + + @override + Widget build(BuildContext context) { + text = widget.text; + childWidget = widget.child; + return Directionality( + textDirection: isRTL(text) ? TextDirection.rtl : TextDirection.ltr, + child: childWidget, + ); + } + + @override + void didUpdateWidget(AutoDirection oldWidget) { + if (isRTL(oldWidget.text) != isRTL(widget.text)) { + WidgetsBinding.instance.addPostFrameCallback( + (_) => widget.onDirectionChange?.call(isRTL(widget.text)), + ); + } + super.didUpdateWidget(oldWidget); + } + + bool isRTL(String text) { + if (text.isEmpty) return Directionality.of(context) == TextDirection.rtl; + return intl.Bidi.detectRtlDirectionality(text); + } +} diff --git a/lib/widgets/local_notifications_extension.dart b/lib/widgets/local_notifications_extension.dart index e3d9ff3c44..c921e2b2ba 100644 --- a/lib/widgets/local_notifications_extension.dart +++ b/lib/widgets/local_notifications_extension.dart @@ -14,6 +14,7 @@ import 'package:universal_html/html.dart' as html; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/utils/text_direction.dart'; import 'package:fluffychat/widgets/matrix.dart'; extension LocalNotificationsExtension on MatrixState { @@ -64,7 +65,7 @@ extension LocalNotificationsExtension on MatrixState { _audioPlayer.play(); html.Notification( title, - body: body, + body: body.bidiFormatted, icon: icon.toString(), ); } else if (Platform.isLinux) { diff --git a/lib/widgets/public_room_bottom_sheet.dart b/lib/widgets/public_room_bottom_sheet.dart index 8bd354ef5e..3c0bc4e282 100644 --- a/lib/widgets/public_room_bottom_sheet.dart +++ b/lib/widgets/public_room_bottom_sheet.dart @@ -7,6 +7,7 @@ import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/utils/fluffy_share.dart'; +import 'package:fluffychat/utils/text_direction.dart'; import 'package:fluffychat/utils/url_launcher.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -184,7 +185,7 @@ class PublicRoomBottomSheet extends StatelessWidget { if (profile?.topic?.isNotEmpty ?? false) ListTile( subtitle: SelectableLinkify( - text: profile!.topic!, + text: profile!.topic!.bidiFormatted, linkStyle: const TextStyle( color: Colors.blueAccent, decorationColor: Colors.blueAccent,