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"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"label": "Customization"
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
id: stream_chat_and_theming
|
||||
sidebar_position: 1
|
||||
title: Theming
|
||||
---
|
||||
|
||||
Understanding How To Customize Widgets Using `StreamChatTheme`
|
||||
|
||||
Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChatTheme-class.html) and [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChatThemeData-class.html)
|
||||
|
||||
### Background
|
||||
|
||||
Stream's UI SDK makes it easy for developers to add custom styles and attributes to our widgets. Like most Flutter frameworks, Stream exposes a dedicated widget for theming.
|
||||
|
||||
Using `StreamChatTheme`, users can customize most aspects of our UI widgets by setting attributes using `StreamChatThemeData`.
|
||||
|
||||
Similar to the `Theme` and `ThemeData` in Flutter, Stream Chat uses a top level [inherited widget](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html) to provide theming information throughout your application. This can be optionally set at the top of your application tree or at a localized point in your widget sub-tree.
|
||||
|
||||
If you'd like to customize the look and feel of Stream chat across your entire application, we recommend setting your theme at the top level. Conversely, users can customize specific screens or widgets by wrapping components in a `StreamChatTheme`.
|
||||
|
||||
### A closer look at StreamChatThemeData
|
||||
|
||||
Looking at the constructor for `StreamChatThemeData`, we can see the full list of properties and widgets available for customization.
|
||||
|
||||
Some high-level properties such as `textTheme` or `colorTheme` can be set application-wide directly from this class. In contrast, larger components such as `ChannelHeader`, `MessageInputs`, etc. have been broken up into smaller theme objects.
|
||||
|
||||
```dart
|
||||
factory StreamChatThemeData({
|
||||
Brightness? brightness,
|
||||
TextTheme? textTheme,
|
||||
ColorTheme? colorTheme,
|
||||
ChannelListHeaderTheme? channelListHeaderTheme,
|
||||
ChannelPreviewTheme? channelPreviewTheme,
|
||||
ChannelTheme? channelTheme,
|
||||
MessageTheme? otherMessageTheme,
|
||||
MessageTheme? ownMessageTheme,
|
||||
MessageInputTheme? messageInputTheme,
|
||||
Widget Function(BuildContext, Channel)? defaultChannelImage,
|
||||
Widget Function(BuildContext, User)? defaultUserImage,
|
||||
IconThemeData? primaryIconTheme,
|
||||
List<ReactionIcon>? reactionIcons,
|
||||
});
|
||||
```
|
||||
|
||||
### Stream Chat Theme in use
|
||||
|
||||
Let's take a look at customizing widgets using `StreamChatTheme`. In the example below, we can change the default color theme to yellow and override the channel header's typography and colors.
|
||||
|
||||
```dart
|
||||
builder: (context, child) => StreamChat(
|
||||
client: client,
|
||||
child: child,
|
||||
streamChatThemeData: StreamChatThemeData(
|
||||
colorTheme: ColorTheme.light(
|
||||
primaryAccent: const Color(0xffffe072),
|
||||
),
|
||||
channelTheme: ChannelTheme(
|
||||
channelHeaderTheme: ChannelHeaderTheme(
|
||||
color: const Color(0xffd34646),
|
||||
title: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
```
|
||||
|
||||
We are creating this class at the very top of our widget tree using the `streamChatThemeData` parameter found in the `StreamChat` widget.
|
||||
|
||||

|
||||
Reference in New Issue
Block a user