Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6d81fe2
Add empty package
nicolasbraun May 23, 2025
47680f9
Add old lib utils
nicolasbraun May 23, 2025
b9bd63f
Bump dependencies, rename
nicolasbraun May 23, 2025
e5d61a8
Add new preview widget
nicolasbraun May 23, 2025
7911ff3
Adapt base text widgets to the new intended layout
nicolasbraun May 23, 2025
64c10da
Add example
nicolasbraun May 23, 2025
2d2ae79
Add more constraints options
nicolasbraun May 24, 2025
415330c
Better image image size management
nicolasbraun May 24, 2025
0810e9a
Ensure bubble sizes account for the timeAndStatus widget size
nicolasbraun May 24, 2025
2293534
Fix forgotten extra linkPreview widget on top
nicolasbraun May 24, 2025
12e6174
Update callback to include if sentByMe and update example
nicolasbraun May 24, 2025
8610f9a
Fix crashes
nicolasbraun Jun 6, 2025
9aae980
Simplify onTap
nicolasbraun Jun 6, 2025
4169542
Lowercase text, iOS has a tendency to put HTTPS which does not work
nicolasbraun Jun 7, 2025
60a1fb6
Revert previous commit
nicolasbraun Jun 10, 2025
6380942
Fix: return the proxied url for image content
nicolasbraun Jun 10, 2025
c98b3d3
Fix crash when content is not utf8
nicolasbraun Jun 10, 2025
cb70fd2
Add try-catch to always return something if possible even when images…
nicolasbraun Jun 10, 2025
1515a8b
Better handle redirects for relative images paths
nicolasbraun Jun 10, 2025
14729a1
Remove commented code
nicolasbraun Jun 11, 2025
e151831
Fix preview not display if no TimeAndStatus
nicolasbraun Jun 11, 2025
0364318
rebase and fix dependencies
demchenkoalex Jul 2, 2025
126d286
Implement in FlyerChatTextMessage
nicolasbraun Jun 11, 2025
6338500
Implement in FlyerChatImageMessage
nicolasbraun Jul 4, 2025
3d2a3c5
Demo
nicolasbraun Jul 4, 2025
9891f60
Add for SimpleTextMessage
nicolasbraun Jul 4, 2025
c9bce85
Add support for fileMessages
nicolasbraun Jul 4, 2025
e4f9579
Allow text under FileMessages
nicolasbraun Jul 4, 2025
1c33a55
Add support in ImageMessages
nicolasbraun Jul 4, 2025
69541c9
Add Center to the AspectRatio to fix when text is wider than image
nicolasbraun Jul 4, 2025
4c80d43
Fix Image rendering
nicolasbraun Jul 4, 2025
978dcdd
ImageMessage: Wrap in Row only if message has text
nicolasbraun Jul 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions examples/flyer_chat/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ PODS:
- SDWebImage/Core (= 5.21.0)
- SDWebImage/Core (5.21.0)
- SwiftyGif (5.4.5)
- url_launcher_ios (0.0.1):
- Flutter

DEPENDENCIES:
- file_picker (from `.symlinks/plugins/file_picker/ios`)
Expand All @@ -55,6 +57,7 @@ DEPENDENCIES:
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)

SPEC REPOS:
trunk:
Expand All @@ -76,6 +79,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/isar_flutter_libs/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"

SPEC CHECKSUMS:
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
Expand All @@ -88,6 +93,7 @@ SPEC CHECKSUMS:
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d

PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5

Expand Down
35 changes: 25 additions & 10 deletions examples/flyer_chat/lib/create_message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ Future<Message> createMessage(
const uuid = Uuid();
Message message;

if (Random().nextBool() || textOnly == true || text != null) {
final randomType = Random().nextInt(3) + 1; // 1, 2, or 3

if (randomType == 1 || textOnly == true || text != null) {
message = TextMessage(
id: uuid.v4(),
authorId: authorId,
Expand Down Expand Up @@ -50,15 +52,28 @@ Future<Message> createMessage(
),
);

message = ImageMessage(
id: uuid.v4(),
authorId: authorId,
createdAt: DateTime.now().toUtc(),
sentAt: localOnly == true ? DateTime.now().toUtc() : null,
source: response.data['img'],
thumbhash: response.data['thumbhash'],
blurhash: response.data['blurhash'],
);
if (randomType == 2) {
message = ImageMessage(
id: uuid.v4(),
authorId: authorId,
text: 'This is an image message',
createdAt: DateTime.now().toUtc(),
sentAt: localOnly == true ? DateTime.now().toUtc() : null,
source: response.data['img'],
thumbhash: response.data['thumbhash'],
blurhash: response.data['blurhash'],
);
} else {
message = FileMessage(
id: uuid.v4(),
name: 'image.png',
text: 'This is a file message',
authorId: authorId,
createdAt: DateTime.now().toUtc(),
sentAt: localOnly == true ? DateTime.now().toUtc() : null,
source: response.data['img'],
);
}
}

// return ImageMessage(
Expand Down
215 changes: 215 additions & 0 deletions examples/flyer_chat/lib/link_preview.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_chat_core/flutter_chat_core.dart';
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
import 'package:flyer_chat_link_preview/flyer_chat_link_preview.dart';
import 'package:uuid/uuid.dart';

class LinkPreviewExample extends StatefulWidget {
final Dio dio;

const LinkPreviewExample({super.key, required this.dio});

@override
LinkPreviewExampleState createState() => LinkPreviewExampleState();
}

class LinkPreviewExampleState extends State<LinkPreviewExample> {
final _chatController = InMemoryChatController();

final _currentUser = const User(
id: 'me',
imageSource: 'https://picsum.photos/id/65/200/200',
);

final metadataLinkPreviewFetchedKey = 'linkPreviewDataFetched';

@override
void dispose() {
_chatController.dispose();
super.dispose();
}

@override
void initState() {
super.initState();
_chatController.setMessages([
Message.text(
id: 'not-fetched',
authorId: _currentUser.id,
text: 'https://flyer.chat/ Not fetchet yet',
createdAt: DateTime.now().toUtc().subtract(const Duration(hours: 1)),
),
Message.text(
id: 'wont-fetch',
authorId: _currentUser.id,
text: 'https://flyer.chat/ Will not fetch',
createdAt: DateTime.now().toUtc().subtract(const Duration(hours: 2)),
metadata: {metadataLinkPreviewFetchedKey: true},
),
Message.text(
id: 'complete',
authorId: 'someone',
text: 'https://flyer.chat/ Already fetched',
createdAt: DateTime.now().toUtc().subtract(const Duration(hours: 3)),
linkPreviewData: LinkPreviewData(
link: 'https://flyer.chat/',
description:
'Open-source chat SDK for Flutter and React Native. Build fast, real-time apps and AI agents with a high-performance, customizable, cross-platform UI.',
image: ImagePreviewData(
url: 'https://flyer.chat/og-image.png',
width: 1200.0,
height: 630.0,
),
title: 'Flyer Chat | Ship faster with a go-to chat SDK',
),
),

Message.text(
id: 'display-only-image',
authorId: _currentUser.id,
text: 'https://flyer.chat/ Display only the image',
createdAt: DateTime.now().toUtc().subtract(const Duration(hours: 4)),
linkPreviewData: LinkPreviewData(
link: 'https://flyer.chat/',
description:
'Open-source chat SDK for Flutter and React Native. Build fast, real-time apps and AI agents with a high-performance, customizable, cross-platform UI.',
image: ImagePreviewData(
url: 'https://flyer.chat/og-image.png',
width: 1200.0,
height: 630.0,
),
title: 'Flyer Chat | Ship faster with a go-to chat SDK',
),
),
Message.text(
id: 'fake-square',
authorId: _currentUser.id,
text: 'https://flyer.chat/ Fake square image',
createdAt: DateTime.now().toUtc().subtract(const Duration(hours: 5)),
linkPreviewData: LinkPreviewData(
link: 'https://flyer.chat/',
description:
'Open-source chat SDK for Flutter and React Native. Build fast, real-time apps and AI agents with a high-performance, customizable, cross-platform UI.',
image: ImagePreviewData(
url: 'https://flyer.chat/og-image.png',
width: 1200.0,
height: 1200.0,
),
title: 'Flyer Chat | Ship faster with a go-to chat SDK',
),
),
Message.text(
id: 'image-content',
authorId: _currentUser.id,
text: 'https://flyer.chat/og-image.png This is an image link',
createdAt: DateTime.now().toUtc().subtract(const Duration(hours: 6)),
),
Message.text(
id: 'short one',
authorId: _currentUser.id,
text: 'Fake short one',
createdAt: DateTime.now().toUtc().subtract(const Duration(hours: 5)),
linkPreviewData: LinkPreviewData(
link: 'https://flyer.chat/',
description: 'Open-source chat SDK',
image: ImagePreviewData(
url: 'https://flyer.chat/og-image.png',
width: 1200.0,
height: 1200.0,
),
title: 'Flyer Chat',
),
),
]);
}

void _addItem(String? text) async {
if (text == null || text.isEmpty) {
return;
}

final message = Message.text(
id: Uuid().v4(),
authorId: _currentUser.id,
text: text,
createdAt: DateTime.now().toUtc().subtract(const Duration(seconds: 1)),
);

if (mounted) {
await _chatController.insertMessage(message);
}
}

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final chatTheme =
theme.brightness == Brightness.dark
? ChatTheme.dark()
: ChatTheme.light();

return Scaffold(
appBar: AppBar(title: const Text('LinkPreview')),
body: Chat(
backgroundColor: Colors.transparent,
builders: Builders(
linkPreviewBuilder: (context, message, isSentByMe) {
final isLinkPreviewDataFetched =
message.metadata?.containsKey(metadataLinkPreviewFetchedKey) ??
false;

// It's up to you to (optionally) implement the logic to avoid every
// client to eventually refetch the preview data
//
// For example, you can use a metadata to indicate if the preview
// was already fetched (and null).
//
// You could also store a data and implement some retry/refresh logic.
//

if (message.linkPreviewData != null || !isLinkPreviewDataFetched) {
return LinkPreview(
text: message.text,
linkPreviewData: message.linkPreviewData,
hideDescription: message.id == 'display-only-image',
hideTitle: message.id == 'display-only-image',
backgroundColor:
isSentByMe
? Colors.white.withAlpha(100)
: chatTheme.colors.primary.withAlpha(100),
sideBorderColor:
isSentByMe ? Colors.white : chatTheme.colors.primary,
onLinkPreviewDataFetched: (linkPreviewData) {
_chatController.updateMessage(
message,
message.copyWith(
metadata: {
...message.metadata ?? {},
metadataLinkPreviewFetchedKey: true,
},
linkPreviewData: linkPreviewData,
),
);
},
);
}
return null;
},
),
onMessageSend: (text) {
_addItem(text);
},
chatController: _chatController,
currentUserId: _currentUser.id,

resolveUser:
(id) => Future.value(switch (id) {
'me' => _currentUser,
_ => null,
}),
theme: chatTheme,
),
);
}
}
24 changes: 24 additions & 0 deletions examples/flyer_chat/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import 'api_get_chat_id.dart';
import 'api_get_initial_messages.dart';
import 'basic.dart';
import 'gemini.dart';
import 'link_preview.dart';
import 'local.dart';
import 'pagination.dart';
import 'topwidgets_demo.dart';

void main() async {
WidgetsFlutterBinding.ensureInitialized();
Expand Down Expand Up @@ -283,6 +285,28 @@ class _FlyerChatHomePageState extends State<FlyerChatHomePage> {
},
child: const Text('basic'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LinkPreviewExample(dio: _dio),
),
);
},
child: const Text('link preview'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => TopWidgetInBubble(dio: _dio),
),
);
},
child: const Text('Top widgets in bubble'),
),
],
),
),
Expand Down
Loading