From 7443309b151fd4c729022ee3a353ad9fa073b1d9 Mon Sep 17 00:00:00 2001 From: Sorunome Date: Sat, 7 Aug 2021 19:36:42 +0200 Subject: [PATCH] feat: Add option to not autoplay stickers and emotes --- assets/l10n/intl_en.arb | 5 ++ lib/config/app_config.dart | 1 + lib/config/setting_keys.dart | 1 + lib/pages/views/image_viewer_view.dart | 1 + lib/pages/views/settings_chat_view.dart | 6 +++ lib/widgets/event_content/html_message.dart | 16 +++++- lib/widgets/event_content/image_bubble.dart | 43 +++++++++++++--- .../event_content/message_content.dart | 27 +--------- lib/widgets/event_content/sticker.dart | 51 +++++++++++++++++++ lib/widgets/matrix.dart | 3 ++ lib/widgets/settings_switch_list_tile.dart | 4 +- 11 files changed, 125 insertions(+), 33 deletions(-) create mode 100644 lib/widgets/event_content/sticker.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index c66578b8..63d89cb0 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -23,6 +23,11 @@ "type": "text", "placeholders": {} }, + "autoplayImages": "Automatically play animated stickers and emotes", + "@autoplayImages": { + "type": "text", + "placeholder": {} + }, "chats": "Chats", "@chats": { "type": "text", diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 523f2f87..c6f04334 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -29,6 +29,7 @@ abstract class AppConfig { static bool renderHtml = true; static bool hideRedactedEvents = false; static bool hideUnknownEvents = true; + static bool autoplayImages = true; static const bool hideTypingUsernames = false; static const bool hideAllStateEvents = false; static const String inviteLinkPrefix = 'https://matrix.to/#/'; diff --git a/lib/config/setting_keys.dart b/lib/config/setting_keys.dart index 62f774eb..d0bd7e66 100644 --- a/lib/config/setting_keys.dart +++ b/lib/config/setting_keys.dart @@ -20,4 +20,5 @@ abstract class SettingKeys { static const String ownStatusMessage = 'chat.fluffy.status_msg'; static const String dontAskForBootstrapKey = 'chat.fluffychat.dont_ask_bootstrap'; + static const String autoplayImages = 'chat.fluffy.autoplay_images'; } diff --git a/lib/pages/views/image_viewer_view.dart b/lib/pages/views/image_viewer_view.dart index eeaad16b..f0958eb8 100644 --- a/lib/pages/views/image_viewer_view.dart +++ b/lib/pages/views/image_viewer_view.dart @@ -51,6 +51,7 @@ class ImageViewerView extends StatelessWidget { maxSize: false, radius: 0.0, thumbnailOnly: false, + animated: true, ), ), ), diff --git a/lib/pages/views/settings_chat_view.dart b/lib/pages/views/settings_chat_view.dart index b7878c03..b517092c 100644 --- a/lib/pages/views/settings_chat_view.dart +++ b/lib/pages/views/settings_chat_view.dart @@ -49,6 +49,12 @@ class SettingsChatView extends StatelessWidget { storeKey: SettingKeys.hideUnknownEvents, defaultValue: AppConfig.hideUnknownEvents, ), + SettingsSwitchListTile( + title: L10n.of(context).autoplayImages, + onChanged: (b) => AppConfig.autoplayImages = b, + storeKey: SettingKeys.autoplayImages, + defaultValue: AppConfig.autoplayImages, + ), ], ), ), diff --git a/lib/widgets/event_content/html_message.dart b/lib/widgets/event_content/html_message.dart index 07019e84..d62f74b5 100644 --- a/lib/widgets/event_content/html_message.dart +++ b/lib/widgets/event_content/html_message.dart @@ -3,8 +3,10 @@ import 'package:flutter_matrix_html/flutter_html.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import '../../utils/url_launcher.dart'; +import '../../config/app_config.dart'; import '../../config/setting_keys.dart'; import '../../utils/matrix_sdk_extensions.dart/matrix_locals.dart'; +import '../../pages/image_viewer.dart'; import '../matrix.dart'; @@ -64,10 +66,22 @@ class HtmlMessage extends StatelessWidget { width: (width ?? 800) * ratio, height: (height ?? 800) * ratio, method: ThumbnailMethod.scale, - animated: animated, + animated: AppConfig.autoplayImages ? animated : false, ) .toString(); }, + onImageTap: (String mxc) => showDialog( + context: Matrix.of(context).navigatorContext, + useRootNavigator: false, + builder: (_) => ImageViewer(Event.fromJson({ + 'type': EventTypes.Message, + 'content': { + 'body': mxc, + 'url': mxc, + 'msgtype': MessageTypes.Image, + }, + 'event_id': 'fake_event', + }, room))), setCodeLanguage: (String key, String value) async { await matrix.store.setItem('${SettingKeys.codeLanguage}.$key', value); }, diff --git a/lib/widgets/event_content/image_bubble.dart b/lib/widgets/event_content/image_bubble.dart index 44eef9e7..15486b90 100644 --- a/lib/widgets/event_content/image_bubble.dart +++ b/lib/widgets/event_content/image_bubble.dart @@ -21,6 +21,7 @@ class ImageBubble extends StatefulWidget { final Color backgroundColor; final double radius; final bool thumbnailOnly; + final bool animated; final double width; final double height; final void Function() onLoaded; @@ -37,6 +38,7 @@ class ImageBubble extends StatefulWidget { this.onLoaded, this.width = 400, this.height = 300, + this.animated = false, this.onTap, Key key, }) : super(key: key); @@ -53,6 +55,9 @@ class _ImageBubbleState extends State { MatrixFile _file; MatrixFile _thumbnail; bool _requestedThumbnailOnFailure = false; + // In case we have animated = false, this will hold the first frame so that we make + // sure that things are never animated + Widget _firstFrame; // the mimetypes that we know how to render, from the flutter Image widget final _knownMimetypes = { @@ -108,6 +113,19 @@ class _ImageBubbleState extends State { } } + Widget frameBuilder( + BuildContext context, Widget child, int frame, bool sync) { + // as servers might return animated gifs as thumbnails and we want them to *not* play + // animated, we'll have to store the first frame in a variable and display that instead + if (widget.animated) { + return child; + } + if (frame == 0) { + _firstFrame = child; + } + return _firstFrame ?? child; + } + @override void initState() { // add the custom renderers for other mimetypes @@ -131,6 +149,7 @@ class _ImageBubbleState extends State { fit: widget.fit, errorBuilder: (context, error, stacktrace) => getErrorWidget(context, error), + animate: widget.animated, ), network: (String url) => Lottie.network( url, @@ -138,6 +157,7 @@ class _ImageBubbleState extends State { fit: widget.fit, errorBuilder: (context, error, stacktrace) => getErrorWidget(context, error), + animate: widget.animated, ), ); @@ -147,9 +167,10 @@ class _ImageBubbleState extends State { } thumbnailUrl = widget.event - .getAttachmentUrl(getThumbnail: true, animated: true) + .getAttachmentUrl(getThumbnail: true, animated: widget.animated) ?.toString(); - attachmentUrl = widget.event.getAttachmentUrl(animated: true)?.toString(); + attachmentUrl = + widget.event.getAttachmentUrl(animated: widget.animated)?.toString(); if (thumbnailUrl == null) { _requestFile(getThumbnail: true); } @@ -263,6 +284,7 @@ class _ImageBubbleState extends State { } return getErrorWidget(context, error); }, + frameBuilder: frameBuilder, ); } } @@ -292,11 +314,20 @@ class _ImageBubbleState extends State { key: ValueKey(thumbnailUrl), imageUrl: thumbnailUrl, placeholder: (c, u) => getPlaceholderWidget(), - fit: widget.fit, + imageBuilder: (context, imageProvider) => Image( + image: imageProvider, + frameBuilder: frameBuilder, + fit: widget.fit, + ), ); } return getPlaceholderWidget(); }, + imageBuilder: (context, imageProvider) => Image( + image: imageProvider, + frameBuilder: frameBuilder, + fit: widget.fit, + ), errorWidget: (context, url, error) { if (widget.event.hasThumbnail && !_requestedThumbnailOnFailure) { // the image failed to load but the event has a thumbnail attached....so we can @@ -308,10 +339,11 @@ class _ImageBubbleState extends State { .getAttachmentUrl( getThumbnail: true, useThumbnailMxcUrl: true, - animated: true) + animated: widget.animated) ?.toString(); attachmentUrl = widget.event - .getAttachmentUrl(useThumbnailMxcUrl: true, animated: true) + .getAttachmentUrl( + useThumbnailMxcUrl: true, animated: widget.animated) ?.toString(); }); }); @@ -346,7 +378,6 @@ class _ImageBubbleState extends State { } return getErrorWidget(context, error); }, - fit: widget.fit, ); } } diff --git a/lib/widgets/event_content/message_content.dart b/lib/widgets/event_content/message_content.dart index f6b2feef..bbe96add 100644 --- a/lib/widgets/event_content/message_content.dart +++ b/lib/widgets/event_content/message_content.dart @@ -6,7 +6,6 @@ import 'package:fluffychat/widgets/event_content/image_bubble.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions.dart/event_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions.dart/matrix_locals.dart'; import 'package:fluffychat/pages/key_verification_dialog.dart'; -import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -21,6 +20,7 @@ import 'html_message.dart'; import '../matrix.dart'; import 'message_download_content.dart'; import 'map_bubble.dart'; +import 'sticker.dart'; class MessageContent extends StatelessWidget { final Event event; @@ -91,30 +91,7 @@ class MessageContent extends StatelessWidget { return MessageDownloadContent(event, textColor); case MessageTypes.Sticker: if (event.showThumbnail) { - // stickers should default to a ratio of 1:1 - var ratio = 1.0; - // if a width and a height is specified for stickers, use those! - if (event.infoMap['w'] is int && event.infoMap['h'] is int) { - ratio = event.infoMap['w'] / event.infoMap['h']; - // make sure the ratio is within 0.9 - 2.0 - if (ratio > 2.0) { - ratio = 2.0; - } - if (ratio < 0.9) { - ratio = 0.9; - } - } - return ImageBubble( - event, - width: 400, - height: 400 / ratio, - fit: ratio <= 1.0 ? BoxFit.contain : BoxFit.cover, - onTap: () => showOkAlertDialog( - context: context, - message: event.body, - okLabel: L10n.of(context).ok, - ), - ); + return Sticker(event); } return MessageDownloadContent(event, textColor); case MessageTypes.Audio: diff --git a/lib/widgets/event_content/sticker.dart b/lib/widgets/event_content/sticker.dart new file mode 100644 index 00000000..b38706ad --- /dev/null +++ b/lib/widgets/event_content/sticker.dart @@ -0,0 +1,51 @@ +import 'package:matrix/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import '../../config/app_config.dart'; +import 'image_bubble.dart'; + +class Sticker extends StatefulWidget { + final Event event; + + const Sticker(this.event, {Key key}) : super(key: key); + + @override + _StickerState createState() => _StickerState(); +} + +class _StickerState extends State { + bool animated; + + @override + Widget build(BuildContext context) { + // stickers should default to a ratio of 1:1 + var ratio = 1.0; + // if a width and a height is specified for stickers, use those! + if (widget.event.infoMap['w'] is int && widget.event.infoMap['h'] is int) { + ratio = widget.event.infoMap['w'] / widget.event.infoMap['h']; + // make sure the ratio is within 0.9 - 2.0 + if (ratio > 2.0) { + ratio = 2.0; + } + if (ratio < 0.9) { + ratio = 0.9; + } + } + return ImageBubble( + widget.event, + width: 400, + height: 400 / ratio, + fit: ratio <= 1.0 ? BoxFit.contain : BoxFit.cover, + onTap: () { + setState(() => animated = true); + showOkAlertDialog( + context: context, + message: widget.event.body, + okLabel: L10n.of(context).ok, + ); + }, + animated: animated ?? AppConfig.autoplayImages, + ); + } +} diff --git a/lib/widgets/matrix.dart b/lib/widgets/matrix.dart index cefeaf52..bb07c9c1 100644 --- a/lib/widgets/matrix.dart +++ b/lib/widgets/matrix.dart @@ -414,6 +414,9 @@ class MatrixState extends State with WidgetsBindingObserver { .getItemBool( SettingKeys.hideUnknownEvents, AppConfig.hideUnknownEvents) .then((value) => AppConfig.hideUnknownEvents = value); + store + .getItemBool(SettingKeys.autoplayImages, AppConfig.autoplayImages) + .then((value) => AppConfig.autoplayImages = value); } } diff --git a/lib/widgets/settings_switch_list_tile.dart b/lib/widgets/settings_switch_list_tile.dart index bd960f86..a09d40b4 100644 --- a/lib/widgets/settings_switch_list_tile.dart +++ b/lib/widgets/settings_switch_list_tile.dart @@ -24,7 +24,9 @@ class _SettingsSwitchListTileState extends State { @override Widget build(BuildContext context) { return FutureBuilder( - future: Matrix.of(context).store.getItemBool(widget.storeKey), + future: Matrix.of(context) + .store + .getItemBool(widget.storeKey, widget.defaultValue), builder: (context, snapshot) => SwitchListTile( value: snapshot.data ?? widget.defaultValue, title: Text(widget.title),