Add server-generated URL preview support
This commit is contained in:
parent
cadd5c1255
commit
f4624c2866
5 changed files with 128 additions and 49 deletions
|
|
@ -67,6 +67,7 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend.
|
||||||
- [x] Plain text
|
- [x] Plain text
|
||||||
- [x] Per message profiles
|
- [x] Per message profiles
|
||||||
- [x] HTML
|
- [x] HTML
|
||||||
|
- [x] URL Previews
|
||||||
- [x] Replies
|
- [x] Replies
|
||||||
- [x] Viewing
|
- [x] Viewing
|
||||||
- [ ] Jump to original message
|
- [ ] Jump to original message
|
||||||
|
|
@ -116,7 +117,7 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend.
|
||||||
- [ ] Devices
|
- [ ] Devices
|
||||||
- [ ] Viewing devices
|
- [ ] Viewing devices
|
||||||
- [ ] Verifying devices
|
- [ ] Verifying devices
|
||||||
- [ ] URL preview: Server / Client / None
|
- [ ] URL preview: Server / Sending Client (Beeper spec) / None
|
||||||
- [ ] Account changes
|
- [ ] Account changes
|
||||||
- [ ] Display name
|
- [ ] Display name
|
||||||
- [ ] Profile picture
|
- [ ] Profile picture
|
||||||
|
|
|
||||||
63
lib/controllers/url_preview_controller.dart
Normal file
63
lib/controllers/url_preview_controller.dart
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
import "dart:convert";
|
||||||
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:http/http.dart";
|
||||||
|
import "package:nexus/controllers/client_state_controller.dart";
|
||||||
|
import "package:nexus/controllers/header_controller.dart";
|
||||||
|
import "package:nexus/helpers/extensions/mxc_to_https.dart";
|
||||||
|
|
||||||
|
class UrlPreviewController extends AsyncNotifier<LinkPreviewData?> {
|
||||||
|
final TextMessage message;
|
||||||
|
UrlPreviewController(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<LinkPreviewData?> build() async {
|
||||||
|
final homeserver = ref.watch(ClientStateController.provider)?.homeserverUrl;
|
||||||
|
final link = RegExp(
|
||||||
|
r'''https?://[^\s"'<>]+''',
|
||||||
|
).allMatches(message.text).firstOrNull?.group(0);
|
||||||
|
|
||||||
|
if (homeserver != null && link != null) {
|
||||||
|
{
|
||||||
|
final response = await get(
|
||||||
|
Uri.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);
|
||||||
|
final mxc = decodedValue["og:image"];
|
||||||
|
final image = mxc == null
|
||||||
|
? null
|
||||||
|
: Uri.tryParse(mxc)?.mxcToHttps(homeserver);
|
||||||
|
|
||||||
|
return LinkPreviewData(
|
||||||
|
link: link,
|
||||||
|
title: decodedValue["og:title"],
|
||||||
|
description: decodedValue["og:description"],
|
||||||
|
image: image == null
|
||||||
|
? null
|
||||||
|
: ImagePreviewData(
|
||||||
|
url: image.toString(),
|
||||||
|
width:
|
||||||
|
(decodedValue["og:image:width"] as int?)?.toDouble() ??
|
||||||
|
0,
|
||||||
|
height:
|
||||||
|
(decodedValue["og:image:height"] as int?)?.toDouble() ??
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final provider = AsyncNotifierProvider.autoDispose
|
||||||
|
.family<UrlPreviewController, LinkPreviewData?, TextMessage>(
|
||||||
|
UrlPreviewController.new,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
|
import "package:cross_cache/cross_cache.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
import "package:flutter_link_previewer/flutter_link_previewer.dart";
|
import "package:flutter_link_previewer/flutter_link_previewer.dart";
|
||||||
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:nexus/controllers/cross_cache_controller.dart";
|
||||||
|
import "package:nexus/controllers/url_preview_controller.dart";
|
||||||
|
import "package:nexus/helpers/extensions/better_when.dart";
|
||||||
|
import "package:nexus/helpers/extensions/get_headers.dart";
|
||||||
import "package:nexus/widgets/chat_page/html/html.dart";
|
import "package:nexus/widgets/chat_page/html/html.dart";
|
||||||
import "package:nexus/widgets/chat_page/wrappers/message_wrapper.dart";
|
import "package:nexus/widgets/chat_page/wrappers/message_wrapper.dart";
|
||||||
import "package:nexus/widgets/chat_page/reply_widget.dart";
|
import "package:nexus/widgets/chat_page/reply_widget.dart";
|
||||||
|
|
||||||
class TextMessageWrapper extends StatelessWidget {
|
class TextMessageWrapper extends ConsumerWidget {
|
||||||
final Message message;
|
final Message message;
|
||||||
final String? content;
|
final String? content;
|
||||||
final MessageGroupStatus? groupStatus;
|
final MessageGroupStatus? groupStatus;
|
||||||
|
|
@ -27,7 +33,7 @@ class TextMessageWrapper extends StatelessWidget {
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final colorScheme = theme.colorScheme;
|
final colorScheme = theme.colorScheme;
|
||||||
final textMessage = message is TextMessage ? message as TextMessage : null;
|
final textMessage = message is TextMessage ? message as TextMessage : null;
|
||||||
|
|
@ -80,7 +86,22 @@ class TextMessageWrapper extends StatelessWidget {
|
||||||
if (textMessage?.editedAt != null)
|
if (textMessage?.editedAt != null)
|
||||||
Text("(edited)", style: theme.textTheme.labelSmall),
|
Text("(edited)", style: theme.textTheme.labelSmall),
|
||||||
if (textMessage != null)
|
if (textMessage != null)
|
||||||
LinkPreview(
|
ref
|
||||||
|
.watch(UrlPreviewController.provider(textMessage))
|
||||||
|
.betterWhen(
|
||||||
|
loading: SizedBox.shrink,
|
||||||
|
data: (preview) => preview == null
|
||||||
|
? SizedBox.shrink()
|
||||||
|
: LinkPreview(
|
||||||
|
imageBuilder: (url) => Image(
|
||||||
|
image: CachedNetworkImage(
|
||||||
|
url,
|
||||||
|
ref.watch(CrossCacheController.provider),
|
||||||
|
headers: ref.headers,
|
||||||
|
),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
errorBuilder: (_, _, _) => SizedBox.shrink(),
|
||||||
|
),
|
||||||
text: textMessage.text,
|
text: textMessage.text,
|
||||||
backgroundColor: isSentByMe
|
backgroundColor: isSentByMe
|
||||||
? colorScheme.inversePrimary
|
? colorScheme.inversePrimary
|
||||||
|
|
@ -90,15 +111,8 @@ class TextMessageWrapper extends StatelessWidget {
|
||||||
vertical: 8,
|
vertical: 8,
|
||||||
horizontal: 16,
|
horizontal: 16,
|
||||||
),
|
),
|
||||||
linkPreviewData: message.metadata?["linkPreviewData"],
|
linkPreviewData: preview,
|
||||||
onLinkPreviewDataFetched: (linkPreviewData) => updateMessage(
|
onLinkPreviewDataFetched: (_) => null,
|
||||||
message,
|
|
||||||
message.copyWith(
|
|
||||||
metadata: {
|
|
||||||
...(message.metadata ?? {}),
|
|
||||||
"linkPreviewData": linkPreviewData,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (extra != null) extra!,
|
if (extra != null) extra!,
|
||||||
|
|
|
||||||
|
|
@ -655,7 +655,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.15.6"
|
version: "0.15.6"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ dependencies:
|
||||||
code_assets: ^1.0.0
|
code_assets: ^1.0.0
|
||||||
ffigen: ^20.1.1
|
ffigen: ^20.1.1
|
||||||
timeago: ^3.7.1
|
timeago: ^3.7.1
|
||||||
|
http: ^1.6.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.4.11
|
build_runner: ^2.4.11
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue