mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-11-04 06:17:26 +01:00 
			
		
		
		
	Merge branch 'soru/animated-images' into 'main'
feat: Add option to not autoplay stickers and emotes See merge request famedly/fluffychat!476
This commit is contained in:
		
						commit
						79c77e41e3
					
				@ -23,6 +23,11 @@
 | 
			
		||||
    "type": "text",
 | 
			
		||||
    "placeholders": {}
 | 
			
		||||
  },
 | 
			
		||||
  "autoplayImages": "Automatically play animated stickers and emotes",
 | 
			
		||||
  "@autoplayImages": {
 | 
			
		||||
    "type": "text",
 | 
			
		||||
    "placeholder": {}
 | 
			
		||||
  },
 | 
			
		||||
  "chats": "Chats",
 | 
			
		||||
  "@chats": {
 | 
			
		||||
    "type": "text",
 | 
			
		||||
 | 
			
		||||
@ -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/#/';
 | 
			
		||||
 | 
			
		||||
@ -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';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -51,6 +51,7 @@ class ImageViewerView extends StatelessWidget {
 | 
			
		||||
            maxSize: false,
 | 
			
		||||
            radius: 0.0,
 | 
			
		||||
            thumbnailOnly: false,
 | 
			
		||||
            animated: true,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
 | 
			
		||||
@ -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': <String, dynamic>{
 | 
			
		||||
                  '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);
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
@ -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<ImageBubble> {
 | 
			
		||||
  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 = <String>{
 | 
			
		||||
@ -108,6 +113,19 @@ class _ImageBubbleState extends State<ImageBubble> {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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<ImageBubble> {
 | 
			
		||||
        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<ImageBubble> {
 | 
			
		||||
        fit: widget.fit,
 | 
			
		||||
        errorBuilder: (context, error, stacktrace) =>
 | 
			
		||||
            getErrorWidget(context, error),
 | 
			
		||||
        animate: widget.animated,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -147,9 +167,10 @@ class _ImageBubbleState extends State<ImageBubble> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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<ImageBubble> {
 | 
			
		||||
          }
 | 
			
		||||
          return getErrorWidget(context, error);
 | 
			
		||||
        },
 | 
			
		||||
        frameBuilder: frameBuilder,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -292,11 +314,20 @@ class _ImageBubbleState extends State<ImageBubble> {
 | 
			
		||||
              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<ImageBubble> {
 | 
			
		||||
                    .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<ImageBubble> {
 | 
			
		||||
          }
 | 
			
		||||
          return getErrorWidget(context, error);
 | 
			
		||||
        },
 | 
			
		||||
        fit: widget.fit,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -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:
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										51
									
								
								lib/widgets/event_content/sticker.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								lib/widgets/event_content/sticker.dart
									
									
									
									
									
										Normal file
									
								
							@ -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<Sticker> {
 | 
			
		||||
  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,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -414,6 +414,9 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
 | 
			
		||||
          .getItemBool(
 | 
			
		||||
              SettingKeys.hideUnknownEvents, AppConfig.hideUnknownEvents)
 | 
			
		||||
          .then((value) => AppConfig.hideUnknownEvents = value);
 | 
			
		||||
      store
 | 
			
		||||
          .getItemBool(SettingKeys.autoplayImages, AppConfig.autoplayImages)
 | 
			
		||||
          .then((value) => AppConfig.autoplayImages = value);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,9 @@ class _SettingsSwitchListTileState extends State<SettingsSwitchListTile> {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return FutureBuilder<bool>(
 | 
			
		||||
      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),
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user