diff --git a/.gitmodules b/.gitmodules index 145276a..dc5e02e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "gomuks"] path = gomuks - url = https://github.com/gomuks/gomuks + url = https://github.com/henry-hiles/gomuks branch = main diff --git a/gomuks b/gomuks index 0f8fb26..1f11743 160000 --- a/gomuks +++ b/gomuks @@ -1 +1 @@ -Subproject commit 0f8fb261ab57977fa24a4f63b7aafbb9a4ff1b7b +Subproject commit 1f11743884a64765b1444b250377a09ebcaba93c diff --git a/lib/controllers/client.dart b/lib/controllers/client.dart index 97c3ed6..2cb1d66 100644 --- a/lib/controllers/client.dart +++ b/lib/controllers/client.dart @@ -17,7 +17,6 @@ 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"; @@ -216,6 +215,11 @@ 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}); @@ -251,9 +255,6 @@ 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 new file mode 100644 index 0000000..3e173ad --- /dev/null +++ b/lib/controllers/header.dart @@ -0,0 +1,20 @@ +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 1b17870..2a81a3c 100644 --- a/lib/controllers/url_preview.dart +++ b/lib/controllers/url_preview.dart @@ -1,28 +1,50 @@ -import "package:flutter/widgets.dart"; +import "dart:convert"; import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/client.dart"; +import "package:http/http.dart"; +import "package:nexus/controllers/client_state.dart"; +import "package:nexus/controllers/header.dart"; import "package:nexus/models/open_graph_data.dart"; class UrlPreviewController extends AsyncNotifier { - final Uri url; - UrlPreviewController(this.url); + final String link; + UrlPreviewController(this.link); @override Future build() async { - if (url.host == "matrix.to") return null; - try { - return await ref - .watch(ClientController.provider.notifier) - .getUrlPreview(url); - } catch (error, stackTrace) { - debugPrintStack(label: error.toString(), stackTrace: stackTrace); + final homeserver = ref.watch( + ClientStateController.provider.select((value) => value?.homeserverUrl), + ); + + 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; } static final provider = - AsyncNotifierProvider.family( - UrlPreviewController.new, - ); + AsyncNotifierProvider.family< + UrlPreviewController, + OpenGraphData?, + String + >(UrlPreviewController.new); } diff --git a/lib/helpers/extensions/get_headers.dart b/lib/helpers/extensions/get_headers.dart new file mode 100644 index 0000000..0fcec48 --- /dev/null +++ b/lib/helpers/extensions/get_headers.dart @@ -0,0 +1,7 @@ +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/main.dart b/lib/main.dart index 53cc57b..f50d22d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ 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"; @@ -143,6 +144,7 @@ class App extends StatelessWidget { IListConst([ SharedPrefsController.provider, ClientController.provider, + HeaderController.provider, ]), ), ) diff --git a/lib/widgets/players/audio.dart b/lib/widgets/players/audio.dart index 22a91f7..0c96579 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/controllers/client.dart"; +import "package:nexus/helpers/extensions/get_headers.dart"; import "package:nexus/models/info/audio.dart"; class AudioPlayer extends HookConsumerWidget { - final Uri uri; + final Uri url; final AudioInfo? info; - final bool encrypted; - const AudioPlayer(this.uri, this.info, {this.encrypted = false, super.key}); + const AudioPlayer(this.url, this.info, {super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -24,12 +24,11 @@ class AudioPlayer extends HookConsumerWidget { final duration = useState(Duration.zero); useEffect(() { - 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); + await player.open( + Media(url.toString(), httpHeaders: ref.headers), + play: false, + ); player.stream.playing.listen((value) { playing.value = value; @@ -68,18 +67,12 @@ class AudioPlayer extends HookConsumerWidget { padding: .only(left: 8, right: 16), child: Row( children: [ - 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, - ), + 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 cd4e569..8083860 100644 --- a/lib/widgets/players/video.dart +++ b/lib/widgets/players/video.dart @@ -2,17 +2,16 @@ 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 uri; - final bool encrypted; - const VideoPlayer(this.uri, this.info, {this.encrypted = false, super.key}); + final Uri url; + const VideoPlayer(this.url, this.info, {super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -22,13 +21,12 @@ class VideoPlayer extends HookConsumerWidget { final controller = useMemoized(() => VideoController(player)); useEffect(() { - 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); - }); + scheduleMicrotask( + () => player.open( + Media(url.toString(), httpHeaders: ref.headers), + play: false, + ), + ); return player.dispose; }, []); diff --git a/lib/widgets/renderers/message.dart b/lib/widgets/renderers/message.dart index 09ef00b..6d660af 100644 --- a/lib/widgets/renderers/message.dart +++ b/lib/widgets/renderers/message.dart @@ -226,25 +226,10 @@ class MessageRenderer extends ConsumerWidget { ConstrainedBox( constraints: .loose(.square(500)), child: switch (event.content) { - VideoMessageContent( - :final info, - :final file, - ) => - VideoPlayer( - url, - info, - encrypted: file != null, - ), - AudioMessageContent( - :final info, - :final file, - ) => - AudioPlayer( - url, - info, - - encrypted: file != null, - ), + VideoMessageContent(:final info) => + VideoPlayer(url, info), + AudioMessageContent(:final info) => + AudioPlayer(url, info), FileMessageContent( :final info, :final filename, @@ -273,8 +258,7 @@ class MessageRenderer extends ConsumerWidget { (element) => element is UrlElement, ) case final UrlElement link?) - if (Uri.tryParse(link.url) case final Uri url?) - UrlPreview(url), + UrlPreview(link.url), ReactionRow(event), ], diff --git a/lib/widgets/url_preview.dart b/lib/widgets/url_preview.dart index 3a20e19..5831300 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 Uri link; + final String link; const UrlPreview(this.link, {super.key}); @override @@ -18,7 +18,8 @@ class UrlPreview extends ConsumerWidget { data: (preview) => preview == null ? SizedBox.shrink() : InkWell( - onTap: () => ref.watch(LaunchHelper.provider).launchUrl(link), + onTap: () => + ref.watch(LaunchHelper.provider).launchUrl(.parse(link)), child: Card( margin: .symmetric(vertical: 4), color: Theme.of(