diff --git a/lib/controllers/url_preview_controller.dart b/lib/controllers/url_preview_controller.dart index dab3d97..15eac11 100644 --- a/lib/controllers/url_preview_controller.dart +++ b/lib/controllers/url_preview_controller.dart @@ -12,7 +12,9 @@ class UrlPreviewController extends AsyncNotifier { @override Future build() async { - final homeserver = ref.watch(ClientStateController.provider)?.homeserverUrl; + final homeserver = ref.watch( + ClientStateController.provider.select((value) => value?.homeserverUrl), + ); if (homeserver != null && !link.contains("matrix.to")) { { diff --git a/lib/models/content/message.dart b/lib/models/content/message.dart index 82b4604..1140809 100644 --- a/lib/models/content/message.dart +++ b/lib/models/content/message.dart @@ -33,7 +33,7 @@ abstract class MessageContent extends Content with _$MessageContent { // EncryptedFile? file String? filename, ImageInfo? info, - String? url, + Uri? url, }) = ImageMessageContent; @FreezedUnionValue("m.file") @@ -44,7 +44,7 @@ abstract class MessageContent extends Content with _$MessageContent { // EncryptedFile? file String? filename, FileInfo? info, - String? url, + Uri? url, }) = FileMessageContent; @FreezedUnionValue("m.audio") @@ -55,7 +55,7 @@ abstract class MessageContent extends Content with _$MessageContent { // EncryptedFile? file String? filename, AudioInfo? info, - String? url, + Uri? url, }) = AudioMessageContent; @FreezedUnionValue("m.video") @@ -66,7 +66,7 @@ abstract class MessageContent extends Content with _$MessageContent { // EncryptedFile? file String? filename, AudioInfo? info, - String? url, + Uri? url, }) = VideoMessageContent; @FreezedUnionValue("m.location") diff --git a/lib/models/info/image.dart b/lib/models/info/image.dart index 9397aa8..9833016 100644 --- a/lib/models/info/image.dart +++ b/lib/models/info/image.dart @@ -6,9 +6,10 @@ part "image.g.dart"; abstract class ImageInfo with _$ImageInfo { /// Information for images, [size] is in bytes. const factory ImageInfo({ - @JsonKey(name: "h") int? height, - @JsonKey(name: "w") int? width, + @JsonKey(name: "h") double? height, + @JsonKey(name: "w") double? width, @JsonKey(name: "mimetype") String? mimeType, + @JsonKey(name: "xyz.amorgan.blurhash") String? blurHash, int? size, }) = _ImageInfo; diff --git a/lib/widgets/chat_page/event_text.dart b/lib/widgets/chat_page/event_text.dart index d274dac..cf2ed80 100644 --- a/lib/widgets/chat_page/event_text.dart +++ b/lib/widgets/chat_page/event_text.dart @@ -1,17 +1,24 @@ +import "package:cross_cache/cross_cache.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter/material.dart"; +import "package:flutter_blurhash/flutter_blurhash.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client_state_controller.dart"; +import "package:nexus/controllers/cross_cache_controller.dart"; +import "package:nexus/helpers/extensions/get_headers.dart"; +import "package:nexus/helpers/extensions/mxc_to_https.dart"; import "package:nexus/helpers/extensions/show_context_menu.dart"; import "package:nexus/helpers/launch_helper.dart"; import "package:nexus/models/content/avatar.dart"; import "package:nexus/models/content/content.dart"; import "package:nexus/models/content/message.dart"; import "package:nexus/models/event.dart"; +import "package:nexus/widgets/chat_page/expandable_image.dart"; import "package:nexus/widgets/chat_page/html/html.dart"; import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart"; import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart"; import "package:nexus/widgets/link_preview.dart"; +import "package:nexus/widgets/loading.dart"; import "package:timeago/timeago.dart"; import "package:flutter_linkify/flutter_linkify.dart"; @@ -110,62 +117,122 @@ class EventText extends ConsumerWidget { :final body, :final formattedBody, :final format, - ) => - Column( - children: [ - format == "org.matrix.custom.html" && - !textOnly - ? Html( - textStyle: - event.localContent?.bigEmoji == - true - ? TextStyle(fontSize: 32) - : null, - formattedBody!.replaceAllMapped( - RegExp( - "(]*>.*?<\\/a>)|(\\bhttps?:\\/\\/[^\\s<]+)", - caseSensitive: false, - dotAll: true, - ), - (m) { - // If it's already an tag, leave it unchanged - if (m.group(1) != null) { - return m.group(1)!; - } + ) || + ImageMessageContent( + :final body, + :final formattedBody, + :final format, + ) => Column( + children: [ + format == "org.matrix.custom.html" && !textOnly + ? Html( + textStyle: + event.localContent?.bigEmoji == true + ? TextStyle(fontSize: 32) + : null, + formattedBody!.replaceAllMapped( + RegExp( + "(]*>.*?<\\/a>)|(\\bhttps?:\\/\\/[^\\s<]+)", + caseSensitive: false, + dotAll: true, + ), + (m) { + // If it's already an tag, leave it unchanged + if (m.group(1) != null) { + return m.group(1)!; + } - // Otherwise, wrap the bare URL - final url = m.group(2)!; - return "$url"; - }, - ), - ) - : Linkify( - text: body, - maxLines: maxLines, - options: LinkifyOptions( - humanize: false, - ), - onOpen: (link) => ref - .watch(LaunchHelper.provider) - .launchUrl(Uri.parse(link.url)), - linkStyle: TextStyle( - color: Theme.of( - context, - ).colorScheme.primary, - ), + // Otherwise, wrap the bare URL + final url = m.group(2)!; + return "$url"; + }, ), - if (event.lastEditRowId != null) - Text( - "(edited)", - style: theme.textTheme.labelSmall, + ) + : Linkify( + text: body, + maxLines: maxLines, + options: LinkifyOptions( + humanize: false, + ), + onOpen: (link) => ref + .watch(LaunchHelper.provider) + .launchUrl(Uri.parse(link.url)), + linkStyle: TextStyle( + color: Theme.of( + context, + ).colorScheme.primary, + ), + ), + if (event.content case ImageMessageContent( + :final url, + :final info, + )) + switch (url?.mxcToHttps( + ref.watch( + ClientStateController.provider.select( + (value) => value!.homeserverUrl!, + ), ), - if (RegExp( - r'''https?://[^\s"'<>]+''', - ).allMatches(body).firstOrNull?.group(0) - case final link?) - LinkPreview(link), - ], - ), + )) { + final url? => 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, + height: info?.height, + loadingBuilder: + ( + context, + child, + loadingProgress, + ) => switch (info?.blurHash) { + final blurHash? => BlurHash( + hash: blurHash, + ), + _ => Loading(), + }, + errorBuilder: + (context, error, stackTrace) => + Center( + child: Text( + "Image Failed to Load", + style: TextStyle( + color: Theme.of( + context, + ).colorScheme.error, + ), + ), + ), + ), + ), + ), + _ => Text( + "Nexus currently cannot handle encrypted media", + style: errorStyle, + ), + }, + if (event.lastEditRowId != null) + Text( + "(edited)", + style: theme.textTheme.labelSmall, + ), + if (RegExp( + r'''https?://[^\s"'<>]+''', + ).allMatches(body).firstOrNull?.group(0) + case final link?) + LinkPreview(link), + ], + ), _ => textOnly ? Text( @@ -206,27 +273,3 @@ class EventText extends ConsumerWidget { ); } } - -// ExpandableImage( -// url, -// child: ClipRRect( -// borderRadius: BorderRadius.all(Radius.circular(8)), -// child: Image( -// image: CachedNetworkImage( -// url, -// ref.watch(CrossCacheController.provider), -// headers: ref.headers, -// ), -// width: width, -// height: height, -// loadingBuilder: (context, child, loadingProgress) => -// blurHash == null ? Loading() : BlurHash(hash: blurHash!), -// errorBuilder: (context, error, stackTrace) => Center( -// child: Text( -// "Image Failed to Load", -// style: TextStyle(color: Theme.of(context).colorScheme.error), -// ), -// ), -// ), -// ), -// )