use gomuks for downloading media

This commit is contained in:
Henry Hiles 2026-07-26 23:01:36 -04:00
commit fa451621de
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
20 changed files with 159 additions and 241 deletions

View file

@ -1,11 +1,7 @@
import "package:color_hash/color_hash.dart";
import "package:cross_cache/cross_cache.dart";
import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/client_state.dart";
import "package:nexus/controllers/cross_cache.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/helpers/extensions/mxc_to_https.dart";
import "package:nexus/helpers/mxc_image.dart";
class AvatarOrHash extends ConsumerWidget {
final Uri? avatar;
@ -27,15 +23,6 @@ class AvatarOrHash extends ConsumerWidget {
child: Center(child: Icon(Icons.person, size: height / 2)),
);
final parsedAvatar = avatar?.mxcToHttps(
ref.watch(
ClientStateController.provider.select(
(value) => value?.homeserverUrl,
),
) ??
"",
);
return SizedBox(
width: height,
height: height,
@ -45,14 +32,10 @@ class AvatarOrHash extends ConsumerWidget {
child: SizedBox(
width: height,
height: height,
child: parsedAvatar == null
child: avatar == null
? fallback ?? box
: Image(
image: CachedNetworkImage(
parsedAvatar.toString(),
ref.watch(CrossCacheController.provider),
headers: ref.headers,
),
image: MxcImage(ref, .new(mxc: avatar!, isAvatar: true)),
fit: .cover,
loadingBuilder: (_, child, loadingProgress) =>
loadingProgress == null ? child : fallback ?? box,

View file

@ -1,19 +1,18 @@
import "package:cross_cache/cross_cache.dart";
import "package:flutter/material.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:m3e_buttons/m3e_buttons.dart";
import "package:nexus/controllers/cross_cache.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/helpers/mxc_image.dart";
import "package:nexus/models/requests/download_media.dart";
import "package:nexus/widgets/error_dialog.dart";
class ExpandableImage extends ConsumerWidget {
final Widget child;
final String? source;
const ExpandableImage(this.source, {required this.child, super.key});
final DownloadMediaRequest? request;
const ExpandableImage(this.request, {required this.child, super.key});
@override
Widget build(BuildContext context, WidgetRef ref) => InkWell(
onTap: source == null
onTap: request == null
? null
: () => showDialog(
context: context,
@ -27,14 +26,10 @@ class ExpandableImage extends ConsumerWidget {
maxScale: 10,
child: Image(
errorBuilder: (_, error, stackTrace) => ErrorDialog(
"Loading failed for $source\nError: $error",
"Loading failed for ${request?.mxc}\nError: $error",
stackTrace,
),
image: CachedNetworkImage(
source!,
ref.watch(CrossCacheController.provider),
headers: ref.headers,
),
image: MxcImage(ref, request!),
),
),
),

View file

@ -1,14 +1,10 @@
import "package:cross_cache/cross_cache.dart";
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart";
import "package:nexus/controllers/client_state.dart";
import "package:nexus/controllers/cross_cache.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/helpers/extensions/link_to_mention.dart";
import "package:nexus/helpers/extensions/mxc_to_https.dart";
import "package:nexus/helpers/launch_helper.dart";
import "package:nexus/helpers/mxc_image.dart";
import "package:nexus/widgets/expandable_image.dart";
import "package:nexus/widgets/html/mention_chip.dart";
import "package:nexus/widgets/html/spoiler_text.dart";
@ -40,16 +36,7 @@ class Html extends ConsumerWidget {
(element.attributes.keys.contains("data-mx-emoticon") ? 32 : null) ??
300;
final width = int.tryParse(element.attributes["width"] ?? "");
final src = Uri.tryParse(element.attributes["src"] ?? "")
?.mxcToHttps(
ref.watch(
ClientStateController.provider.select(
(value) => value?.homeserverUrl,
),
) ??
"",
)
.toString();
final src = Uri.tryParse(element.attributes["src"] ?? "");
return switch (element.localName) {
"code" =>
@ -77,13 +64,9 @@ class Html extends ConsumerWidget {
: InlineCustomWidget(
alignment: PlaceholderAlignment.middle,
child: ExpandableImage(
src,
.new(mxc: src),
child: Image(
image: CachedNetworkImage(
src,
ref.watch(CrossCacheController.provider),
headers: ref.headers,
),
image: MxcImage(ref, .new(mxc: src)),
errorBuilder: (_, error, _) => Text(
"Image Failed to Load",
style: .new(color: Theme.of(context).colorScheme.error),

View file

@ -1,57 +1,57 @@
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.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/helpers/mxc_image.dart";
import "package:nexus/models/info/image.dart" as i;
import "package:nexus/models/requests/download_media.dart";
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});
final bool encrypted;
const MessageImage(this.url, {this.info, required this.encrypted, super.key});
@override
Widget build(BuildContext context, WidgetRef ref) => ExpandableImage(
url.toString(),
child: ClipRRect(
borderRadius: .all(.circular(8)),
child: AspectRatio(
aspectRatio: (info?.width ?? 1) / (info?.height ?? 1),
child: Image(
image: CachedNetworkImage(
url.toString(),
ref.watch(CrossCacheController.provider),
headers: ref.headers,
),
width: info?.width,
fit: BoxFit.fitWidth,
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),
)
: SizedBox(
width: info!.width,
child: BlurHash(hash: blurHash),
),
_ => Loading(),
},
errorBuilder: (context, error, stackTrace) => Center(
child: Text(
"Image Failed to Load",
style: .new(color: Theme.of(context).colorScheme.error),
Widget build(BuildContext context, WidgetRef ref) {
final request = DownloadMediaRequest(mxc: url, encrypted: encrypted);
return ExpandableImage(
request,
child: ClipRRect(
borderRadius: .all(.circular(8)),
child: AspectRatio(
aspectRatio: (info?.width ?? 1) / (info?.height ?? 1),
child: Image(
image: MxcImage(ref, request),
width: info?.width,
fit: BoxFit.fitWidth,
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),
)
: SizedBox(
width: info!.width,
child: BlurHash(hash: blurHash),
),
_ => Loading(),
},
errorBuilder: (context, error, stackTrace) => Center(
child: Text(
"Image Failed to Load",
style: .new(color: Theme.of(context).colorScheme.error),
),
),
),
),
),
),
);
);
}
}

View file

@ -1,13 +1,10 @@
import "package:cross_cache/cross_cache.dart";
import "package:flutter/material.dart";
import "package:flutter_hooks/flutter_hooks.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/client_state.dart";
import "package:nexus/controllers/cross_cache.dart";
import "package:nexus/controllers/reactions.dart";
import "package:nexus/controllers/room_chat.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/helpers/extensions/mxc_to_https.dart";
import "package:nexus/helpers/mxc_image.dart";
import "package:nexus/models/event.dart";
import "package:nexus/widgets/error_dialog.dart";
import "package:nexus/main.dart";
@ -55,16 +52,9 @@ class ReactionRow extends ConsumerWidget {
child: reaction.startsWith("mxc://")
? Image(
height: 20,
image: CachedNetworkImage(
headers: ref.headers,
Uri.parse(reaction)
.mxcToHttps(
clientState!.homeserverUrl!,
)
.toString(),
ref.watch(
CrossCacheController.provider,
),
image: MxcImage(
ref,
.new(mxc: Uri.parse(reaction)),
),
)
: Text(reaction, overflow: .ellipsis),

View file

@ -4,7 +4,6 @@ import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:linkify/linkify.dart";
import "package:nexus/controllers/client_state.dart";
import "package:nexus/controllers/event.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";
@ -146,14 +145,9 @@ class MessageRenderer extends ConsumerWidget {
: ConstrainedBox(
constraints: .loose(.square(200)),
child: MessageImage(
url.mxcToHttps(
ref.watch(
ClientStateController.provider.select(
(value) => value!.homeserverUrl!,
),
),
),
url,
info: info,
encrypted: false,
),
),
// TODO: Handle locations
@ -229,35 +223,30 @@ class MessageRenderer extends ConsumerWidget {
FileMessageContent(:final url) ||
VideoMessageContent(:final url) ||
AudioMessageContent(:final url))
switch (url?.mxcToHttps(
ref.watch(
ClientStateController.provider.select(
(value) => value!.homeserverUrl!,
),
),
)) {
final url? => ConstrainedBox(
constraints: .loose(.square(500)),
child: switch (event.content) {
VideoMessageContent(:final info) =>
VideoPlayer(url, info),
AudioMessageContent(:final info) =>
AudioPlayer(url, info),
FileMessageContent(
:final info,
:final filename,
) =>
FileCard(url, info, filename: filename),
ImageMessageContent(:final info) =>
MessageImage(url, info: info),
_ => SizedBox.shrink(),
},
),
_ => Text(
"Nexus currently cannot handle encrypted media",
style: errorStyle,
),
},
ConstrainedBox(
constraints: .loose(.square(500)),
child: switch (event.content) {
VideoMessageContent(:final info) =>
VideoPlayer(url, info),
AudioMessageContent(:final info) =>
AudioPlayer(url, info),
FileMessageContent(
:final info,
:final filename,
) =>
FileCard(url, info, filename: filename),
ImageMessageContent(
:final info,
:final file,
) =>
MessageImage(
url,
info: info,
encrypted: file != null,
),
_ => SizedBox.shrink(),
},
),
if (event.lastEditRowId != 0)
Text(

View file

@ -1,11 +1,9 @@
import "package:flutter/material.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/rooms.dart";
import "package:nexus/helpers/extensions/mxc_to_https.dart";
import "package:nexus/widgets/appbar.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
import "package:nexus/widgets/expandable_image.dart";
import "package:nexus/controllers/client_state.dart";
import "package:nexus/widgets/linkified_text.dart";
import "package:nexus/widgets/room_menu.dart";
@ -53,15 +51,9 @@ class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget {
children: [
if (room.metadata?.avatar != null)
ExpandableImage(
room.metadata!.avatar!
.mxcToHttps(
ref.watch(
ClientStateController.provider.select(
(value) => value!.homeserverUrl!,
),
),
)
.toString(),
room.metadata?.avatar == null
? null
: .new(mxc: room.metadata!.avatar!),
child: AvatarOrHash(
room.metadata?.avatar,
room.metadata?.name ?? "Unnamed Room",

View file

@ -1,11 +1,9 @@
import "package:cross_cache/cross_cache.dart";
import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/cross_cache.dart";
import "package:nexus/controllers/url_preview.dart";
import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/helpers/launch_helper.dart";
import "package:nexus/helpers/mxc_image.dart";
class UrlPreview extends ConsumerWidget {
final String link;
@ -48,10 +46,9 @@ class UrlPreview extends ConsumerWidget {
child: Image(
errorBuilder: (_, _, _) => SizedBox.shrink(),
width: preview.width,
image: CachedNetworkImage(
preview.imageUrl.toString(),
ref.watch(CrossCacheController.provider),
headers: ref.headers,
image: MxcImage(
ref,
.new(mxc: preview.imageUrl!),
),
fit: .fitWidth,
),

View file

@ -10,7 +10,6 @@ import "package:nexus/controllers/power_level.dart";
import "package:nexus/controllers/profile.dart";
import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/helpers/extensions/get_localpart.dart";
import "package:nexus/helpers/extensions/mxc_to_https.dart";
import "package:nexus/models/content/membership.dart";
import "package:nexus/models/membership_action.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
@ -97,15 +96,7 @@ class UserBottomSheet extends ConsumerWidget {
SizedBox(height: 18),
ExpandableImage(
member.avatarUrl
?.mxcToHttps(
ref.watch(
ClientStateController.provider.select(
(value) => value!.homeserverUrl!,
),
),
)
.toString(),
member.avatarUrl == null ? null : .new(mxc: member.avatarUrl!),
child: AvatarOrHash(
member.avatarUrl,
member.displayName ?? userId.localpart,