From 16fdf0c4202ea16c30fee06a842a55e604cf3aff Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Mon, 27 Jul 2026 15:46:08 -0400 Subject: [PATCH 1/3] download audio and video using gomuks --- .gitmodules | 2 +- gomuks | 2 +- lib/helpers/extensions/get_headers.dart | 7 ------ lib/widgets/players/audio.dart | 33 +++++++++++++++---------- lib/widgets/players/video.dart | 20 ++++++++------- lib/widgets/renderers/message.dart | 23 ++++++++++++++--- 6 files changed, 52 insertions(+), 35 deletions(-) delete mode 100644 lib/helpers/extensions/get_headers.dart diff --git a/.gitmodules b/.gitmodules index dc5e02e..145276a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "gomuks"] path = gomuks - url = https://github.com/henry-hiles/gomuks + url = https://github.com/gomuks/gomuks branch = main diff --git a/gomuks b/gomuks index 1f11743..0f8fb26 160000 --- a/gomuks +++ b/gomuks @@ -1 +1 @@ -Subproject commit 1f11743884a64765b1444b250377a09ebcaba93c +Subproject commit 0f8fb261ab57977fa24a4f63b7aafbb9a4ff1b7b diff --git a/lib/helpers/extensions/get_headers.dart b/lib/helpers/extensions/get_headers.dart deleted file mode 100644 index 0fcec48..0000000 --- a/lib/helpers/extensions/get_headers.dart +++ /dev/null @@ -1,7 +0,0 @@ -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/header.dart"; - -extension GetHeaders on WidgetRef { - Map get headers => - watch(HeaderController.provider).requireValue; -} diff --git a/lib/widgets/players/audio.dart b/lib/widgets/players/audio.dart index 0c96579..22a91f7 100644 --- a/lib/widgets/players/audio.dart +++ b/lib/widgets/players/audio.dart @@ -1,17 +1,17 @@ import "dart:async"; - import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:media_kit/media_kit.dart"; -import "package:nexus/helpers/extensions/get_headers.dart"; +import "package:nexus/controllers/client.dart"; import "package:nexus/models/info/audio.dart"; class AudioPlayer extends HookConsumerWidget { - final Uri url; + final Uri uri; final AudioInfo? info; + final bool encrypted; - const AudioPlayer(this.url, this.info, {super.key}); + const AudioPlayer(this.uri, this.info, {this.encrypted = false, super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -24,11 +24,12 @@ class AudioPlayer extends HookConsumerWidget { final duration = useState(Duration.zero); useEffect(() { + player.platform?.state = player.platform!.state.copyWith(buffering: true); scheduleMicrotask(() async { - await player.open( - Media(url.toString(), httpHeaders: ref.headers), - play: false, - ); + final video = await ref + .watch(ClientController.provider.notifier) + .downloadMedia(.new(mxc: uri, encrypted: encrypted)); + await player.open(Media(video.path), play: false); player.stream.playing.listen((value) { playing.value = value; @@ -67,12 +68,18 @@ class AudioPlayer extends HookConsumerWidget { padding: .only(left: 8, right: 16), child: Row( children: [ - IconButton( - onPressed: player.playOrPause, - icon: Icon( - playing.value ? Icons.pause_circle : Icons.play_circle, + if (player.state.buffering) + SizedBox.square( + dimension: 24, + child: CircularProgressIndicator(padding: .all(4)), + ) + else + IconButton( + onPressed: player.playOrPause, + icon: Icon( + playing.value ? Icons.pause_circle : Icons.play_circle, + ), ), - ), SizedBox(width: 8), Text( format(position.value), diff --git a/lib/widgets/players/video.dart b/lib/widgets/players/video.dart index 8083860..cd4e569 100644 --- a/lib/widgets/players/video.dart +++ b/lib/widgets/players/video.dart @@ -2,16 +2,17 @@ import "dart:async"; import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:nexus/controllers/client.dart"; import "package:nexus/models/info/video.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:media_kit/media_kit.dart"; import "package:media_kit_video/media_kit_video.dart"; -import "package:nexus/helpers/extensions/get_headers.dart"; class VideoPlayer extends HookConsumerWidget { final VideoInfo? info; - final Uri url; - const VideoPlayer(this.url, this.info, {super.key}); + final Uri uri; + final bool encrypted; + const VideoPlayer(this.uri, this.info, {this.encrypted = false, super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -21,12 +22,13 @@ class VideoPlayer extends HookConsumerWidget { final controller = useMemoized(() => VideoController(player)); useEffect(() { - scheduleMicrotask( - () => player.open( - Media(url.toString(), httpHeaders: ref.headers), - play: false, - ), - ); + player.platform?.state = player.platform!.state.copyWith(buffering: true); + scheduleMicrotask(() async { + final video = await ref + .watch(ClientController.provider.notifier) + .downloadMedia(.new(mxc: uri, encrypted: encrypted)); + await player.open(Media(video.path), play: false); + }); return player.dispose; }, []); diff --git a/lib/widgets/renderers/message.dart b/lib/widgets/renderers/message.dart index 6d660af..0e0f6fa 100644 --- a/lib/widgets/renderers/message.dart +++ b/lib/widgets/renderers/message.dart @@ -226,10 +226,25 @@ class MessageRenderer extends ConsumerWidget { ConstrainedBox( constraints: .loose(.square(500)), child: switch (event.content) { - VideoMessageContent(:final info) => - VideoPlayer(url, info), - AudioMessageContent(:final info) => - AudioPlayer(url, info), + VideoMessageContent( + :final info, + :final file, + ) => + VideoPlayer( + url, + info, + encrypted: file != null, + ), + AudioMessageContent( + :final info, + :final file, + ) => + AudioPlayer( + url, + info, + + encrypted: file != null, + ), FileMessageContent( :final info, :final filename, From eca9d3b411dc47c94254b4c60cbbf2d70146a1da Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Mon, 27 Jul 2026 16:01:40 -0400 Subject: [PATCH 2/3] use gomuks for URL previews --- lib/controllers/client.dart | 9 +++--- lib/controllers/header.dart | 20 ------------- lib/controllers/url_preview.dart | 48 +++++++----------------------- lib/main.dart | 2 -- lib/widgets/renderers/message.dart | 3 +- lib/widgets/url_preview.dart | 5 ++-- 6 files changed, 18 insertions(+), 69 deletions(-) delete mode 100644 lib/controllers/header.dart diff --git a/lib/controllers/client.dart b/lib/controllers/client.dart index 2cb1d66..97c3ed6 100644 --- a/lib/controllers/client.dart +++ b/lib/controllers/client.dart @@ -17,6 +17,7 @@ import "package:nexus/main.dart"; import "package:nexus/models/content/message.dart"; import "package:nexus/models/event.dart"; import "package:nexus/models/oauth_auth_code_response.dart"; +import "package:nexus/models/open_graph_data.dart"; import "package:nexus/models/paginate.dart"; import "package:nexus/models/requests/download_media.dart"; import "package:nexus/models/requests/get_event.dart"; @@ -215,11 +216,6 @@ class ClientController extends AsyncNotifier { return response["room_id"]; } - Future getAccessToken() async { - final response = await _sendCommand("get_account_info", {}); - return response?["access_token"]; - } - Future leaveRoom(Room room) async { if (room.metadata == null) return; await _sendCommand("leave_room", {"room_id": room.metadata!.id}); @@ -255,6 +251,9 @@ class ClientController extends AsyncNotifier { return json == null ? null : .fromJson(json); } + Future getUrlPreview(Uri url) async => + .fromJson(await _sendCommand("get_url_preview", {"url": url.toString()})); + Future paginate(PaginateRequest request) async => .fromJson(await _sendCommand("paginate", request.toJson())); diff --git a/lib/controllers/header.dart b/lib/controllers/header.dart deleted file mode 100644 index 3e173ad..0000000 --- a/lib/controllers/header.dart +++ /dev/null @@ -1,20 +0,0 @@ -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/client.dart"; -import "package:nexus/controllers/client_state.dart"; - -class HeaderController extends AsyncNotifier> { - @override - Future> build() async { - if (ref.watch(ClientStateController.provider)?.isLoggedIn != true) { - return {}; - } - final client = ref.watch(ClientController.provider.notifier); - final accessToken = await client.getAccessToken(); - return {"authorization": "Bearer $accessToken"}; - } - - static final provider = - AsyncNotifierProvider>( - HeaderController.new, - ); -} diff --git a/lib/controllers/url_preview.dart b/lib/controllers/url_preview.dart index 2a81a3c..4e7d3a0 100644 --- a/lib/controllers/url_preview.dart +++ b/lib/controllers/url_preview.dart @@ -1,50 +1,22 @@ -import "dart:convert"; import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:http/http.dart"; -import "package:nexus/controllers/client_state.dart"; -import "package:nexus/controllers/header.dart"; +import "package:nexus/controllers/client.dart"; import "package:nexus/models/open_graph_data.dart"; class UrlPreviewController extends AsyncNotifier { - final String link; - UrlPreviewController(this.link); + final Uri url; + UrlPreviewController(this.url); @override Future build() async { - try { - final homeserver = ref.watch( - ClientStateController.provider.select((value) => value?.homeserverUrl), - ); + if (url.host == "matrix.to") return null; - if (homeserver != null && !link.contains("matrix.to")) { - { - final response = await get( - .parse(homeserver) - .resolve("/_matrix/client/v1/media/preview_url") - .replace(queryParameters: {"url": link}), - headers: await ref.watch(HeaderController.provider.future), - ); - - if (response.statusCode == 200) { - final decodedValue = json.decode(response.body); - if (decodedValue is! Map) return null; - - return .fromJson( - decodedValue, - ).copyWith(imageUrl: decodedValue["og:image"]); - } - } - } - } catch (_) { - return null; - } - return null; + return await ref + .watch(ClientController.provider.notifier) + .getUrlPreview(url); } static final provider = - AsyncNotifierProvider.family< - UrlPreviewController, - OpenGraphData?, - String - >(UrlPreviewController.new); + AsyncNotifierProvider.family( + UrlPreviewController.new, + ); } diff --git a/lib/main.dart b/lib/main.dart index f50d22d..53cc57b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,7 +6,6 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:media_kit/media_kit.dart"; import "package:nexus/controllers/client.dart"; import "package:nexus/controllers/client_state.dart"; -import "package:nexus/controllers/header.dart"; import "package:nexus/controllers/multi_provider.dart"; import "package:nexus/controllers/settings.dart"; import "package:nexus/controllers/shared_prefs.dart"; @@ -144,7 +143,6 @@ class App extends StatelessWidget { IListConst([ SharedPrefsController.provider, ClientController.provider, - HeaderController.provider, ]), ), ) diff --git a/lib/widgets/renderers/message.dart b/lib/widgets/renderers/message.dart index 0e0f6fa..09ef00b 100644 --- a/lib/widgets/renderers/message.dart +++ b/lib/widgets/renderers/message.dart @@ -273,7 +273,8 @@ class MessageRenderer extends ConsumerWidget { (element) => element is UrlElement, ) case final UrlElement link?) - UrlPreview(link.url), + if (Uri.tryParse(link.url) case final Uri url?) + UrlPreview(url), ReactionRow(event), ], diff --git a/lib/widgets/url_preview.dart b/lib/widgets/url_preview.dart index 5831300..3a20e19 100644 --- a/lib/widgets/url_preview.dart +++ b/lib/widgets/url_preview.dart @@ -6,7 +6,7 @@ import "package:nexus/helpers/launch_helper.dart"; import "package:nexus/helpers/mxc_image.dart"; class UrlPreview extends ConsumerWidget { - final String link; + final Uri link; const UrlPreview(this.link, {super.key}); @override @@ -18,8 +18,7 @@ class UrlPreview extends ConsumerWidget { data: (preview) => preview == null ? SizedBox.shrink() : InkWell( - onTap: () => - ref.watch(LaunchHelper.provider).launchUrl(.parse(link)), + onTap: () => ref.watch(LaunchHelper.provider).launchUrl(link), child: Card( margin: .symmetric(vertical: 4), color: Theme.of( From 5f13d3fbf5cb76872aa95b3d2918addf087a8168 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Mon, 27 Jul 2026 22:02:20 -0400 Subject: [PATCH 3/3] don't show errors when url preview fails --- lib/controllers/url_preview.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/controllers/url_preview.dart b/lib/controllers/url_preview.dart index 4e7d3a0..1b17870 100644 --- a/lib/controllers/url_preview.dart +++ b/lib/controllers/url_preview.dart @@ -1,3 +1,4 @@ +import "package:flutter/widgets.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client.dart"; import "package:nexus/models/open_graph_data.dart"; @@ -10,9 +11,14 @@ class UrlPreviewController extends AsyncNotifier { Future build() async { if (url.host == "matrix.to") return null; - return await ref - .watch(ClientController.provider.notifier) - .getUrlPreview(url); + try { + return await ref + .watch(ClientController.provider.notifier) + .getUrlPreview(url); + } catch (error, stackTrace) { + debugPrintStack(label: error.toString(), stackTrace: stackTrace); + return null; + } } static final provider =