diff --git a/lib/models/content/content.dart b/lib/models/content/content.dart index e6382e0..ab17db8 100644 --- a/lib/models/content/content.dart +++ b/lib/models/content/content.dart @@ -14,6 +14,7 @@ import "package:nexus/models/content/encrypted.dart"; import "package:nexus/models/content/redaction.dart"; import "package:nexus/models/content/server_acl.dart"; import "package:nexus/models/content/topic.dart"; +import "package:nexus/models/content/sticker.dart"; import "package:nexus/models/content/history_visibility.dart"; class Content { @@ -50,6 +51,7 @@ enum EventType { HistoryVisibilityContent.fromJson, ), canonicalAlias("m.room.canonical_alias", CanonicalAliasContent.fromJson), + sticker("m.sticker", StickerContent.fromJson), joinRules("m.room.join_rules", JoinRulesContent.fromJson), powerLevels("m.room.power_levels", PowerLevelsContent.fromJson), serverACL("m.room.server_acl", ServerACLContent.fromJson), diff --git a/lib/models/content/sticker.dart b/lib/models/content/sticker.dart new file mode 100644 index 0000000..89d9332 --- /dev/null +++ b/lib/models/content/sticker.dart @@ -0,0 +1,18 @@ +import "package:freezed_annotation/freezed_annotation.dart"; +import "package:nexus/models/content/content.dart"; +import "package:nexus/models/info/image.dart"; +part "sticker.freezed.dart"; +part "sticker.g.dart"; + +@freezed +abstract class StickerContent extends Content with _$StickerContent { + StickerContent._(); + factory StickerContent({ + required String body, + required ImageInfo info, + required Uri url, + }) = _StickerContent; + + factory StickerContent.fromJson(Map json) => + _$StickerContentFromJson(json); +} diff --git a/lib/widgets/message_image.dart b/lib/widgets/message_image.dart new file mode 100644 index 0000000..e2cc823 --- /dev/null +++ b/lib/widgets/message_image.dart @@ -0,0 +1,56 @@ +import "package:cross_cache/cross_cache.dart"; +import "package:flutter/material.dart"; +import "package:flutter_blurhash/flutter_blurhash.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:nexus/controllers/cross_cache_controller.dart"; +import "package:nexus/helpers/extensions/get_headers.dart"; +import "package:nexus/models/info/image.dart" as i; +import "package:nexus/widgets/expandable_image.dart"; +import "package:nexus/widgets/loading.dart"; + +class MessageImage extends ConsumerWidget { + final Uri url; + final i.ImageInfo? info; + const MessageImage(this.url, {this.info, super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) => ExpandableImage( + url.toString(), + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(8)), + child: Image( + image: CachedNetworkImage( + url.toString(), + ref.watch(CrossCacheController.provider), + headers: ref.headers, + ), + width: info?.width, + loadingBuilder: (_, child, loadingProgress) => loadingProgress == null + ? child + : switch (info?.blurHash) { + final blurHash? => + info?.width == null || info?.height == null + ? SizedBox( + width: 200, + height: 200, + child: BlurHash(hash: blurHash), + ) + : AspectRatio( + aspectRatio: info!.width! / info!.height!, + child: SizedBox( + width: info!.width, + child: BlurHash(hash: blurHash), + ), + ), + _ => Loading(), + }, + errorBuilder: (context, error, stackTrace) => Center( + child: Text( + "Image Failed to Load", + style: TextStyle(color: Theme.of(context).colorScheme.error), + ), + ), + ), + ), + ); +} diff --git a/lib/widgets/renderers/event.dart b/lib/widgets/renderers/event.dart index 20085c0..5f42fd9 100644 --- a/lib/widgets/renderers/event.dart +++ b/lib/widgets/renderers/event.dart @@ -14,6 +14,7 @@ import "package:nexus/models/content/message.dart"; import "package:nexus/models/content/pinned_events.dart"; import "package:nexus/models/content/power_levels.dart"; import "package:nexus/models/content/server_acl.dart"; +import "package:nexus/models/content/sticker.dart"; import "package:nexus/models/content/topic.dart"; import "package:nexus/models/event.dart"; import "package:nexus/widgets/lazy_loading/message_displayname.dart"; @@ -53,7 +54,9 @@ class EventRenderer extends ConsumerWidget { style: errorStyle, ), - MessageContent() || EncryptedContent() => MessageRenderer( + MessageContent() || + EncryptedContent() || + StickerContent() => MessageRenderer( event, onTapReply: onTapReply, isGrouped: isGrouped, diff --git a/lib/widgets/renderers/message.dart b/lib/widgets/renderers/message.dart index 11d5556..6957c54 100644 --- a/lib/widgets/renderers/message.dart +++ b/lib/widgets/renderers/message.dart @@ -1,25 +1,21 @@ import "package:collection/collection.dart"; -import "package:cross_cache/cross_cache.dart"; import "package:flutter/material.dart"; -import "package:flutter_blurhash/flutter_blurhash.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:linkify/linkify.dart"; import "package:nexus/controllers/client_state_controller.dart"; -import "package:nexus/controllers/cross_cache_controller.dart"; import "package:nexus/controllers/event_controller.dart"; -import "package:nexus/helpers/extensions/get_headers.dart"; import "package:nexus/helpers/extensions/mxc_to_https.dart"; import "package:nexus/models/content/encrypted.dart"; import "package:nexus/models/content/message.dart"; +import "package:nexus/models/content/sticker.dart"; import "package:nexus/models/event.dart"; import "package:nexus/models/requests/get_event_request.dart"; -import "package:nexus/widgets/expandable_image.dart"; import "package:nexus/widgets/file_card.dart"; import "package:nexus/widgets/html/html.dart"; import "package:nexus/widgets/lazy_loading/message_avatar.dart"; import "package:nexus/widgets/lazy_loading/message_displayname.dart"; import "package:nexus/widgets/linkified_text.dart"; -import "package:nexus/widgets/loading.dart"; +import "package:nexus/widgets/message_image.dart"; import "package:nexus/widgets/reaction_row.dart"; import "package:nexus/widgets/url_preview.dart"; import "package:timeago/timeago.dart"; @@ -144,6 +140,20 @@ class MessageRenderer extends ConsumerWidget { "Unable to decrypt event", style: errorStyle, ), + StickerContent(:final url, :final info) => + ConstrainedBox( + constraints: BoxConstraints.loose(Size.square(200)), + child: MessageImage( + url.mxcToHttps( + ref.watch( + ClientStateController.provider.select( + (value) => value!.homeserverUrl!, + ), + ), + ), + info: info, + ), + ), // TODO: Handle locations // LocationMessageContent(:final body , :final geoUri) => TextMessageContent( @@ -239,75 +249,7 @@ class MessageRenderer extends ConsumerWidget { ) => FileCard(url, info, filename: filename), ImageMessageContent(:final info) => - ExpandableImage( - url.toString(), - child: ClipRRect( - borderRadius: BorderRadius.all( - Radius.circular(8), - ), - child: Image( - image: CachedNetworkImage( - url.toString(), - ref.watch( - CrossCacheController.provider, - ), - headers: ref.headers, - ), - width: info?.width, - loadingBuilder: - ( - _, - child, - loadingProgress, - ) => loadingProgress == null - ? child - : switch (info?.blurHash) { - final blurHash? => - info?.width == null || - info?.height == - null - ? SizedBox( - width: 200, - height: 200, - child: BlurHash( - hash: - blurHash, - ), - ) - : AspectRatio( - aspectRatio: - info! - .width! / - info.height!, - child: SizedBox( - width: info - .width, - child: BlurHash( - hash: - blurHash, - ), - ), - ), - _ => Loading(), - }, - errorBuilder: - ( - context, - error, - stackTrace, - ) => Center( - child: Text( - "Image Failed to Load", - style: TextStyle( - color: Theme.of( - context, - ).colorScheme.error, - ), - ), - ), - ), - ), - ), + MessageImage(url, info: info), _ => SizedBox.shrink(), }, ),