Docs: reorganize sidebar docs + fix broken links (#1369)
* reorganize sidebar docs * fix broken links * tweaks
This commit is contained in:
+184
@@ -0,0 +1,184 @@
|
||||
---
|
||||
id: customize_message_widget
|
||||
title: Message
|
||||
---
|
||||
|
||||
Customizing Text Messages with the StreamMessageWidget
|
||||
|
||||
### Introduction
|
||||
|
||||
Every application provides a unique look and feel to their own messaging interface including and not
|
||||
limited to fonts, colors, and shapes.
|
||||
|
||||
This guide details how to customize the `StreamMessageWidget` in the Stream Chat Flutter UI SDK.
|
||||
|
||||
### Building Custom Messages
|
||||
|
||||
This guide goes into detail about the ability to customize the `StreamMessageWidget`. However, if you want
|
||||
to customize the default `StreamMessageWidget` in the `StreamMessageListView` provided, you can use the `.copyWith()` method
|
||||
provided inside the `messageBuilder` parameter of the `StreamMessageListView` like this:
|
||||
|
||||
```dart
|
||||
StreamMessageListView(
|
||||
messageBuilder: (context, details, messageList, defaultImpl) {
|
||||
// Your implementation of the message here
|
||||
// E.g: return Text(details.message.text ?? '');
|
||||
},
|
||||
),
|
||||
```
|
||||
|
||||
### Theming
|
||||
|
||||
You can customize the `StreamMessageWidget` using the `StreamChatTheme` class, so that you can change the
|
||||
message theme at the top instead of creating your own `StreamMessageWidget` at the lower implementation level.
|
||||
|
||||
There are several things you can change in the theme including text styles and colors of various elements.
|
||||
|
||||
You can also set a different theme for the user's own messages and messages received by them.
|
||||
|
||||
:::note
|
||||
Theming allows you to change minor factors like style while using the widget directly allows you much
|
||||
more customization such as replacing a certain widget with another. Some things can only be customized
|
||||
through the widget and not the theme.
|
||||
:::
|
||||
|
||||
Here is an example:
|
||||
|
||||
```dart
|
||||
StreamChatThemeData(
|
||||
|
||||
/// Sets theme for user's messages
|
||||
ownMessageTheme: StreamMessageThemeData(
|
||||
messageBackgroundColor: colorTheme.textHighEmphasis,
|
||||
),
|
||||
|
||||
/// Sets theme for received messages
|
||||
otherMessageTheme: StreamMessageThemeData(
|
||||
avatarTheme: StreamAvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
|
||||
)
|
||||
```
|
||||
|
||||

|
||||
|
||||
#### Change message text style
|
||||
|
||||
The `StreamMessageWidget` has multiple `Text` widgets that you can manipulate the styles of. The three main
|
||||
are the actual message text, user name, message links, and the message timestamp.
|
||||
|
||||
```dart
|
||||
StreamMessageThemeData(
|
||||
messageTextStyle: TextStyle(...),
|
||||
createdAtStyle: TextStyle(...),
|
||||
messageAuthorStyle: TextStyle(...),
|
||||
messageLinksStyle: TextStyle(...),
|
||||
)
|
||||
```
|
||||
|
||||

|
||||
|
||||
#### Change avatar theme
|
||||
|
||||
You can change the attributes of the avatar (if displayed) using the `avatarTheme` property.
|
||||
|
||||
```dart
|
||||
StreamMessageThemeData(
|
||||
avatarTheme: StreamAvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||

|
||||
|
||||
#### Changing Reaction theme
|
||||
|
||||
You also customize the reactions attached to every message using the theme.
|
||||
|
||||
```dart
|
||||
StreamMessageThemeData(
|
||||
reactionsBackgroundColor: Colors.red,
|
||||
reactionsBorderColor: Colors.redAccent,
|
||||
reactionsMaskColor: Colors.pink,
|
||||
),
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Changing Message Actions
|
||||
|
||||
When a message is long pressed, the `StreamMessageActionsModal` is shown.
|
||||
|
||||
The `StreamMessageWidget` allows showing or hiding some options if you so choose.
|
||||
|
||||
```dart
|
||||
StreamMessageWidget(
|
||||
...
|
||||
showUsername = true,
|
||||
showTimestamp = true,
|
||||
showReactions = true,
|
||||
showDeleteMessage = true,
|
||||
showEditMessage = true,
|
||||
showReplyMessage = true,
|
||||
showThreadReplyMessage = true,
|
||||
showResendMessage = true,
|
||||
showCopyMessage = true,
|
||||
showFlagButton = true,
|
||||
showPinButton = true,
|
||||
showPinHighlight = true,
|
||||
),
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Building attachments
|
||||
|
||||
The `customAttachmentBuilders` property allows you to build any kind of attachment (inbuilt or custom)
|
||||
in your own way. While a separate guide is written for this, it is included here because of relevance.
|
||||
|
||||
```dart
|
||||
StreamMessageListView(
|
||||
messageBuilder: (context, details, messages, defaultMessage) {
|
||||
return defaultMessage.copyWith(
|
||||
customAttachmentBuilders: {
|
||||
'location': (context, message, attachments) {
|
||||
final attachmentWidget = Image.network(
|
||||
_buildMapAttachment(
|
||||
attachments[0].extraData['latitude'],
|
||||
attachments[0].extraData['longitude'],
|
||||
),
|
||||
);
|
||||
|
||||
return WrapAttachmentWidget(
|
||||
attachmentWidget: attachmentWidget,
|
||||
attachmentShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
```
|
||||
|
||||
### Widget Builders
|
||||
|
||||
Some parameters allow you to construct your own widget in place of some elements in the `StreamMessageWidget`.
|
||||
|
||||
These are:
|
||||
* `userAvatarBuilder` : Allows user to substitute their own widget in place of the user avatar.
|
||||
* `editMessageInputBuilder` : Allows user to substitute their own widget in place of the input in edit mode.
|
||||
* `textBuilder` : Allows user to substitute their own widget in place of the text.
|
||||
* `bottomRowBuilder` : Allows user to substitute their own widget in the bottom of the message when not deleted.
|
||||
* `deletedBottomRowBuilder` : Allows user to substitute their own widget in the bottom of the message when deleted.
|
||||
|
||||
```dart
|
||||
StreamMessageWidget(
|
||||
...
|
||||
textBuilder: (context, message) {
|
||||
// Add your own text implementation here.
|
||||
},
|
||||
),
|
||||
```
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
---
|
||||
id: customize_text_messages
|
||||
title: Message List View
|
||||
---
|
||||
|
||||
Customizing Text Messages
|
||||
|
||||
### Introduction
|
||||
|
||||
Every application provides a unique look and feel to their own messaging interface including and not
|
||||
limited to fonts, colors, and shapes.
|
||||
|
||||
This guide details how to customize message text in the `StreamMessageListView` / `StreamMessageWidget` in the
|
||||
Stream Chat Flutter UI SDK.
|
||||
|
||||
:::note
|
||||
This guide is specifically for the `StreamMessageListView` but if you intend to display a `StreamMessageWidget`
|
||||
separately, follow the same process without the `.copyWith` and use the default constructor instead.
|
||||
:::
|
||||
|
||||
### Basics of customizing a `StreamMessageWidget`
|
||||
|
||||
First, add a `StreamMessageListView` in the appropriate place where you intend to display messages from a
|
||||
channel.
|
||||
|
||||
```dart
|
||||
StreamMessageListView(
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
Now, we use the `messageBuilder` parameter to build a custom message. The builder function also provides
|
||||
the default implementation of the `StreamMessageWidget` so that we can change certain aspects of the widget
|
||||
without redoing all of the default parameters.
|
||||
|
||||
:::note
|
||||
In earlier versions of the SDK, some `StreamMessageWidget` parameters were exposed directly through the `StreamMessageListView`,
|
||||
however, this quickly becomes hard to maintain as more parameters and customizations are added to the
|
||||
`StreamMessageWidget`. Newer version utilise a cleaner interface to change the parameters by supplying a
|
||||
default message implementation as aforementioned.
|
||||
:::
|
||||
|
||||
```dart
|
||||
StreamMessageListView(
|
||||
...
|
||||
messageBuilder: (context, messageDetails, messageList, defaultWidget) {
|
||||
return defaultWidget;
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
We use `.copyWith()` to customize the widget:
|
||||
|
||||
```dart
|
||||
StreamMessageListView(
|
||||
...
|
||||
messageBuilder: (context, messageDetails, messageList, defaultWidget) {
|
||||
return defaultWidget.copyWith(
|
||||
...
|
||||
);
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### Customizing text
|
||||
|
||||
If you intend to simply change the theme for the text, you need not recreate the whole widget. The
|
||||
`StreamMessageWidget` has a `messageTheme` parameter that allows you to pass the theme for most aspects
|
||||
of the message.
|
||||
|
||||
```dart
|
||||
StreamMessageListView(
|
||||
...
|
||||
messageBuilder: (context, messageDetails, messageList, defaultWidget) {
|
||||
return defaultWidget.copyWith(
|
||||
messageTheme: StreamMessageThemeData(
|
||||
...
|
||||
messageTextStyle: TextStyle(),
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
If you want to replace the entire text widget in the `StreamMessageWidget`, you can use the `textBuilder`
|
||||
parameter which provides a builder for creating a widget to substitute the default text.parameter
|
||||
|
||||
```dart
|
||||
StreamMessageListView(
|
||||
...
|
||||
messageBuilder: (context, messageDetails, messageList, defaultWidget) {
|
||||
return defaultWidget.copyWith(
|
||||
textBuilder: (context, message) {
|
||||
return Text(message.text);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### Adding Hashtags
|
||||
|
||||
To add elements like hashtags, we can override the `textBuilder` in the StreamMessageWidget:
|
||||
|
||||
```dart
|
||||
StreamMessageListView(
|
||||
...
|
||||
messageBuilder: (context, messageDetails, messageList, defaultWidget) {
|
||||
return defaultWidget.copyWith(
|
||||
textBuilder: (context, message) {
|
||||
final text = _replaceHashtags(message.text).replaceAll('\n', '\\\n');
|
||||
final messageTheme = StreamChatTheme.of(context).ownMessageTheme;
|
||||
|
||||
return MarkdownBody(
|
||||
data: text,
|
||||
onTapLink: (
|
||||
String link,
|
||||
String href,
|
||||
String title,
|
||||
) {
|
||||
// Do something with tapped hashtag
|
||||
},
|
||||
styleSheet: MarkdownStyleSheet.fromTheme(
|
||||
Theme.of(context).copyWith(
|
||||
textTheme: Theme.of(context).textTheme.apply(
|
||||
bodyColor: messageTheme.messageText.color,
|
||||
decoration: messageTheme.messageText.decoration,
|
||||
decorationColor: messageTheme.messageText.decorationColor,
|
||||
decorationStyle: messageTheme.messageText.decorationStyle,
|
||||
fontFamily: messageTheme.messageText.fontFamily,
|
||||
),
|
||||
),
|
||||
).copyWith(
|
||||
a: messageTheme.messageLinks,
|
||||
p: messageTheme.messageText,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
|
||||
String _replaceHashtags(String text) {
|
||||
RegExp exp = new RegExp(r"\B#\w\w+");
|
||||
exp.allMatches(text).forEach((match){
|
||||
text = text.replaceAll(
|
||||
'${match.group(0)}', '[${match.group(0)}](${match.group(0).replaceAll(' ', '')})');
|
||||
});
|
||||
return text;
|
||||
}
|
||||
```
|
||||
|
||||
We can replace the hashtags using RegEx and add links for the MarkdownBody which is done here in the
|
||||
`_replaceHashtags()` function.
|
||||
Inside the textBuilder, we use the `flutter_markdown` package to build our hashtags as links.
|
||||
|
||||

|
||||
+81
@@ -0,0 +1,81 @@
|
||||
---
|
||||
id: customize_message_actions
|
||||
title: Message Actions
|
||||
---
|
||||
|
||||
Customizing Message Actions
|
||||
|
||||
### Introduction
|
||||
|
||||
Message actions pop up in message overlay, when you long-press a message.
|
||||
|
||||

|
||||
|
||||
We have provided granular control over these actions.
|
||||
|
||||
By default we render the following message actions:
|
||||
|
||||
* edit message
|
||||
|
||||
* delete message
|
||||
|
||||
* reply
|
||||
|
||||
* thread reply
|
||||
|
||||
* copy message
|
||||
|
||||
* flag message
|
||||
|
||||
* pin message
|
||||
|
||||
:::note
|
||||
Edit and delete message are only available on messages sent by the user.
|
||||
Additionally, pinning a message requires you to add the roles which are allowed to pin messages.
|
||||
:::
|
||||
|
||||
### Partially remove some message actions
|
||||
|
||||
For example, if you only want to keep "copy message" and "delete message",
|
||||
here is how to do it using the `messageBuilder` with our `StreamMessageWidget`.
|
||||
|
||||
```dart
|
||||
StreamMessageListView(
|
||||
messageBuilder: (context, details, messages, defaultMessage) {
|
||||
return defaultMessage.copyWith(
|
||||
showFlagButton: false,
|
||||
showEditMessage: false,
|
||||
showCopyMessage: true,
|
||||
showDeleteMessage: details.isMyMessage,
|
||||
showReplyMessage: false,
|
||||
showThreadReplyMessage: false,
|
||||
);
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### Add a new custom message action
|
||||
|
||||
The SDK also allows you to add new actions into the dialog.
|
||||
|
||||
For example, let's suppose you want to introduce a new message action - "Demo Action":
|
||||
|
||||
We use the `customActions` parameter of the `StreamMessageWidget` to add extra actions.
|
||||
|
||||
```dart
|
||||
StreamMessageListView(
|
||||
messageBuilder: (context, details, messages, defaultMessage) {
|
||||
return defaultMessage.copyWith(
|
||||
customActions: [
|
||||
StreamMessageAction(
|
||||
leading: Icon(Icons.add),
|
||||
title: Text('Demo Action'),
|
||||
onTap: (message) {
|
||||
/// Complete action here
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
)
|
||||
```
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
---
|
||||
id: adding_custom_attachments
|
||||
title: Attachments
|
||||
---
|
||||
|
||||
Adding Your Own Types Of Attachments To A Message
|
||||
|
||||
### Introduction
|
||||
|
||||
Stream Chat supports attachment types like images, video and files by default. You can also add your
|
||||
own types of attachments through the SDK such as location, audio, etc.
|
||||
|
||||
This involves doing three things:
|
||||
|
||||
1) Rendering the attachment thumbnail in the `StreamMessageInput`
|
||||
|
||||
2) Sending a message with the custom attachment
|
||||
|
||||
3) Rendering the custom message attachment
|
||||
|
||||
To do this, let's check out an example to add location sharing to Stream Chat.
|
||||
|
||||
### Location Sharing
|
||||
|
||||
Let's build an example of location sharing option in the app:
|
||||
|
||||

|
||||
|
||||
* Show a "Share Location" button next to StreamMessageInput Textfield.
|
||||
|
||||
* When the user presses this button, it should fetch the current location coordinates of the user, and send a message on the channel as follows:
|
||||
|
||||
```dart
|
||||
Message(
|
||||
text: 'This is my location',
|
||||
attachments: [
|
||||
Attachment(
|
||||
uploadState: UploadState.success(),
|
||||
type: 'location',
|
||||
extraData: {
|
||||
'latitude': 'fetched_latitude',
|
||||
'longitude': 'fetched_longitude',
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
For our example, we are going to use [geolocator](https://pub.dev/packages/geolocator) library.
|
||||
Please check their [setup instructions](https://pub.dev/packages/geolocator) on their docs.
|
||||
|
||||
NOTE: If you are testing on iOS simulator, you will need to set some dummy coordinates, as mentioned [here](https://stackoverflow.com/a/31238119/7489541).
|
||||
Also don't forget to enable "location update" capability in background mode, from XCode.
|
||||
|
||||
On the receiver end, `location` type attachment should be rendered in map view, in the `StreamMessageListView`.
|
||||
We are going to use [Google Static Maps API](https://developers.google.com/maps/documentation/maps-static/overview) to render the map in the message.
|
||||
You can use other libraries as well such as [google_maps_flutter](https://pub.dev/packages/google_maps_flutter).
|
||||
|
||||
First, we add a button which when clicked fetches and shares location into the `MessageInput`:
|
||||
|
||||
```dart
|
||||
StreamMessageInput(
|
||||
actions: [
|
||||
InkWell(
|
||||
child: Icon(
|
||||
Icons.location_on,
|
||||
size: 20.0,
|
||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||
),
|
||||
onTap: () {
|
||||
var channel = StreamChannel.of(context).channel;
|
||||
var user = StreamChat.of(context).user;
|
||||
|
||||
_determinePosition().then((value) {
|
||||
channel.sendMessage(
|
||||
Message(
|
||||
text: 'This is my location',
|
||||
attachments: [
|
||||
Attachment(
|
||||
uploadState: UploadState.success(),
|
||||
type: 'location',
|
||||
extraData: {
|
||||
'latitude': value.latitude.toString(),
|
||||
'longitude': value.longitude.toString(),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).catchError((err) {
|
||||
print('Error getting location!');
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Future<Position> _determinePosition() async {
|
||||
bool serviceEnabled;
|
||||
LocationPermission permission;
|
||||
|
||||
serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
return Future.error('Location services are disabled.');
|
||||
}
|
||||
|
||||
permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
if (permission == LocationPermission.deniedForever) {
|
||||
return Future.error(
|
||||
'Location permissions are permanently denied, we cannot request permissions.');
|
||||
}
|
||||
|
||||
if (permission == LocationPermission.denied) {
|
||||
return Future.error(
|
||||
'Location permissions are denied');
|
||||
}
|
||||
}
|
||||
|
||||
return await Geolocator.getCurrentPosition();
|
||||
}
|
||||
```
|
||||
|
||||
Next, we build the Static Maps URL (Add your API key before using the code snippet):
|
||||
|
||||
```dart
|
||||
String _buildMapAttachment(String lat, String long) {
|
||||
var baseURL = 'https://maps.googleapis.com/maps/api/staticmap?';
|
||||
var url = Uri(
|
||||
scheme: 'https',
|
||||
host: 'maps.googleapis.com',
|
||||
port: 443,
|
||||
path: '/maps/api/staticmap',
|
||||
queryParameters: {
|
||||
'center': '${lat},${long}',
|
||||
'zoom': '15',
|
||||
'size': '600x300',
|
||||
'maptype': 'roadmap',
|
||||
'key': 'YOUR_API_KEY',
|
||||
'markers': 'color:red|${lat},${long}'
|
||||
});
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
```
|
||||
|
||||
And then modify the `StreamMessageListView` and tell it how to build a location attachment, using the `messageBuilder` property and copying the default message implementation overriding the `customAttachmentBuilders` property:
|
||||
|
||||
```dart
|
||||
StreamMessageListView(
|
||||
messageBuilder: (context, details, messages, defaultMessage) {
|
||||
return defaultMessage.copyWith(
|
||||
customAttachmentBuilders: {
|
||||
'location': (context, message, attachments) {
|
||||
final attachmentWidget = Image.network(
|
||||
_buildMapAttachment(
|
||||
attachments[0].extraData['latitude'],
|
||||
attachments[0].extraData['longitude'],
|
||||
),
|
||||
);
|
||||
|
||||
return WrapAttachmentWidget(
|
||||
attachmentWidget: attachmentWidget,
|
||||
attachmentShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
```
|
||||
|
||||
This gives us the final location attachment:
|
||||
|
||||

|
||||
|
||||
Additionally, you can also add a thumbnail if a message has a location attachment (unlike in this case, where we sent the message directly).
|
||||
|
||||
To do this, we will:
|
||||
|
||||
1) Add an attachment instead of sending a message
|
||||
|
||||
2) Customize the `StreamMessageInput`
|
||||
|
||||
First, we add the attachment when the location button is clicked:
|
||||
|
||||
```dart
|
||||
StreamMessageInputController _messageInputController = StreamMessageInputController();
|
||||
|
||||
StreamMessageInput(
|
||||
messageInputController: _messageInputController,
|
||||
actions: [
|
||||
InkWell(
|
||||
child: Icon(
|
||||
Icons.location_on,
|
||||
size: 20.0,
|
||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||
),
|
||||
onTap: () {
|
||||
_determinePosition().then((value) {
|
||||
_messageInputController.addAttachment(
|
||||
Attachment(
|
||||
uploadState: UploadState.success(),
|
||||
type: 'location',
|
||||
extraData: {
|
||||
'latitude': value.latitude.toString(),
|
||||
'longitude': value.longitude.toString(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}).catchError((err) {
|
||||
print('Error getting location!');
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
```
|
||||
|
||||
After this, we can build the thumbnail:
|
||||
|
||||
```dart
|
||||
StreamMessageInput(
|
||||
messageInputController: _messageInputController,
|
||||
actions: [
|
||||
InkWell(
|
||||
child: Icon(
|
||||
Icons.location_on,
|
||||
size: 20.0,
|
||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||
),
|
||||
onTap: () {
|
||||
_determinePosition().then((value) {
|
||||
_messageInputController.addAttachment(
|
||||
Attachment(
|
||||
uploadState: UploadState.success(),
|
||||
type: 'location',
|
||||
extraData: {
|
||||
'latitude': value.latitude.toString(),
|
||||
'longitude': value.longitude.toString(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}).catchError((err) {
|
||||
print('Error getting location!');
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
attachmentThumbnailBuilders: {
|
||||
'location': (context, attachment) {
|
||||
return Image.network(
|
||||
_buildMapAttachment(
|
||||
attachment.extraData['latitude'],
|
||||
attachment.extraData['longitude'],
|
||||
),
|
||||
);
|
||||
},
|
||||
},
|
||||
),
|
||||
```
|
||||
|
||||
And we can see the thumbnails in the StreamMessageInput:
|
||||
|
||||

|
||||
+164
@@ -0,0 +1,164 @@
|
||||
---
|
||||
id: customize_attachment_picker_modal
|
||||
title: Attachment Picker Modal
|
||||
---
|
||||
|
||||
Customizing the Attachment Picker Modal
|
||||
|
||||
### Introduction
|
||||
|
||||
The Attachment Picker is a modal that allows users to select attachments from their device.
|
||||
It is generally used when a user taps the attachment button in the [StreamMessageInput](../../03-stream_chat_flutter/stream_message_input.mdx).
|
||||
|
||||
By default, the Attachment Picker provides multiple picker options as per the platform.
|
||||
- For example, on Mobile, the default options are Camera, Gallery, File, and Video.
|
||||
- On Web and Desktop, the default options are Image, Video and File.
|
||||
|
||||
### Customizing the Attachment Picker Modal
|
||||
|
||||
The Attachment Picker Modal can be customized by passing the different values to the `showStreamAttachmentPickerModalBottomSheet` function.
|
||||
|
||||
#### Initial Attachments
|
||||
|
||||
The initial attachments can be passed to the Attachment Picker Modal in two ways.
|
||||
|
||||
* By passing the `initialAttachments` parameter.
|
||||
|
||||
```dart
|
||||
showStreamAttachmentPickerModalBottomSheet(
|
||||
context: context,
|
||||
initialAttachments: [
|
||||
// Pass the initial attachments to the modal here if any are available already (optional)
|
||||
...messageInputController.attachments,
|
||||
],
|
||||
);
|
||||
```
|
||||
|
||||
* By creating a new instance of the `AttachmentPickerModalController` and passing it to the `controller` parameter.
|
||||
|
||||
```dart
|
||||
final attachmentPickerController = StreamAttachmentPickerController(
|
||||
initialAttachments: [
|
||||
// Pass the initial attachments to the modal here if any are available already (optional)
|
||||
...messageInputController.attachments,
|
||||
],
|
||||
|
||||
// The `maxAttachmentSize` and `maxAttachmentCount` can also be set while creating a controller.
|
||||
maxAttachmentSize: 10 * 1024 * 1024, // 10 MB
|
||||
maxAttachmentCount: 10, // 10 attachments
|
||||
);
|
||||
|
||||
showStreamAttachmentPickerModalBottomSheet(
|
||||
context: context,
|
||||
controller: attachmentPickerController,
|
||||
);
|
||||
```
|
||||
|
||||
#### Custom Attachment Picker Options
|
||||
|
||||
The Attachment Picker Modal provides a default set of options as per the platform.
|
||||
However, you can also customize the options by passing the `customOptions` parameter.
|
||||
|
||||
```dart
|
||||
showStreamAttachmentPickerModalBottomSheet(
|
||||
context: context,
|
||||
customOptions: [
|
||||
// Pass the custom attachment picker options here
|
||||
AttachmentPickerOption(
|
||||
icon: Icon(Icons.audiotrack),
|
||||
supportedTypes: [AttachmentPickerType.audios],
|
||||
optionViewBuilder: (context, attachmentPickerController) {
|
||||
return AudioPicker(
|
||||
onAudioPicked: (audio) async {
|
||||
await attachmentPickerController.addAttachment(audio);
|
||||
return Navigator.pop(context, attachmentPickerController.value);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
```
|
||||
|
||||
#### Attachment thumbnail size
|
||||
|
||||
The size of the attachment thumbnail item shown in the gallery picker can be defined by passing the `attachmentThumbnailSize` parameter.
|
||||
|
||||
```dart
|
||||
showStreamAttachmentPickerModalBottomSheet(
|
||||
context: context,
|
||||
attachmentThumbnailSize: ThumbnailSize.square(600),
|
||||
);
|
||||
```
|
||||
|
||||
#### Attachment thumbnail format
|
||||
|
||||
The format of the attachment thumbnail item shown in the gallery picker can be defined by passing the `attachmentThumbnailFormat` parameter.
|
||||
|
||||
Possible values are `ThumbnailFormat.jpeg` and `ThumbnailFormat.png`.
|
||||
|
||||
```dart
|
||||
showStreamAttachmentPickerModalBottomSheet(
|
||||
context: context,
|
||||
attachmentThumbnailFormat: ThumbnailFormat.jpeg,
|
||||
);
|
||||
```
|
||||
|
||||
#### Attachment thumbnail quality
|
||||
|
||||
The quality of the attachment thumbnail item shown in the gallery picker can be defined by passing the `attachmentThumbnailQuality` parameter.
|
||||
|
||||
Possible values are between 0 and 100.
|
||||
|
||||
```dart
|
||||
showStreamAttachmentPickerModalBottomSheet(
|
||||
context: context,
|
||||
attachmentThumbnailQuality: 70,
|
||||
);
|
||||
```
|
||||
|
||||
#### Attachment thumbnail scale
|
||||
|
||||
The scale of the attachment thumbnail item shown in the gallery picker can be defined by passing the `attachmentThumbnailScale` parameter.
|
||||
|
||||
For example, if this is 2.0, it means that there are four image pixels for every one logical pixel, and the image's actual width and height are
|
||||
double the height and width that should be used when painting the image.
|
||||
|
||||
```dart
|
||||
showStreamAttachmentPickerModalBottomSheet(
|
||||
context: context,
|
||||
attachmentThumbnailScale: 2.0,
|
||||
);
|
||||
```
|
||||
|
||||
#### Additional modal bottom sheet parameters
|
||||
|
||||
The `showStreamAttachmentPickerModalBottomSheet` function also accepts the parameters that are available in the `showModalBottomSheet` function.
|
||||
|
||||
```dart
|
||||
showStreamAttachmentPickerModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
useRootNavigator: true,
|
||||
elevation: 4,
|
||||
isDismissible: true,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
barrierColor: Colors.black.withOpacity(0.5),
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: 500,
|
||||
maxWidth: 500,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(16.0),
|
||||
),
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
---
|
||||
id: autocomplete_triggers
|
||||
title: Autocomplete Triggers
|
||||
---
|
||||
|
||||
Adding Custom Autocomplete Triggers
|
||||
|
||||
### Introduction
|
||||
|
||||
The [StreamMessageInput](../../03-stream_chat_flutter/stream_message_input.mdx) widget provides a way to add custom autocomplete triggers using the `StreamMessageInput.customAutocompleteTriggers` property.
|
||||
|
||||
By default we provide autocomplete triggers for mentions and commands, but it's very easy to add your custom ones.
|
||||
|
||||
### Add Emoji Autocomplete Trigger
|
||||
|
||||
To add a custom emoji autocomplete trigger, you must first create an `AutoCompleteOptions` widget.
|
||||
This widget will be used to show the autocomplete options.
|
||||
|
||||
For this example we're using two external dependencies:
|
||||
|
||||
- [emojis](https://pub.dev/packages/emojis)
|
||||
- [substring_highlight](https://pub.dev/packages/substring_highlight)
|
||||
|
||||
```dart
|
||||
import 'package:emojis/emoji.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:substring_highlight/substring_highlight.dart';
|
||||
|
||||
/// Overlay for displaying emoji that can be used
|
||||
class StreamEmojiAutocompleteOptions extends StatelessWidget {
|
||||
/// Constructor for creating a [StreamEmojiAutocompleteOptions]
|
||||
const StreamEmojiAutocompleteOptions({
|
||||
super.key,
|
||||
required this.query,
|
||||
this.onEmojiSelected,
|
||||
});
|
||||
|
||||
/// Query for searching emoji.
|
||||
final String query;
|
||||
|
||||
/// Callback called when an emoji is selected.
|
||||
final ValueSetter<Emoji>? onEmojiSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final emojis = Emoji.all().where((it) {
|
||||
final normalizedQuery = query.toUpperCase();
|
||||
final normalizedShortName = it.shortName.toUpperCase();
|
||||
|
||||
return normalizedShortName.contains(normalizedQuery);
|
||||
});
|
||||
|
||||
if (emojis.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
return StreamAutocompleteOptions<Emoji>(
|
||||
options: emojis,
|
||||
optionBuilder: (context, emoji) {
|
||||
final themeData = Theme.of(context);
|
||||
return ListTile(
|
||||
dense: true,
|
||||
horizontalTitleGap: 0,
|
||||
leading: Text(
|
||||
emoji.char,
|
||||
style: themeData.textTheme.headline6!.copyWith(
|
||||
fontSize: 24,
|
||||
),
|
||||
),
|
||||
title: SubstringHighlight(
|
||||
text: emoji.shortName,
|
||||
term: query,
|
||||
textStyleHighlight: themeData.textTheme.headline6!.copyWith(
|
||||
color: Colors.yellow,
|
||||
fontSize: 14.5,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textStyle: themeData.textTheme.headline6!.copyWith(
|
||||
fontSize: 14.5,
|
||||
),
|
||||
),
|
||||
onTap: onEmojiSelected == null ? null : () => onEmojiSelected!(emoji),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now it's time to use the `StreamEmojiAutocompleteOptions` widget.
|
||||
|
||||
```dart
|
||||
StreamMessageInput(
|
||||
customAutocompleteTriggers: [
|
||||
StreamAutocompleteTrigger(
|
||||
trigger: ':',
|
||||
minimumRequiredCharacters: 2,
|
||||
optionsViewBuilder: (
|
||||
context,
|
||||
autocompleteQuery,
|
||||
messageEditingController,
|
||||
) {
|
||||
final query = autocompleteQuery.query;
|
||||
return StreamEmojiAutocompleteOptions(
|
||||
query: query,
|
||||
onEmojiSelected: (emoji) {
|
||||
// accepting the autocomplete option.
|
||||
StreamAutocomplete.of(context).acceptAutocompleteOption(
|
||||
emoji.char,
|
||||
keepTrigger: false,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
```
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
---
|
||||
id: slidable_channel_list_preview
|
||||
title: Channel List Preview
|
||||
---
|
||||
|
||||
Slidable Channel List Preview
|
||||
|
||||
### Introduction
|
||||
|
||||
The default slidable behavior within the channel list has been removed in v4 of the Stream Chat Flutter SDK.
|
||||
This guide will show you how you can easily add this functionality yourself.
|
||||
|
||||
Please see our [full v4 migration guide](../../05-guides/08-migrations/migration_guide_4_0.mdx) if you're migrating from an earlier version of the Stream Chat Flutter SDK.
|
||||
|
||||

|
||||
|
||||
### Prerequisites
|
||||
|
||||
This guide assumes you are familiar with the Stream Chat SDK.
|
||||
If you're new to Stream Chat Flutter, we recommend looking at our [getting started tutorial](https://getstream.io/chat/flutter/tutorial/).
|
||||
|
||||
**Dependencies:**
|
||||
|
||||
```dart
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
stream_chat_flutter: ^4.0.0
|
||||
flutter_slidable: ^1.2.0
|
||||
```
|
||||
|
||||
⚠️ Note: The examples shown in this guide use the above packages and versions.
|
||||
|
||||
### Example Code - Custom Stream Channel Item Builder
|
||||
|
||||
In this example, you are doing a few important things in the ChannelListPage widget. You're:
|
||||
|
||||
- Using the **flutter_slidable** package to easily add slide functionality.
|
||||
- Passing in the `itemBuilder` argument for the **StreamChannelListView** widget. This gives access to the current **BuildContext**, **Channel**, and **StreamChannelListTile**, and allows you to create, or customize, the stream channel list tiles.
|
||||
- Returning a Slidable widget with two CustomSlidableAction widgets - to delete a channel and show more options. These widgets come from the flutter_slidable package.
|
||||
- Adding `onPressed` behaviour to call `showConfirmationBottomSheet` and `showChannelInfoModalBottomSheet`. These methods come from the **stream_chat_flutter** package. They have a few different on-tap callbacks you can supply, for example, `onViewInfoTap`. Alternatively, you can create custom dialogs from scratch.
|
||||
- Using the **StreamChannelListController** to perform actions, such as, `deleteChannel`.
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
void main() async {
|
||||
final client = StreamChatClient(
|
||||
's2dxdhpxd94g',
|
||||
);
|
||||
|
||||
await client.connectUser(
|
||||
User(id: 'super-band-9'),
|
||||
'''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
|
||||
);
|
||||
|
||||
runApp(
|
||||
MyApp(
|
||||
client: client,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({
|
||||
Key? key,
|
||||
required this.client,
|
||||
}) : super(key: key);
|
||||
|
||||
final StreamChatClient client;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
builder: (context, child) => StreamChat(
|
||||
client: client,
|
||||
child: child,
|
||||
),
|
||||
home: ChannelListPage(
|
||||
client: client,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelListPage extends StatefulWidget {
|
||||
const ChannelListPage({
|
||||
Key? key,
|
||||
required this.client,
|
||||
}) : super(key: key);
|
||||
|
||||
final StreamChatClient client;
|
||||
|
||||
@override
|
||||
State<ChannelListPage> createState() => _ChannelListPageState();
|
||||
}
|
||||
|
||||
class _ChannelListPageState extends State<ChannelListPage> {
|
||||
late final _controller = StreamChannelListController(
|
||||
client: widget.client,
|
||||
filter: Filter.in_(
|
||||
'members',
|
||||
[StreamChat.of(context).currentUser!.id],
|
||||
),
|
||||
sort: const [SortOption('last_message_at')],
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
body: SlidableAutoCloseBehavior(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: _controller.refresh,
|
||||
child: StreamChannelListView(
|
||||
controller: _controller,
|
||||
itemBuilder: (context, channel, tile) {
|
||||
final chatTheme = StreamChatTheme.of(context);
|
||||
final backgroundColor = chatTheme.colorTheme.inputBg;
|
||||
final canDeleteChannel = channel.ownCapabilities
|
||||
.contains(PermissionType.deleteChannel);
|
||||
return Slidable(
|
||||
groupTag: 'channels-actions',
|
||||
endActionPane: ActionPane(
|
||||
extentRatio: canDeleteChannel ? 0.40 : 0.20,
|
||||
motion: const BehindMotion(),
|
||||
children: [
|
||||
CustomSlidableAction(
|
||||
onPressed: (_) {
|
||||
showChannelInfoModalBottomSheet(
|
||||
context: context,
|
||||
channel: channel,
|
||||
onViewInfoTap: () {
|
||||
Navigator.pop(context);
|
||||
// Navigate to info screen
|
||||
},
|
||||
);
|
||||
},
|
||||
backgroundColor: backgroundColor,
|
||||
child: const Icon(Icons.more_horiz),
|
||||
),
|
||||
if (canDeleteChannel)
|
||||
CustomSlidableAction(
|
||||
backgroundColor: backgroundColor,
|
||||
child: StreamSvgIcon.delete(
|
||||
color: chatTheme.colorTheme.accentError,
|
||||
),
|
||||
onPressed: (_) async {
|
||||
final res = await showConfirmationBottomSheet(
|
||||
context,
|
||||
title: 'Delete Conversation',
|
||||
question:
|
||||
'Are you sure you want to delete this conversation?',
|
||||
okText: 'Delete',
|
||||
cancelText: 'Cancel',
|
||||
icon: StreamSvgIcon.delete(
|
||||
color: chatTheme.colorTheme.accentError,
|
||||
),
|
||||
);
|
||||
if (res == true) {
|
||||
await _controller.deleteChannel(channel);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
child: tile,
|
||||
);
|
||||
},
|
||||
onChannelTap: (channel) => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => StreamChannel(
|
||||
channel: channel,
|
||||
child: const ChannelPage(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class ChannelPage extends StatelessWidget {
|
||||
const ChannelPage({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: const StreamChannelHeader(),
|
||||
body: Column(
|
||||
children: const <Widget>[
|
||||
Expanded(
|
||||
child: StreamMessageListView(),
|
||||
),
|
||||
StreamMessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The above is the complete sample, and all you need for a basic implementation.
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"label": "Custom Widgets"
|
||||
}
|
||||
Reference in New Issue
Block a user