Merge remote-tracking branch 'origin/develop' into feat/localization

# Conflicts:
#	packages/stream_chat_flutter/example/lib/main.dart
#	packages/stream_chat_flutter/example/pubspec.yaml
#	packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart
#	packages/stream_chat_flutter/lib/src/channel_list_view.dart
#	packages/stream_chat_flutter/lib/src/gallery_footer.dart
#	packages/stream_chat_flutter/lib/src/message_input.dart
#	packages/stream_chat_flutter/lib/src/message_list_view.dart
#	packages/stream_chat_flutter/lib/src/user_item.dart
This commit is contained in:
xsahil03x
2021-07-19 12:49:07 +05:30
188 changed files with 6663 additions and 3145 deletions
+85
View File
@@ -1,3 +1,88 @@
## 2.0.0
🛑️ Breaking Changes from `1.5.4`
- Migrate this package to null safety
- Renamed `ChannelImage` to `ChannelAvatar`
- Updated `StreamChatThemeData.reactionIcons` to accept custom builder
- Renamed `ColorTheme` properties to reflect the purpose of the colors
- `ColorTheme.black` -> `ColorTheme.textHighEmphasis`
- `ColorTheme.grey` -> `ColorTheme.textLowEmphasis`
- `ColorTheme.greyGainsboro` -> `ColorTheme.disabled`
- `ColorTheme.greyWhisper` -> `ColorTheme.borders`
- `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg`
- `ColorTheme.whiteSnow` -> `ColorTheme.appBg`
- `ColorTheme.white` -> `ColorTheme.barsBg`
- `ColorTheme.blueAlice` -> `ColorTheme.linkBg`
- `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary`
- `ColorTheme.accentRed` -> `ColorTheme.accentError`
- `ColorTheme.accentGreen` -> `ColorTheme.accentInfo`
- `ChannelListCore` options property is removed in favor of individual properties
- `options.state` -> bool state
- `options.watch` -> bool watch
- `options.presence` -> bool presence
- `UserListView` options property is removed in favor of individual properties
- `options.presence` -> bool presence
- Renamed `ImageHeader` to `GalleryHeader`
- Renamed `ImageFooter` to `GalleryFooter`
- `MessageBuilder` and `ParentMessageBuilder` signature is now
```dart
typedef MessageBuilder = Widget Function(
BuildContext,
MessageDetails,
List<Message>,
MessageWidget defaultMessageWidget,
);
```
the last parameter is the default `MessageWidget`
You can call `.copyWith` to customize just a subset of properties
✅ Added
- Added video compress options (frame and quality) to `MessageInput`
- TypingIndicator now has a property called `parentId` to show typing indicator specific to threads
- [#493](https://github.com/GetStream/stream-chat-flutter/pull/493): add support for messageListView header/footer
- `MessageWidget` accepts a `userAvatarBuilder`
- Added pinMessage ui support
- Added `MessageListView.threadSeparatorBuilder` property
- Added `MessageInput.onError` property to allow error handling
- Added `GalleryHeader/GalleryFooter` theme classes
🐞 Fixed
- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text box when editing
message
- Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator use case
- [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without a reload
- `MessageListView` not rendering if the user is not a member of the channel
- Fix `MessageInput` overflow when there are no actions
- Minor fixes and improvements
## 2.0.0-nullsafety.9
🛑️ Breaking Changes from `2.0.0-nullsafety.8`
- Renamed `ColorTheme` properties to reflect the purpose of the colors
- `ColorTheme.black` -> `ColorTheme.textHighEmphasis`
- `ColorTheme.grey` -> `ColorTheme.textLowEmphasis`
- `ColorTheme.greyGainsboro` -> `ColorTheme.disabled`
- `ColorTheme.greyWhisper` -> `ColorTheme.borders`
- `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg`
- `ColorTheme.whiteSnow` -> `ColorTheme.appBg`
- `ColorTheme.white` -> `ColorTheme.barsBg`
- `ColorTheme.blueAlice` -> `ColorTheme.linkBg`
- `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary`
- `ColorTheme.accentRed` -> `ColorTheme.accentError`
- `ColorTheme.accentGreen` -> `ColorTheme.accentInfo`
✅ Added
- Added video compress options (frame and quality) to `MessageInput`
## 2.0.0-nullsafety.8
🛑️ Breaking Changes from `2.0.0-nullsafety.7`
+1 -1
View File
@@ -28,7 +28,7 @@ It teaches you how to use this SDK and also shows how to make frequently require
## Example App
This repo includes a fully functional example app with setup instructions.
The example is available under the [example](https://github.com/GetStream/stream-chat-flutter/tree/master/example) folder.
The example is available under the [example](https://github.com/GetStream/stream-chat-flutter/tree/master/packages/stream_chat_flutter/example) folder.
## Add dependency
Add this to your package's pubspec.yaml file, use the latest version [![Pub](https://img.shields.io/pub/v/stream_chat_flutter.svg)](https://pub.dartlang.org/packages/stream_chat_flutter)
@@ -34,84 +34,96 @@ class _HindiStreamChatLocalizationsDelegate
void main() async {
WidgetsFlutterBinding.ensureInitialized();
/// Create a new instance of [StreamChatClient] passing the apikey obtained from your
/// project dashboard.
/// Create a new instance of [StreamChatClient] passing the apikey obtained
/// from your project dashboard.
final client = StreamChatClient(
's2dxdhpxd94g',
logLevel: Level.INFO,
);
/// Set the current user and connect the websocket. In a production scenario, this should be done using
/// a backend to generate a user token using our server SDK.
/// Set the current user and connect the websocket. In a production
/// scenario, this should be done using a backend to generate a user token
/// using our server SDK.
///
/// Please see the following for more information:
/// https://getstream.io/chat/docs/ios_user_setup_and_tokens/
await client.connectUser(
User(id: 'super-band-9'),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.'
'0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
);
final channel = client.channel('messaging', id: 'godevs');
await channel.watch();
runApp(MyApp(client, channel));
runApp(
MyApp(
client: client,
channel: channel,
),
);
}
/// Example application using Stream Chat Flutter widgets.
/// Stream Chat Flutter is a set of Flutter widgets which provide full chat functionalities
/// for building Flutter applications using Stream.
/// If you'd prefer using minimal wrapper widgets for your app, please see our other
///
/// Stream Chat Flutter is a set of Flutter widgets which provide full chat
/// functionalities for building Flutter applications using Stream. If you'd
/// prefer using minimal wrapper widgets for your app, please see our other
/// package, `stream_chat_flutter_core`.
class MyApp extends StatelessWidget {
/// Example using Stream's Flutter package.
///
/// If you'd prefer using minimal wrapper widgets for your app, please see
/// our other package, `stream_chat_flutter_core`.
const MyApp({
Key? key,
required this.client,
required this.channel,
}) : super(key: key);
/// Instance of Stream Client.
/// Stream's [StreamChatClient] can be used to connect to our servers and set the default
/// user for the application. Performing these actions trigger a websocket connection
/// allowing for real-time updates.
///
/// Stream's [StreamChatClient] can be used to connect to our servers and
/// set the default user for the application. Performing these actions
/// trigger a websocket connection allowing for real-time updates.
final StreamChatClient client;
/// Instance of the Channel
final Channel channel;
/// Example using Stream's Flutter package.
/// If you'd prefer using minimal wrapper widgets for your app, please see our other
/// package, `stream_chat_flutter_core`.
MyApp(this.client, this.channel);
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: ThemeMode.system,
supportedLocales: [
Locale('en', 'US'),
Locale('hi', 'IN'),
],
localizationsDelegates: [
GlobalStreamChatLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
StreamChatLocalizationsHi.delegate,
],
builder: (context, widget) {
return StreamChat(
Widget build(BuildContext context) => MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: ThemeMode.system,
supportedLocales: [
Locale('en', 'US'),
Locale('hi', 'IN'),
],
localizationsDelegates: [
GlobalStreamChatLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
StreamChatLocalizationsHi.delegate,
],
builder: (context, widget) => StreamChat(
client: client,
child: widget,
);
},
home: StreamChannel(
channel: channel,
child: ChannelPage(),
),
);
}
),
home: StreamChannel(
channel: channel,
child: const ChannelPage(),
),
);
}
/// A list of messages sent in the current channel.
///
/// This is implemented using [MessageListView], a widget that provides query functionalities
/// fetching the messages from the api and showing them in a listView
/// This is implemented using [MessageListView], a widget that provides query
/// functionalities fetching the messages from the api and showing them in a
/// listView.
class ChannelPage extends StatelessWidget {
/// Creates the page that shows the list of messages
const ChannelPage({
@@ -119,17 +131,15 @@ class ChannelPage extends StatelessWidget {
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: ChannelHeader(),
body: Column(
children: <Widget>[
Expanded(
child: MessageListView(),
),
MessageInput(),
],
),
);
}
Widget build(BuildContext context) => Scaffold(
appBar: const ChannelHeader(),
body: Column(
children: const <Widget>[
Expanded(
child: MessageListView(),
),
MessageInput(),
],
),
);
}
@@ -10,30 +10,39 @@ void main() async {
await client.connectUser(
User(id: 'super-band-9'),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
'''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
);
runApp(MyApp(client));
runApp(
MyApp(
client: client,
),
);
}
class MyApp extends StatelessWidget {
const MyApp({
Key? key,
required this.client,
}) : super(key: key);
final StreamChatClient client;
MyApp(this.client);
@override
Widget build(BuildContext context) {
return MaterialApp(
builder: (context, child) => StreamChat(
client: client,
child: child,
),
home: SplitView(),
);
}
Widget build(BuildContext context) => MaterialApp(
builder: (context, child) => StreamChat(
client: client,
child: child,
),
home: const SplitView(),
);
}
class SplitView extends StatefulWidget {
const SplitView({
Key? key,
}) : super(key: key);
@override
_SplitViewState createState() => _SplitViewState();
}
@@ -42,69 +51,67 @@ class _SplitViewState extends State<SplitView> {
Channel? selectedChannel;
@override
Widget build(BuildContext context) {
return Flex(
direction: Axis.horizontal,
children: <Widget>[
Flexible(
flex: 1,
child: ChannelListPage(
onTap: (channel) {
setState(() {
selectedChannel = channel;
});
},
Widget build(BuildContext context) => Flex(
direction: Axis.horizontal,
children: <Widget>[
Flexible(
child: ChannelListPage(
onTap: (channel) {
setState(() {
selectedChannel = channel;
});
},
),
),
),
Flexible(
flex: 2,
child: Scaffold(
body: selectedChannel != null
? StreamChannel(
key: ValueKey(selectedChannel!.cid),
channel: selectedChannel!,
child: ChannelPage(),
)
: Center(
child: Text(
'Pick a channel to show the messages 💬',
style: Theme.of(context).textTheme.headline5,
Flexible(
flex: 2,
child: Scaffold(
body: selectedChannel != null
? StreamChannel(
key: ValueKey(selectedChannel!.cid),
channel: selectedChannel!,
child: const ChannelPage(),
)
: Center(
child: Text(
'Pick a channel to show the messages 💬',
style: Theme.of(context).textTheme.headline5,
),
),
),
),
),
),
],
);
}
],
);
}
class ChannelListPage extends StatelessWidget {
const ChannelListPage({
Key? key,
this.onTap,
}) : super(key: key);
final void Function(Channel)? onTap;
ChannelListPage({this.onTap});
@override
Widget build(BuildContext context) {
return Scaffold(
body: ChannelsBloc(
child: ChannelListView(
onChannelTap: onTap != null
? (channel, _) {
onTap!(channel);
}
: null,
filter: Filter.in_(
'members',
[StreamChat.of(context).user!.id],
),
sort: [SortOption('last_message_at')],
pagination: PaginationParams(
limit: 20,
Widget build(BuildContext context) => Scaffold(
body: ChannelsBloc(
child: ChannelListView(
onChannelTap: onTap != null
? (channel, _) {
onTap!(channel);
}
: null,
filter: Filter.in_(
'members',
[StreamChat.of(context).user!.id],
),
sort: const [SortOption('last_message_at')],
pagination: const PaginationParams(
limit: 20,
),
),
),
),
);
}
);
}
class ChannelPage extends StatelessWidget {
@@ -113,27 +120,21 @@ class ChannelPage extends StatelessWidget {
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Navigator(
onGenerateRoute: (settings) {
return MaterialPageRoute(
builder: (context) {
return Scaffold(
appBar: ChannelHeader(
showBackButton: false,
),
body: Column(
children: <Widget>[
Expanded(
child: MessageListView(),
),
MessageInput(),
],
),
);
},
);
},
);
}
Widget build(BuildContext context) => Navigator(
onGenerateRoute: (settings) => MaterialPageRoute(
builder: (context) => Scaffold(
appBar: const ChannelHeader(
showBackButton: false,
),
body: Column(
children: const <Widget>[
Expanded(
child: MessageListView(),
),
MessageInput(),
],
),
),
),
);
}
@@ -4,25 +4,30 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// First step of the [tutorial](https://getstream.io/chat/flutter/tutorial/)
///
/// There are three important things to notice that are common to all Flutter application using StreamChat:
/// There are three important things to notice that are common to all Flutter
/// application using StreamChat:
///
/// 1. The Dart API [StreamChatClient] is initialized with your API Key
/// 2. The current user is set by calling [StreamChatClient.connectUser]
/// 3. The client is then passed to the top-level [StreamChat] widget
/// [StreamChat] is an inherited widget and must be the parent of all Chat related widgets.
/// [StreamChat] is an inherited widget and must be the parent of all
/// Chat related widgets.
///
/// Please note that while Flutter can be used to build both mobile and web applications;
/// in this tutorial we focus on mobile, make sure when running the app you use a mobile device.
/// Please note that while Flutter can be used to build both mobile and web
/// applications, in this tutorial we focus on mobile. Make sure when running
/// the app that you use a mobile device.
///
/// Let's have a look at what we've built:
///
/// - We set up the Chat [StreamChatClient] with the API key
///
/// - We set the the current user for Chat with [StreamChatClient.connectUser] and a pre-generated user token
/// - We set the the current user for Chat with [StreamChatClient.connectUser]
/// and a pre-generated user token
///
/// - We make [StreamChat] the root Widget of our application
///
/// - We create a single [ChannelPage] widget under [StreamChat] with three widgets: [ChannelHeader], [MessageListView] and [MessageInput]
/// - We create a single [ChannelPage] widget under [StreamChat] with three
/// widgets: [ChannelHeader], [MessageListView] and [MessageInput]
///
/// If you now run the simulator you will see a single channel UI.
void main() async {
@@ -33,26 +38,38 @@ void main() async {
await client.connectUser(
User(id: 'super-band-9'),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
'''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
);
final channel = client.channel('messaging', id: 'godevs');
// ignore: unawaited_futures
// ignore: unawaited_futures, cascade_invocations
channel.watch();
runApp(MyApp(client, channel));
runApp(
MyApp(
client: client,
channel: channel,
),
);
}
class MyApp extends StatelessWidget {
const MyApp({
Key? key,
required this.client,
required this.channel,
}) : super(key: key);
final StreamChatClient client;
final Channel channel;
MyApp(this.client, this.channel);
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return MaterialApp(
// ignore: prefer_expression_function_bodies
builder: (context, widget) {
return StreamChat(
client: client,
@@ -61,7 +78,7 @@ class MyApp extends StatelessWidget {
},
home: StreamChannel(
channel: channel,
child: ChannelPage(),
child: const ChannelPage(),
),
);
}
@@ -73,11 +90,12 @@ class ChannelPage extends StatelessWidget {
}) : super(key: key);
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return Scaffold(
appBar: ChannelHeader(),
appBar: const ChannelHeader(),
body: Column(
children: <Widget>[
children: const <Widget>[
Expanded(
child: MessageListView(),
),
@@ -5,20 +5,29 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Second step of the [tutorial](https://getstream.io/chat/flutter/tutorial/)
///
/// Most chat applications handle more than just one single conversation.
/// Apps like Facebook Messenger, Whatsapp and Telegram allows you to have multiple one to one and group conversations.
/// Apps like Facebook Messenger, Whatsapp and Telegram allows you to have
/// multiple one-to-one and group conversations.
///
/// Lets find out how we can change our application chat screen to display the list of conversations and navigate between them.
/// Lets find out how we can change our application chat screen to display
/// the list of conversations and navigate between them.
///
/// > Note: the SDK uses Flutters [Navigator] to move from one route to another, this allows us to avoid any boiler-plate code.
/// > Of course you can take total control of how navigation works by customizing widgets like [Channel] and [ChannelList].
/// > Note: the SDK uses Flutters [Navigator] to move from one route to
/// another. This allows us to avoid any boiler-plate code.
/// > Of course, you can take total control of how navigation works by
/// customizing widgets like [Channel] and [ChannelList].
///
/// If you run the application, you will see that the first screen shows a list of conversations, you can open each by tapping and go back to the list.
/// If you run the application, you will see that the first screen shows a
/// list of conversations, you can open each by tapping and go back to the list.
///
/// Every single widget involved in this UI can be customized or swapped with your own.
/// Every single widget involved in this UI can be customized or swapped
/// with your own.
///
/// The [ChannelListPage] widget retrieves the list of channels based on a custom query and ordering.
/// In this case we are showing the list of channels the current user is a member and we order them based on the time they had a new message.
/// [ChannelListView] handles pagination and updates automatically out of the box when new channels are created or when a new message is added to a channel.
/// The [ChannelListPage] widget retrieves the list of channels based on a
/// custom query and ordering. In this case we are showing the list of
/// channels in which the current user is a member and we order them based
/// on the time they had a new message. [ChannelListView] handles pagination
/// and updates automatically when new channels are created or when a new
/// message is added to a channel.
void main() async {
final client = StreamChatClient(
's2dxdhpxd94g',
@@ -27,31 +36,44 @@ void main() async {
await client.connectUser(
User(id: 'super-band-9'),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
'''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
);
runApp(MyApp(client));
runApp(
MyApp(
client: client,
),
);
}
class MyApp extends StatelessWidget {
const MyApp({
Key? key,
required this.client,
}) : super(key: key);
final StreamChatClient client;
MyApp(this.client);
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return MaterialApp(
builder: (context, child) => StreamChat(
client: client,
child: child,
),
home: ChannelListPage(),
home: const ChannelListPage(),
);
}
}
class ChannelListPage extends StatelessWidget {
const ChannelListPage({
Key? key,
}) : super(key: key);
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return Scaffold(
body: ChannelsBloc(
@@ -60,11 +82,11 @@ class ChannelListPage extends StatelessWidget {
'members',
[StreamChat.of(context).user!.id],
),
sort: [SortOption('last_message_at')],
pagination: PaginationParams(
sort: const [SortOption('last_message_at')],
pagination: const PaginationParams(
limit: 20,
),
channelWidget: ChannelPage(),
channelWidget: const ChannelPage(),
),
),
);
@@ -77,11 +99,12 @@ class ChannelPage extends StatelessWidget {
}) : super(key: key);
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return Scaffold(
appBar: ChannelHeader(),
appBar: const ChannelHeader(),
body: Column(
children: <Widget>[
children: const <Widget>[
Expanded(
child: MessageListView(),
),
@@ -6,22 +6,30 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Third step of the [tutorial](https://getstream.io/chat/flutter/tutorial/)
///
/// So far youve learned how to use the default widgets.
/// The library has been designed with composition in mind and to allow all common customizations to be very easy.
/// This means that you can change any component in your application by swapping the default widgets with the ones you build yourself.
/// The library has been designed with composition in mind and to allow all
/// common customizations to be very easy.
/// This means that you can change any component in your application by
/// swapping the default widgets with the ones you build yourself.
///
/// Lets see how we can make some changes to the SDKs UI components.
/// We start by changing how channel previews are shown in the channel list and include the number of unread messages for each.
/// We start by changing how channel previews are shown in the channel list
/// and include the number of unread messages for each.
///
/// We're passing a custom widget to [ChannelListView.channelPreviewBuilder], this will override the default [ChannelPreview] and allows you to create one yourself.
/// We're passing a custom widget to [ChannelListView.channelPreviewBuilder];
/// this will override the default [ChannelPreview] and allows you to create
/// one yourself.
///
/// There are a couple interesting things we do in this widget:
///
/// - Instead of creating a whole new style for the channel name, we inherit the text style from the parent theme ([StreamChatTheme.of]) and only change the color attribute
/// - Instead of creating a whole new style for the channel name, we inherit
/// the text style from the parent theme ([StreamChatTheme.of]) and only
/// change the color attribute
///
/// - We loop over the list of channel messages to search for the first not deleted message ([Channel.state.messages])
/// - We loop over the list of channel messages to search for the first not
/// deleted message ([Channel.state.messages])
///
/// - We retrieve the count of unread messages from [Channel.state]
void main() async {
Future<void> main() async {
final client = StreamChatClient(
's2dxdhpxd94g',
logLevel: Level.INFO,
@@ -29,31 +37,44 @@ void main() async {
await client.connectUser(
User(id: 'super-band-9'),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
'''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
);
runApp(MyApp(client));
runApp(
MyApp(
client: client,
),
);
}
class MyApp extends StatelessWidget {
const MyApp({
Key? key,
required this.client,
}) : super(key: key);
final StreamChatClient client;
MyApp(this.client);
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return MaterialApp(
builder: (context, child) => StreamChat(
client: client,
child: child,
),
home: ChannelListPage(),
home: const ChannelListPage(),
);
}
}
class ChannelListPage extends StatelessWidget {
const ChannelListPage({
Key? key,
}) : super(key: key);
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return Scaffold(
body: ChannelsBloc(
@@ -64,10 +85,10 @@ class ChannelListPage extends StatelessWidget {
),
channelPreviewBuilder: _channelPreviewBuilder,
// sort: [SortOption('last_message_at')],
pagination: PaginationParams(
pagination: const PaginationParams(
limit: 20,
),
channelWidget: ChannelPage(),
channelWidget: const ChannelPage(),
),
),
);
@@ -78,7 +99,7 @@ class ChannelListPage extends StatelessWidget {
(message) => !message.isDeleted,
);
final subtitle = (lastMessage == null ? 'nothing yet' : lastMessage.text!);
final subtitle = lastMessage == null ? 'nothing yet' : lastMessage.text!;
final opacity = (channel.state?.unreadCount ?? 0) > 0 ? 1.0 : 0.5;
return ListTile(
@@ -88,12 +109,12 @@ class ChannelListPage extends StatelessWidget {
MaterialPageRoute(
builder: (_) => StreamChannel(
channel: channel,
child: ChannelPage(),
child: const ChannelPage(),
),
),
);
},
leading: ChannelImage(
leading: ChannelAvatar(
channel: channel,
),
title: ChannelName(
@@ -101,17 +122,17 @@ class ChannelListPage extends StatelessWidget {
StreamChatTheme.of(context).channelPreviewTheme.title!.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.textHighEmphasis
.withOpacity(opacity),
),
),
subtitle: Text(subtitle),
trailing: channel.state!.unreadCount! > 0
trailing: channel.state!.unreadCount > 0
? CircleAvatar(
radius: 10,
child: Text(channel.state!.unreadCount.toString()),
)
: SizedBox(),
: const SizedBox(),
);
}
}
@@ -122,11 +143,12 @@ class ChannelPage extends StatelessWidget {
}) : super(key: key);
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return Scaffold(
appBar: ChannelHeader(),
appBar: const ChannelHeader(),
body: Column(
children: <Widget>[
children: const <Widget>[
Expanded(
child: MessageListView(),
),
@@ -4,13 +4,17 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Fourth step of the [tutorial](https://getstream.io/chat/flutter/tutorial/)
///
/// Stream Chat supports message threads out of the box. Threads allows users to create sub-conversations inside the same channel.
/// Stream Chat supports message threads out of the box. Threads allows users
/// to create sub-conversations inside the same channel.
///
/// Using threaded conversations is very simple and mostly a matter of plugging the [MessageListView] to another widget that renders the widget.
/// To make this simple, such a widget only needs to build [MessageListView] with the parent attribute set to the threads root message.
/// Using threaded conversations is very simple and mostly a matter of
/// plugging the [MessageListView] to another widget that renders the widget.
/// To make this simple, such a widget only needs to build [MessageListView]
/// with the parent attribute set to the threads root message.
///
/// Now we can open threads and create new ones as well, if you long press a message you can tap on Reply and it will open the same [ThreadPage].
void main() async {
/// Now we can open threads and create new ones as well. If you long-press a
/// message, you can tap on "Reply" and it will open the same [ThreadPage].
Future<void> main() async {
final client = StreamChatClient(
's2dxdhpxd94g',
logLevel: Level.INFO,
@@ -18,33 +22,44 @@ void main() async {
await client.connectUser(
User(id: 'super-band-9'),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
'''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
);
runApp(MyApp(client));
runApp(
MyApp(
client: client,
),
);
}
class MyApp extends StatelessWidget {
const MyApp({
Key? key,
required this.client,
}) : super(key: key);
final StreamChatClient client;
MyApp(this.client);
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return MaterialApp(
builder: (context, child) => StreamChat(
client: client,
child: child,
),
home: Container(
child: ChannelListPage(),
),
home: const ChannelListPage(),
);
}
}
class ChannelListPage extends StatelessWidget {
const ChannelListPage({
Key? key,
}) : super(key: key);
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return Scaffold(
body: ChannelsBloc(
@@ -53,11 +68,11 @@ class ChannelListPage extends StatelessWidget {
'members',
[StreamChat.of(context).user!.id],
),
sort: [SortOption('last_message_at')],
pagination: PaginationParams(
sort: const [SortOption('last_message_at')],
pagination: const PaginationParams(
limit: 20,
),
channelWidget: ChannelPage(),
channelWidget: const ChannelPage(),
),
),
);
@@ -70,21 +85,20 @@ class ChannelPage extends StatelessWidget {
}) : super(key: key);
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return Scaffold(
appBar: ChannelHeader(),
appBar: const ChannelHeader(),
body: Column(
children: <Widget>[
Expanded(
child: MessageListView(
threadBuilder: (_, parentMessage) {
return ThreadPage(
parent: parentMessage,
);
},
threadBuilder: (_, parentMessage) => ThreadPage(
parent: parentMessage,
),
),
),
MessageInput(),
const MessageInput(),
],
),
);
@@ -92,14 +106,15 @@ class ChannelPage extends StatelessWidget {
}
class ThreadPage extends StatelessWidget {
final Message? parent;
ThreadPage({
const ThreadPage({
Key? key,
this.parent,
}) : super(key: key);
final Message? parent;
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return Scaffold(
appBar: ThreadHeader(
@@ -4,18 +4,23 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Fifth step of the [tutorial](https://getstream.io/chat/flutter/tutorial/)
///
/// Customizing how messages are rendered is another very common use-case that the SDK supports easily.
/// Customizing how messages are rendered is another very common use-case that
/// the SDK supports easily.
///
/// Replace the built-in message component with your own is done by passing it as a builder function to the [MessageListView] widget.
/// Replacing the built-in message component with your own is done by passing
/// it as a builder function to the [MessageListView] widget.
///
/// The message builder function will get the usual [BuildContext] argument as well as the [Message] object and its position inside the list.
/// The message builder function will get the usual [BuildContext] argument
/// as well as the [Message] object and its position inside the list.
///
/// If you look at the code you can see that we use [StreamChat.of] to retrieve the current user so that we can style messages own messages in a different way.
/// If you look at the code you can see that we use [StreamChat.of] to
/// retrieve the current user so that we can style messages in a different way.
///
/// Since custom widgets and builders are always children of [StreamChat] or part of a [Channel],
/// you can use [StreamChat.of], [StreamChannel.of] and [StreamChatTheme.of] to use the API client directly
/// or to retrieve outer scope needed such as messages from the [Channel.state].
void main() async {
/// Since custom widgets and builders are always children of [StreamChat] or
/// part of a [Channel], you can use [StreamChat.of], [StreamChannel.of],
/// and [StreamChatTheme.of] to use the API client directly or to retrieve
/// outer scope needed such as messages from the [Channel.state].
Future<void> main() async {
final client = StreamChatClient(
's2dxdhpxd94g',
logLevel: Level.INFO,
@@ -23,31 +28,44 @@ void main() async {
await client.connectUser(
User(id: 'super-band-9'),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
'''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
);
runApp(MyApp(client));
runApp(
MyApp(
client: client,
),
);
}
class MyApp extends StatelessWidget {
const MyApp({
Key? key,
required this.client,
}) : super(key: key);
final StreamChatClient client;
MyApp(this.client);
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return MaterialApp(
builder: (context, child) => StreamChat(
client: client,
child: child,
),
home: ChannelListPage(),
home: const ChannelListPage(),
);
}
}
class ChannelListPage extends StatelessWidget {
const ChannelListPage({
Key? key,
}) : super(key: key);
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return Scaffold(
body: ChannelsBloc(
@@ -56,11 +74,11 @@ class ChannelListPage extends StatelessWidget {
'members',
[StreamChat.of(context).user!.id],
),
sort: [SortOption('last_message_at')],
pagination: PaginationParams(
sort: const [SortOption('last_message_at')],
pagination: const PaginationParams(
limit: 20,
),
channelWidget: ChannelPage(),
channelWidget: const ChannelPage(),
),
),
);
@@ -73,9 +91,10 @@ class ChannelPage extends StatelessWidget {
}) : super(key: key);
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return Scaffold(
appBar: ChannelHeader(),
appBar: const ChannelHeader(),
body: Column(
children: <Widget>[
Expanded(
@@ -83,7 +102,7 @@ class ChannelPage extends StatelessWidget {
messageBuilder: _messageBuilder,
),
),
MessageInput(),
const MessageInput(),
],
),
);
@@ -101,12 +120,14 @@ class ChannelPage extends StatelessWidget {
final color = isCurrentUser ? Colors.blueGrey : Colors.blue;
return Padding(
padding: EdgeInsets.all(5.0),
padding: const EdgeInsets.all(5),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: color, width: 1),
borderRadius: BorderRadius.all(
Radius.circular(5.0),
border: Border.all(
color: color,
),
borderRadius: const BorderRadius.all(
Radius.circular(5),
),
),
child: ListTile(
@@ -115,7 +136,7 @@ class ChannelPage extends StatelessWidget {
textAlign: textAlign,
),
subtitle: Text(
message.user!.extraData['name'] as String,
message.user!.name,
textAlign: textAlign,
),
),
@@ -4,22 +4,29 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Sixth step of the [tutorial](https://getstream.io/chat/flutter/tutorial/)
///
/// The Flutter SDK comes with a fully designed set of widgets which you can customize to fit with your application style and typography.
/// Changing the theme of Chat widgets works in a very similar way that [MaterialApp] and [Theme] do.
/// The Flutter SDK comes with a fully designed set of widgets which you can
/// customize to fit with your application style and typography.
/// Changing the theme of Chat widgets works in a very similar way that
/// [MaterialApp] and [Theme] do.
///
/// Out of the box all chat widgets use their own default styling, there are two ways to change the styling:
/// All chat widgets use their own default styling out of the box. There are
/// two ways to change the styling:
///
/// 1. Initialize the [StreamChatTheme] from your existing [MaterialApp] style
/// 2. Construct a custom theme and provide all the customizations needed
///
/// First we create a new Material [Theme] and pick [Colors.green] as swatch color. The theme is then passed to [MaterialApp] as usual.
/// First, we create a new Material [Theme] and pick [Colors.green] as the
/// swatch color. The theme is then passed to [MaterialApp] as usual.
///
/// Then we create a new [StreamChatTheme] from the green theme we just created.
/// After saving the app you will see the UI will update several widgets to match with the new color.
/// Then, we create a new [StreamChatTheme] from the green theme we just
/// created. After saving the app you will see that several widgets have
/// been updated with the new color.
///
/// We also change the message color posted by the current user.
/// You can perform these more granular style changes using [StreamChatTheme.copyWith].
void main() async {
///
/// You can perform these more granular style changes using
/// [StreamChatTheme.copyWith].
Future<void> main() async {
final client = StreamChatClient(
's2dxdhpxd94g',
logLevel: Level.INFO,
@@ -27,16 +34,23 @@ void main() async {
await client.connectUser(
User(id: 'super-band-9'),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
'''eyJ0eXAiO«iJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
);
runApp(MyApp(client));
runApp(
MyApp(
client: client,
),
);
}
class MyApp extends StatelessWidget {
final StreamChatClient client;
const MyApp({
Key? key,
required this.client,
}) : super(key: key);
MyApp(this.client);
final StreamChatClient client;
@override
Widget build(BuildContext context) {
@@ -50,9 +64,9 @@ class MyApp extends StatelessWidget {
),
),
otherMessageTheme: MessageTheme(
messageBackgroundColor: colorTheme.black,
messageBackgroundColor: colorTheme.textHighEmphasis,
messageText: TextStyle(
color: colorTheme.white,
color: colorTheme.barsBg,
),
avatarTheme: AvatarTheme(
borderRadius: BorderRadius.circular(8),
@@ -62,20 +76,23 @@ class MyApp extends StatelessWidget {
return MaterialApp(
theme: themeData,
builder: (context, child) {
return StreamChat(
client: client,
streamChatThemeData: customTheme,
child: child,
);
},
home: ChannelListPage(),
builder: (context, child) => StreamChat(
client: client,
streamChatThemeData: customTheme,
child: child,
),
home: const ChannelListPage(),
);
}
}
class ChannelListPage extends StatelessWidget {
const ChannelListPage({
Key? key,
}) : super(key: key);
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return Scaffold(
body: ChannelsBloc(
@@ -84,11 +101,11 @@ class ChannelListPage extends StatelessWidget {
'members',
[StreamChat.of(context).user!.id],
),
sort: [SortOption('last_message_at')],
pagination: PaginationParams(
sort: const [SortOption('last_message_at')],
pagination: const PaginationParams(
limit: 20,
),
channelWidget: ChannelPage(),
channelWidget: const ChannelPage(),
),
),
);
@@ -101,21 +118,20 @@ class ChannelPage extends StatelessWidget {
}) : super(key: key);
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return Scaffold(
appBar: ChannelHeader(),
appBar: const ChannelHeader(),
body: Column(
children: <Widget>[
Expanded(
child: MessageListView(
threadBuilder: (_, parentMessage) {
return ThreadPage(
parent: parentMessage,
);
},
threadBuilder: (_, parentMessage) => ThreadPage(
parent: parentMessage,
),
),
),
MessageInput(),
const MessageInput(),
],
),
);
@@ -123,14 +139,15 @@ class ChannelPage extends StatelessWidget {
}
class ThreadPage extends StatelessWidget {
final Message? parent;
ThreadPage({
const ThreadPage({
Key? key,
this.parent,
}) : super(key: key);
final Message? parent;
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return Scaffold(
appBar: ThreadHeader(
@@ -21,23 +21,23 @@ environment:
sdk: '>=2.12.0 <3.0.0'
dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
collection: ^1.15.0
cupertino_icons: ^1.0.3
flutter:
sdk: flutter
# stream_chat:
# path: ../../stream_chat
# stream_chat_flutter_core:
# path: ../../stream_chat_flutter_core
# stream_chat:
# path: ../../stream_chat
# stream_chat_flutter_core:
# path: ../../stream_chat_flutter_core
stream_chat_flutter:
path: ../
stream_chat_localizations:
path: ../../stream_chat_localizations
stream_chat_persistence:
stream_chat_persistence:
path: ../../stream_chat_persistence
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
collection: ^1.15.0
dev_dependencies:
flutter_test:
@@ -35,7 +35,7 @@ class AttachmentTitle extends StatelessWidget {
attachment.title!,
overflow: TextOverflow.ellipsis,
style: messageTheme.messageText?.copyWith(
color: StreamChatTheme.of(context).colorTheme.accentBlue,
color: StreamChatTheme.of(context).colorTheme.accentPrimary,
fontWeight: FontWeight.bold,
),
),
@@ -130,7 +130,7 @@ class _PreparingState extends StatelessWidget {
alignment: Alignment.topRight,
child: _IconButton(
icon: StreamSvgIcon.close(
color: StreamChatTheme.of(context).colorTheme.white,
color: StreamChatTheme.of(context).colorTheme.barsBg,
),
onPressed: () => channel.cancelAttachmentUpload(attachmentId),
),
@@ -170,7 +170,7 @@ class _InProgressState extends StatelessWidget {
alignment: Alignment.topRight,
child: _IconButton(
icon: StreamSvgIcon.close(
color: StreamChatTheme.of(context).colorTheme.white,
color: StreamChatTheme.of(context).colorTheme.barsBg,
),
onPressed: () => channel.cancelAttachmentUpload(attachmentId),
),
@@ -209,7 +209,7 @@ class _FailedState extends StatelessWidget {
children: [
_IconButton(
icon: StreamSvgIcon.retry(
color: theme.colorTheme.white,
color: theme.colorTheme.barsBg,
),
onPressed: () {
channel.retryAttachmentUpload(messageId, attachmentId);
@@ -229,7 +229,7 @@ class _FailedState extends StatelessWidget {
child: Text(
context.translations.uploadErrorLabel,
style: theme.textTheme.footnote.copyWith(
color: theme.colorTheme.white,
color: theme.colorTheme.barsBg,
),
),
),
@@ -248,7 +248,7 @@ class _SuccessState extends StatelessWidget {
backgroundColor: StreamChatTheme.of(context).colorTheme.overlayDark,
maxRadius: 12,
child: StreamSvgIcon.check(
color: StreamChatTheme.of(context).colorTheme.white,
color: StreamChatTheme.of(context).colorTheme.barsBg,
),
),
);
@@ -75,12 +75,14 @@ class AttachmentError extends StatelessWidget {
child: Container(
width: size?.width,
height: size?.height,
color:
StreamChatTheme.of(context).colorTheme.accentRed.withOpacity(.1),
color: StreamChatTheme.of(context)
.colorTheme
.accentError
.withOpacity(.1),
child: Center(
child: Icon(
Icons.error_outline,
color: StreamChatTheme.of(context).colorTheme.black,
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis,
),
),
),
@@ -54,10 +54,10 @@ class FileAttachment extends AttachmentWidget {
width: size?.width ?? 100,
height: 56,
decoration: BoxDecoration(
color: colorTheme.white,
color: colorTheme.barsBg,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: colorTheme.greyWhisper,
color: colorTheme.borders,
),
),
child: Row(
@@ -141,8 +141,8 @@ class FileAttachment extends AttachmentWidget {
final colorTheme = StreamChatTheme.of(context).colorTheme;
return Shimmer.fromColors(
baseColor: colorTheme.greyGainsboro,
highlightColor: colorTheme.whiteSmoke,
baseColor: colorTheme.disabled,
highlightColor: colorTheme.inputBg,
child: image,
);
},
@@ -217,7 +217,7 @@ class FileAttachment extends AttachmentWidget {
preparing: () => Padding(
padding: const EdgeInsets.all(8),
child: _buildButton(
icon: StreamSvgIcon.close(color: theme.colorTheme.white),
icon: StreamSvgIcon.close(color: theme.colorTheme.barsBg),
fillColor: theme.colorTheme.overlayDark,
onPressed: () => channel.cancelAttachmentUpload(attachmentId),
),
@@ -225,7 +225,7 @@ class FileAttachment extends AttachmentWidget {
inProgress: (_, __) => Padding(
padding: const EdgeInsets.all(8),
child: _buildButton(
icon: StreamSvgIcon.close(color: theme.colorTheme.white),
icon: StreamSvgIcon.close(color: theme.colorTheme.barsBg),
fillColor: theme.colorTheme.overlayDark,
onPressed: () => channel.cancelAttachmentUpload(attachmentId),
),
@@ -233,15 +233,15 @@ class FileAttachment extends AttachmentWidget {
success: () => Padding(
padding: const EdgeInsets.all(8),
child: CircleAvatar(
backgroundColor: theme.colorTheme.accentBlue,
backgroundColor: theme.colorTheme.accentPrimary,
maxRadius: 12,
child: StreamSvgIcon.check(color: theme.colorTheme.white),
child: StreamSvgIcon.check(color: theme.colorTheme.barsBg),
),
),
failed: (_) => Padding(
padding: const EdgeInsets.all(8),
child: _buildButton(
icon: StreamSvgIcon.retry(color: theme.colorTheme.white),
icon: StreamSvgIcon.retry(color: theme.colorTheme.barsBg),
fillColor: theme.colorTheme.overlayDark,
onPressed: () => channel.retryAttachmentUpload(
message.id,
@@ -253,7 +253,8 @@ class FileAttachment extends AttachmentWidget {
if (message.status == MessageSendingStatus.sent) {
trailingWidget = IconButton(
icon: StreamSvgIcon.cloudDownload(color: theme.colorTheme.black),
icon: StreamSvgIcon.cloudDownload(
color: theme.colorTheme.textHighEmphasis),
visualDensity: VisualDensity.compact,
splashRadius: 16,
onPressed: () {
@@ -272,7 +273,7 @@ class FileAttachment extends AttachmentWidget {
final theme = StreamChatTheme.of(context);
final size = attachment.file?.size ?? attachment.extraData['file_size'];
final textStyle = theme.textTheme.footnote.copyWith(
color: theme.colorTheme.grey,
color: theme.colorTheme.textLowEmphasis,
);
return attachment.uploadState.when(
preparing: () => Text(fileSize(size), style: textStyle),
@@ -282,7 +283,7 @@ class FileAttachment extends AttachmentWidget {
showBackground: false,
padding: EdgeInsets.zero,
textStyle: textStyle,
progressIndicatorColor: theme.colorTheme.accentBlue,
progressIndicatorColor: theme.colorTheme.accentPrimary,
),
success: () => Text(fileSize(size), style: textStyle),
failed: (_) => Text(
@@ -52,7 +52,7 @@ class GiphyAttachment extends AttachmentWidget {
mainAxisSize: MainAxisSize.min,
children: [
Card(
color: StreamChatTheme.of(context).colorTheme.white,
color: StreamChatTheme.of(context).colorTheme.barsBg,
elevation: 2,
clipBehavior: Clip.hardEdge,
shape: const RoundedRectangleBorder(
@@ -84,7 +84,7 @@ class GiphyAttachment extends AttachmentWidget {
style: TextStyle(
color: StreamChatTheme.of(context)
.colorTheme
.black
.textHighEmphasis
.withOpacity(0.5),
),
overflow: TextOverflow.ellipsis,
@@ -118,7 +118,7 @@ class GiphyAttachment extends AttachmentWidget {
Container(
color: StreamChatTheme.of(context)
.colorTheme
.black
.textHighEmphasis
.withOpacity(0.2),
width: double.infinity,
height: 0.5,
@@ -142,7 +142,7 @@ class GiphyAttachment extends AttachmentWidget {
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.textHighEmphasis
.withOpacity(0.5),
),
),
@@ -153,7 +153,7 @@ class GiphyAttachment extends AttachmentWidget {
width: 0.5,
color: StreamChatTheme.of(context)
.colorTheme
.black
.textHighEmphasis
.withOpacity(0.2),
height: 50,
),
@@ -174,7 +174,7 @@ class GiphyAttachment extends AttachmentWidget {
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.textHighEmphasis
.withOpacity(0.5),
),
maxLines: 1,
@@ -186,7 +186,7 @@ class GiphyAttachment extends AttachmentWidget {
width: 0.5,
color: StreamChatTheme.of(context)
.colorTheme
.black
.textHighEmphasis
.withOpacity(0.2),
height: 50,
),
@@ -204,7 +204,7 @@ class GiphyAttachment extends AttachmentWidget {
style: TextStyle(
color: StreamChatTheme.of(context)
.colorTheme
.accentBlue,
.accentPrimary,
fontWeight: FontWeight.bold,
),
),
@@ -227,7 +227,7 @@ class GiphyAttachment extends AttachmentWidget {
StreamSvgIcon.eye(
color: StreamChatTheme.of(context)
.colorTheme
.black
.textHighEmphasis
.withOpacity(0.5),
size: 16,
),
@@ -242,7 +242,7 @@ class GiphyAttachment extends AttachmentWidget {
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.textHighEmphasis
.withOpacity(0.5)),
),
],
@@ -307,8 +307,8 @@ class GiphyAttachment extends AttachmentWidget {
final colorTheme = StreamChatTheme.of(context).colorTheme;
return Shimmer.fromColors(
baseColor: colorTheme.greyGainsboro,
highlightColor: colorTheme.whiteSmoke,
baseColor: colorTheme.disabled,
highlightColor: colorTheme.inputBg,
child: image,
);
},
@@ -323,7 +323,7 @@ class GiphyAttachment extends AttachmentWidget {
child: Material(
color: StreamChatTheme.of(context)
.colorTheme
.black
.textHighEmphasis
.withOpacity(.5),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
@@ -336,13 +336,14 @@ class GiphyAttachment extends AttachmentWidget {
child: Row(
children: [
StreamSvgIcon.lightning(
color: StreamChatTheme.of(context).colorTheme.white,
color: StreamChatTheme.of(context).colorTheme.barsBg,
size: 16,
),
Text(
context.translations.giphyLabel.toUpperCase(),
style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.white,
color:
StreamChatTheme.of(context).colorTheme.barsBg,
fontWeight: FontWeight.bold,
fontSize: 11,
),
@@ -103,8 +103,8 @@ class ImageAttachment extends AttachmentWidget {
);
final colorTheme = StreamChatTheme.of(context).colorTheme;
return Shimmer.fromColors(
baseColor: colorTheme.greyGainsboro,
highlightColor: colorTheme.whiteSmoke,
baseColor: colorTheme.disabled,
highlightColor: colorTheme.inputBg,
child: image,
);
},
@@ -72,7 +72,7 @@ class AttachmentActionsModal extends StatelessWidget {
context.translations.replyLabel,
StreamSvgIcon.iconCurveLineLeftUp(
size: 24,
color: theme.colorTheme.grey,
color: theme.colorTheme.textLowEmphasis,
),
() {
Navigator.pop(context, ReturnActionType.reply);
@@ -83,7 +83,7 @@ class AttachmentActionsModal extends StatelessWidget {
context.translations.showInChatLabel,
StreamSvgIcon.eye(
size: 24,
color: theme.colorTheme.black,
color: theme.colorTheme.textHighEmphasis,
),
onShowMessage,
),
@@ -94,7 +94,7 @@ class AttachmentActionsModal extends StatelessWidget {
: context.translations.saveImageLabel,
StreamSvgIcon.iconSave(
size: 24,
color: theme.colorTheme.grey,
color: theme.colorTheme.textLowEmphasis,
),
() {
final attachment = message.attachments[currentIndex];
@@ -145,7 +145,7 @@ class AttachmentActionsModal extends StatelessWidget {
context.translations.deleteLabel,
StreamSvgIcon.delete(
size: 24,
color: theme.colorTheme.accentRed,
color: theme.colorTheme.accentError,
),
() {
final channel = StreamChannel.of(context).channel;
@@ -166,7 +166,7 @@ class AttachmentActionsModal extends StatelessWidget {
..maybePop();
}
},
color: theme.colorTheme.accentRed,
color: theme.colorTheme.accentError,
),
]
.map<Widget>((e) => Align(
@@ -176,7 +176,7 @@ class AttachmentActionsModal extends StatelessWidget {
.insertBetween(
Container(
height: 1,
color: theme.colorTheme.greyWhisper,
color: theme.colorTheme.borders,
),
),
),
@@ -197,7 +197,7 @@ class AttachmentActionsModal extends StatelessWidget {
}) =>
Material(
key: key,
color: StreamChatTheme.of(context).colorTheme.white,
color: StreamChatTheme.of(context).colorTheme.barsBg,
child: InkWell(
onTap: onTap,
child: Padding(
@@ -242,7 +242,7 @@ class AttachmentActionsModal extends StatelessWidget {
width: 182,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: theme.colorTheme.white,
color: theme.colorTheme.barsBg,
),
child: Center(
child: progress == null
@@ -250,7 +250,7 @@ class AttachmentActionsModal extends StatelessWidget {
height: 100,
width: 100,
child: StreamSvgIcon.error(
color: theme.colorTheme.greyGainsboro,
color: theme.colorTheme.disabled,
),
)
: progress.toProgressIndicatorValue == 1.0
@@ -259,7 +259,7 @@ class AttachmentActionsModal extends StatelessWidget {
height: 160,
width: 160,
child: StreamSvgIcon.check(
color: theme.colorTheme.greyGainsboro,
color: theme.colorTheme.disabled,
),
)
: SizedBox(
@@ -272,14 +272,14 @@ class AttachmentActionsModal extends StatelessWidget {
value: progress.toProgressIndicatorValue,
strokeWidth: 8,
valueColor: AlwaysStoppedAnimation(
theme.colorTheme.accentBlue,
theme.colorTheme.accentPrimary,
),
),
Center(
child: Text(
'${progress.toPercentage}%',
style: theme.textTheme.headline.copyWith(
color: theme.colorTheme.grey,
color: theme.colorTheme.textLowEmphasis,
),
),
),
@@ -44,7 +44,7 @@ class StreamBackButton extends StatelessWidget {
padding: const EdgeInsets.all(14),
child: StreamSvgIcon.left(
size: 24,
color: StreamChatTheme.of(context).colorTheme.black,
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis,
),
),
if (showUnreads)
@@ -0,0 +1,202 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/group_avatar.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_image.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_image_paint.png)
///
/// It shows the current [Channel] image.
///
/// ```dart
/// class MyApp extends StatelessWidget {
/// final StreamChatClient client;
/// final Channel channel;
///
/// MyApp(this.client, this.channel);
///
/// @override
/// Widget build(BuildContext context) {
/// return MaterialApp(
/// debugShowCheckedModeBanner: false,
/// home: StreamChat(
/// client: client,
/// child: StreamChannel(
/// channel: channel,
/// child: Center(
/// child: ChannelImage(
/// channel: channel,
/// ),
/// ),
/// ),
/// ),
/// );
/// }
/// }
/// ```
///
/// The widget uses a [StreamBuilder] to render the channel information
/// image as soon as it updates.
///
/// By default the widget radius size is 40x40 pixels.
/// Set the property [constraints] to set a custom dimension.
///
/// The widget renders the ui based on the first ancestor of type
/// [StreamChatTheme].
/// Modify it to change the widget appearance.
class ChannelAvatar extends StatelessWidget {
/// Instantiate a new ChannelImage
const ChannelAvatar({
Key? key,
this.channel,
this.constraints,
this.onTap,
this.borderRadius,
this.selected = false,
this.selectionColor,
this.selectionThickness = 4,
}) : super(key: key);
/// [BorderRadius] to display the widget
final BorderRadius? borderRadius;
/// The channel to show the image of
final Channel? channel;
/// The diameter of the image
final BoxConstraints? constraints;
/// The function called when the image is tapped
final VoidCallback? onTap;
/// If image is selected
final bool selected;
/// Selection color for image
final Color? selectionColor;
/// Thickness of selection image
final double selectionThickness;
@override
Widget build(BuildContext context) {
final streamChat = StreamChat.of(context);
final channel = this.channel ?? StreamChannel.of(context).channel;
assert(channel.state != null, 'Channel ${channel.id} is not initialized');
final chatThemeData = StreamChatTheme.of(context);
final colorTheme = chatThemeData.colorTheme;
final previewTheme = chatThemeData.channelPreviewTheme.avatarTheme;
return BetterStreamBuilder<Map<String, dynamic>>(
stream: channel.extraDataStream,
initialData: channel.extraData,
builder: (context, extraData) {
final channelImage = extraData['image'];
if (channelImage != null) {
Widget child = ClipRRect(
borderRadius: borderRadius ?? previewTheme?.borderRadius,
child: Container(
constraints: constraints ?? previewTheme?.constraints,
decoration: BoxDecoration(color: colorTheme.accentPrimary),
child: InkWell(
onTap: onTap,
child: CachedNetworkImage(
imageUrl: channelImage,
errorWidget: (_, __, ___) => Center(
child: Text(
extraData['name']?[0] ?? '',
style: TextStyle(
color: colorTheme.barsBg,
fontWeight: FontWeight.bold,
),
),
),
fit: BoxFit.cover,
),
),
),
);
if (selected) {
child = ClipRRect(
key: const Key('selectedImage'),
borderRadius: BorderRadius.circular(selectionThickness) +
(borderRadius ??
previewTheme?.borderRadius ??
BorderRadius.zero),
child: Container(
constraints: constraints ?? previewTheme?.constraints,
color: selectionColor ?? colorTheme.accentPrimary,
child: Padding(
padding: EdgeInsets.all(selectionThickness),
child: child,
),
),
);
}
return child;
}
final currentUser = streamChat.user!;
final otherMembers = channel.state!.members
.where((it) => it.userId != currentUser.id)
.toList(growable: false);
// our own space, no other members
if (otherMembers.isEmpty) {
return BetterStreamBuilder<User>(
stream: streamChat.client.state.userStream.map((it) => it!),
initialData: currentUser,
builder: (context, user) => UserAvatar(
borderRadius: borderRadius ?? previewTheme?.borderRadius,
user: user,
constraints: constraints ?? previewTheme?.constraints,
onTap: onTap != null ? (_) => onTap!() : null,
selected: selected,
selectionColor: selectionColor ?? colorTheme.accentPrimary,
selectionThickness: selectionThickness,
),
);
}
// 1-1 Conversation
if (otherMembers.length == 1) {
final member = otherMembers.first;
return BetterStreamBuilder<Member>(
stream: channel.state!.membersStream.map(
(members) => members.firstWhere(
(it) => it.userId == member.userId,
orElse: () => member,
),
),
initialData: member,
builder: (context, member) => UserAvatar(
borderRadius: borderRadius ?? previewTheme?.borderRadius,
user: member.user!,
constraints: constraints ?? previewTheme?.constraints,
onTap: onTap != null ? (_) => onTap!() : null,
selected: selected,
selectionColor: selectionColor ?? colorTheme.accentPrimary,
selectionThickness: selectionThickness,
),
);
}
// Group conversation
return GroupAvatar(
members: otherMembers,
borderRadius: borderRadius ?? previewTheme?.borderRadius,
constraints: constraints ?? previewTheme?.constraints,
onTap: onTap,
selected: selected,
selectionColor: selectionColor ?? colorTheme.accentPrimary,
selectionThickness: selectionThickness,
);
},
);
}
}
@@ -33,7 +33,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
final isOwner = userAsMember.role == 'owner';
return Material(
color: _streamChatThemeData.colorTheme.white,
color: _streamChatThemeData.colorTheme.barsBg,
clipBehavior: Clip.antiAlias,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
@@ -147,7 +147,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: StreamSvgIcon.user(
color: _streamChatThemeData.colorTheme.grey,
color: _streamChatThemeData.colorTheme.textLowEmphasis,
),
),
title: context.translations.viewInfoLabel,
@@ -158,7 +158,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: StreamSvgIcon.userRemove(
color: _streamChatThemeData.colorTheme.grey,
color: _streamChatThemeData.colorTheme.textLowEmphasis,
),
),
title: context.translations.leaveGroupLabel,
@@ -177,11 +177,11 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: StreamSvgIcon.delete(
color: _streamChatThemeData.colorTheme.accentRed,
color: _streamChatThemeData.colorTheme.accentError,
),
),
title: context.translations.deleteConversationLabel,
titleColor: _streamChatThemeData.colorTheme.accentRed,
titleColor: _streamChatThemeData.colorTheme.accentError,
onTap: () async {
setState(() {
_showActions = false;
@@ -196,7 +196,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: StreamSvgIcon.closeSmall(
color: _streamChatThemeData.colorTheme.grey,
color: _streamChatThemeData.colorTheme.textLowEmphasis,
),
),
title: context.translations.cancelLabel,
@@ -225,7 +225,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
question: context.translations.deleteConversationQuestion,
cancelText: context.translations.cancelLabel,
icon: StreamSvgIcon.delete(
color: _streamChatThemeData.colorTheme.accentRed,
color: _streamChatThemeData.colorTheme.accentError,
),
);
final channel = _streamChannelState.channel;
@@ -243,7 +243,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
question: context.translations.leaveConversationQuestion,
cancelText: context.translations.cancelLabel,
icon: StreamSvgIcon.userRemove(
color: _streamChatThemeData.colorTheme.accentRed,
color: _streamChatThemeData.colorTheme.accentError,
),
);
if (res == true) {
@@ -99,7 +99,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
final Widget? leading;
/// AppBar actions
/// By default it shows the [ChannelImage]
/// By default it shows the [ChannelAvatar]
final List<Widget>? actions;
@override
@@ -148,7 +148,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
Padding(
padding: const EdgeInsets.only(right: 10),
child: Center(
child: ChannelImage(
child: ChannelAvatar(
borderRadius: chatThemeData.channelTheme
.channelHeaderTheme.avatarTheme?.borderRadius,
constraints: chatThemeData.channelTheme
@@ -1,204 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/group_image.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_image.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_image_paint.png)
///
/// It shows the current [Channel] image.
///
/// ```dart
/// class MyApp extends StatelessWidget {
/// final StreamChatClient client;
/// final Channel channel;
///
/// MyApp(this.client, this.channel);
///
/// @override
/// Widget build(BuildContext context) {
/// return MaterialApp(
/// debugShowCheckedModeBanner: false,
/// home: StreamChat(
/// client: client,
/// child: StreamChannel(
/// channel: channel,
/// child: Center(
/// child: ChannelImage(
/// channel: channel,
/// ),
/// ),
/// ),
/// ),
/// );
/// }
/// }
/// ```
///
/// The widget uses a [StreamBuilder] to render the channel information
/// image as soon as it updates.
///
/// By default the widget radius size is 40x40 pixels.
/// Set the property [constraints] to set a custom dimension.
///
/// The widget renders the ui based on the first ancestor of type
/// [StreamChatTheme].
/// Modify it to change the widget appearance.
class ChannelImage extends StatelessWidget {
/// Instantiate a new ChannelImage
const ChannelImage({
Key? key,
this.channel,
this.constraints,
this.onTap,
this.borderRadius,
this.selected = false,
this.selectionColor,
this.selectionThickness = 4,
}) : super(key: key);
/// [BorderRadius] to display the widget
final BorderRadius? borderRadius;
/// The channel to show the image of
final Channel? channel;
/// The diameter of the image
final BoxConstraints? constraints;
/// The function called when the image is tapped
final VoidCallback? onTap;
/// If image is selected
final bool selected;
/// Selection color for image
final Color? selectionColor;
/// Thickness of selection image
final double selectionThickness;
@override
Widget build(BuildContext context) {
final streamChat = StreamChat.of(context);
final channel = this.channel ?? StreamChannel.of(context).channel;
return BetterStreamBuilder<Map<String, dynamic>>(
stream: channel.extraDataStream,
initialData: channel.extraData,
builder: (context, data) {
String? image;
final chatThemeData = StreamChatTheme.of(context);
if (data.containsKey('image') == true) {
image = data['image'];
} else if (channel.state?.members.length == 2) {
final otherMember = channel.state?.members
.firstWhere((member) => member.user?.id != streamChat.user?.id);
return BetterStreamBuilder<User?>(
stream: streamChat.client.state.usersStream
.map((users) =>
users[otherMember?.userId] ?? otherMember!.user!)
.distinct(),
initialData: otherMember!.user,
builder: (context, user) => UserAvatar(
borderRadius: borderRadius ??
chatThemeData
.channelPreviewTheme.avatarTheme?.borderRadius,
user: user ?? otherMember.user!,
constraints: constraints ??
chatThemeData
.channelPreviewTheme.avatarTheme?.constraints,
onTap: onTap != null ? (_) => onTap!() : null,
selected: selected,
selectionColor:
selectionColor ?? chatThemeData.colorTheme.accentBlue,
selectionThickness: selectionThickness,
));
} else {
final images = channel.state?.members
.where((member) =>
member.user?.id != streamChat.user?.id &&
member.user?.extraData['image'] != null)
.take(4)
// ignore: cast_nullable_to_non_nullable
.map((e) => e.user?.extraData['image'] as String)
.toList();
return GroupImage(
images: images ?? [],
borderRadius: borderRadius ??
chatThemeData.channelPreviewTheme.avatarTheme?.borderRadius,
constraints: constraints ??
chatThemeData.channelPreviewTheme.avatarTheme?.constraints,
onTap: onTap,
selected: selected,
selectionColor:
selectionColor ?? chatThemeData.colorTheme.accentBlue,
selectionThickness: selectionThickness,
);
}
Widget child = ClipRRect(
borderRadius: borderRadius ??
chatThemeData.channelPreviewTheme.avatarTheme?.borderRadius,
child: Container(
constraints: constraints ??
chatThemeData.channelPreviewTheme.avatarTheme?.constraints,
decoration: BoxDecoration(
color: chatThemeData.colorTheme.accentBlue,
),
child: Stack(
alignment: Alignment.center,
fit: StackFit.expand,
children: <Widget>[
if (image != null)
CachedNetworkImage(
imageUrl: image,
errorWidget: (_, __, ___) => Center(
child: Text(
data.containsKey('name') ? data['name'][0] : '',
style: TextStyle(
color: chatThemeData.colorTheme.white,
fontWeight: FontWeight.bold,
),
),
),
fit: BoxFit.cover,
)
else
chatThemeData.defaultChannelImage(
context,
channel,
),
Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
),
),
],
),
),
);
if (selected) {
child = ClipRRect(
key: const Key('selectedImage'),
borderRadius: (borderRadius ??
chatThemeData.ownMessageTheme.avatarTheme?.borderRadius ??
BorderRadius.zero) +
BorderRadius.circular(selectionThickness),
child: Container(
constraints: constraints ??
chatThemeData.ownMessageTheme.avatarTheme?.constraints,
color: selectionColor ?? chatThemeData.colorTheme.accentBlue,
child: Padding(
padding: EdgeInsets.all(selectionThickness),
child: child,
),
),
);
}
return child;
},
);
}
}
@@ -148,7 +148,7 @@ class ChannelInfo extends StatelessWidget {
child: Text(
context.translations.tryAgainLabel,
style: textStyle?.copyWith(
color: StreamChatTheme.of(context).colorTheme.accentBlue,
color: StreamChatTheme.of(context).colorTheme.accentPrimary,
),
),
),
@@ -154,7 +154,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
Color? color;
switch (status) {
case ConnectionStatus.connected:
color = chatThemeData.colorTheme.accentBlue;
color = chatThemeData.colorTheme.accentPrimary;
break;
case ConnectionStatus.connecting:
color = Colors.grey;
@@ -209,7 +209,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
return Text(
context.translations.streamChatLabel,
style: chatThemeData.textTheme.headlineBold.copyWith(
color: chatThemeData.colorTheme.black,
color: chatThemeData.colorTheme.textHighEmphasis,
),
);
}
@@ -262,7 +262,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
style: chatThemeData.channelListHeaderTheme.title?.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
color: chatThemeData.colorTheme.accentBlue,
color: chatThemeData.colorTheme.accentPrimary,
),
),
),
@@ -239,39 +239,32 @@ class _ChannelListViewState extends State<ChannelListView> {
}
Widget _buildListView(BuildContext context, List<Channel> channels) {
late Widget child;
if (channels.isNotEmpty) {
if (widget.crossAxisCount > 1) {
child = GridView.builder(
padding: widget.padding,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: widget.crossAxisCount,
),
itemCount: channels.length,
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) =>
_gridItemBuilder(context, index, channels),
);
} else {
child = ListView.separated(
padding: widget.padding,
physics: const AlwaysScrollableScrollPhysics(),
itemCount:
channels.isNotEmpty ? channels.length + 1 : channels.length,
separatorBuilder: (_, index) {
if (widget.separatorBuilder != null) {
return widget.separatorBuilder!(context, index);
}
return _separatorBuilder(context, index);
},
itemBuilder: (context, index) =>
_listItemBuilder(context, index, channels),
);
}
if (widget.crossAxisCount > 1) {
return GridView.builder(
padding: widget.padding,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: widget.crossAxisCount,
),
itemCount: channels.length,
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) =>
_gridItemBuilder(context, index, channels),
);
}
return child;
return ListView.separated(
padding: widget.padding,
physics: const AlwaysScrollableScrollPhysics(),
// all channels + progress loader
itemCount: channels.length + 1,
separatorBuilder: (_, index) {
if (widget.separatorBuilder != null) {
return widget.separatorBuilder!(context, index);
}
return _separatorBuilder(context, index);
},
itemBuilder: (context, index) =>
_listItemBuilder(context, index, channels),
);
}
Widget _buildEmptyWidget(BuildContext context) => LayoutBuilder(
@@ -292,7 +285,7 @@ class _ChannelListViewState extends State<ChannelListView> {
padding: const EdgeInsets.all(8),
child: StreamSvgIcon.message(
size: 136,
color: chatThemeData.colorTheme.greyGainsboro,
color: chatThemeData.colorTheme.disabled,
),
),
Padding(
@@ -311,7 +304,7 @@ class _ChannelListViewState extends State<ChannelListView> {
context.translations.sendingFirstMessageLabel,
textAlign: TextAlign.center,
style: chatThemeData.textTheme.body.copyWith(
color: chatThemeData.colorTheme.grey,
color: chatThemeData.colorTheme.textLowEmphasis,
),
),
),
@@ -329,7 +322,7 @@ class _ChannelListViewState extends State<ChannelListView> {
child: Text(
context.translations.startAChatLabel,
style: chatThemeData.textTheme.bodyBold.copyWith(
color: chatThemeData.colorTheme.accentBlue,
color: chatThemeData.colorTheme.accentPrimary,
),
),
),
@@ -364,8 +357,8 @@ class _ChannelListViewState extends State<ChannelListView> {
final chatThemeData = StreamChatTheme.of(context);
if (widget.crossAxisCount > 1) {
return Shimmer.fromColors(
baseColor: chatThemeData.colorTheme.greyGainsboro,
highlightColor: chatThemeData.colorTheme.whiteSmoke,
baseColor: chatThemeData.colorTheme.disabled,
highlightColor: chatThemeData.colorTheme.inputBg,
child: Column(
children: [
const SizedBox(height: 4),
@@ -393,12 +386,12 @@ class _ChannelListViewState extends State<ChannelListView> {
);
} else {
return Shimmer.fromColors(
baseColor: chatThemeData.colorTheme.greyGainsboro,
highlightColor: chatThemeData.colorTheme.whiteSmoke,
baseColor: chatThemeData.colorTheme.disabled,
highlightColor: chatThemeData.colorTheme.inputBg,
child: ListTile(
leading: Container(
decoration: BoxDecoration(
color: chatThemeData.colorTheme.white,
color: chatThemeData.colorTheme.barsBg,
shape: BoxShape.circle,
),
constraints: const BoxConstraints.tightFor(
@@ -414,7 +407,7 @@ class _ChannelListViewState extends State<ChannelListView> {
alignment: Alignment.centerLeft,
child: Container(
decoration: BoxDecoration(
color: chatThemeData.colorTheme.white,
color: chatThemeData.colorTheme.barsBg,
borderRadius: BorderRadius.circular(11),
),
constraints: const BoxConstraints.tightFor(
@@ -431,7 +424,7 @@ class _ChannelListViewState extends State<ChannelListView> {
alignment: Alignment.centerLeft,
child: Container(
decoration: BoxDecoration(
color: chatThemeData.colorTheme.white,
color: chatThemeData.colorTheme.barsBg,
borderRadius: BorderRadius.circular(11),
),
constraints: const BoxConstraints.expand(
@@ -443,7 +436,7 @@ class _ChannelListViewState extends State<ChannelListView> {
Container(
margin: const EdgeInsets.only(left: 16),
decoration: BoxDecoration(
color: chatThemeData.colorTheme.white,
color: chatThemeData.colorTheme.barsBg,
borderRadius: BorderRadius.circular(11),
),
constraints: const BoxConstraints.tightFor(
@@ -488,70 +481,73 @@ class _ChannelListViewState extends State<ChannelListView> {
Widget _listItemBuilder(BuildContext context, int i, List<Channel> channels) {
final channelsBloc = ChannelsBloc.of(context);
if (i == channels.length) {
return _buildQueryProgressIndicator(context, channelsBloc);
}
final onTap = _getChannelTap(context);
final chatThemeData = StreamChatTheme.of(context);
final backgroundColor = chatThemeData.colorTheme.whiteSmoke;
final backgroundColor = chatThemeData.colorTheme.inputBg;
final channel = channels[i];
if (i < channels.length) {
final channel = channels[i];
return StreamChannel(
key: ValueKey<String>('CHANNEL-${channel.cid}'),
channel: channel,
child: Slidable(
controller: _slideController,
enabled: widget.swipeToAction,
actionPane: const SlidableBehindActionPane(),
actionExtentRatio: 0.12,
secondaryActions: widget.swipeActions
?.map((e) => IconSlideAction(
color: e.color,
iconWidget: e.iconWidget,
onTap: () {
e.onTap?.call(channel);
},
))
.toList() ??
<Widget>[
return StreamChannel(
key: ValueKey<String>('CHANNEL-${channel.cid}'),
channel: channel,
child: Slidable(
controller: _slideController,
enabled: widget.swipeToAction,
actionPane: const SlidableBehindActionPane(),
actionExtentRatio: 0.12,
secondaryActions: widget.swipeActions
?.map((e) => IconSlideAction(
color: e.color,
iconWidget: e.iconWidget,
onTap: () {
e.onTap?.call(channel);
},
))
.toList() ??
<Widget>[
IconSlideAction(
color: backgroundColor,
icon: Icons.more_horiz,
onTap: widget.onMoreDetailsPressed != null
? () {
widget.onMoreDetailsPressed!(channel);
}
: () {
showModalBottomSheet(
clipBehavior: Clip.hardEdge,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(32),
topRight: Radius.circular(32),
),
),
context: context,
builder: (context) => StreamChannel(
channel: channel,
child: ChannelBottomSheet(
onViewInfoTap: () {
widget.onViewInfoTap?.call(channel);
},
),
),
);
},
),
if ([
'admin',
'owner',
].contains(channel.state!.members
.firstWhereOrNull(
(m) => m.userId == channel.client.state.user?.id)
?.role))
IconSlideAction(
color: backgroundColor,
icon: Icons.more_horiz,
onTap: widget.onMoreDetailsPressed != null
? () {
widget.onMoreDetailsPressed!(channel);
}
: () {
showModalBottomSheet(
clipBehavior: Clip.hardEdge,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(32),
topRight: Radius.circular(32),
),
),
context: context,
builder: (context) => StreamChannel(
channel: channel,
child: ChannelBottomSheet(
onViewInfoTap: () {
widget.onViewInfoTap?.call(channel);
},
),
),
);
},
),
if ([
'admin',
'owner',
].contains(channel.state!.members
.firstWhereOrNull(
(m) => m.userId == channel.client.state.user?.id)
?.role))
IconSlideAction(
color: backgroundColor,
iconWidget: StreamSvgIcon.delete(
color: chatThemeData.colorTheme.accentRed,
iconWidget: StreamSvgIcon.delete(
color: chatThemeData.colorTheme.accentError,
),
onTap: widget.onDeletePressed != null
? () {
@@ -563,36 +559,33 @@ class _ChannelListViewState extends State<ChannelListView> {
title:
context.translations.deleteConversationLabel,
question: context
.translations.deleteConversationQuestion,
.translations.deleteConversationQuestion,
okText: context.translations.deleteLabel,
cancelText: context.translations.cancelLabel,
icon: StreamSvgIcon.delete(
color: chatThemeData.colorTheme.accentRed,
),
);
if (res == true) {
await channel.delete();
}
},
),
],
child: DecoratedBox(
decoration: BoxDecoration(
color: chatThemeData.colorTheme.whiteSnow,
),
child: widget.channelPreviewBuilder?.call(context, channel) ??
ChannelPreview(
onLongPress: widget.onChannelLongPress,
channel: channel,
onImageTap: () => widget.onImageTap?.call(channel),
onTap: (channel) => onTap(channel, widget.channelWidget),
cancelText: context.translations.cancelLabel,
icon: StreamSvgIcon.delete(
color: chatThemeData.colorTheme.accentError,
),
);
if (res == true) {
await channel.delete();
}
},
),
],
child: DecoratedBox(
decoration: BoxDecoration(
color: chatThemeData.colorTheme.appBg,
),
child: widget.channelPreviewBuilder?.call(context, channel) ??
ChannelPreview(
onLongPress: widget.onChannelLongPress,
channel: channel,
onImageTap: () => widget.onImageTap?.call(channel),
onTap: (channel) => onTap(channel, widget.channelWidget),
),
),
);
} else {
return _buildQueryProgressIndicator(context, channelsBloc);
}
),
);
}
ChannelTapCallback _getChannelTap(BuildContext context) {
@@ -628,7 +621,7 @@ class _ChannelListViewState extends State<ChannelListView> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ChannelImage(
ChannelAvatar(
channel: channel,
borderRadius: BorderRadius.circular(32),
selected: selected,
@@ -661,28 +654,33 @@ class _ChannelListViewState extends State<ChannelListView> {
ChannelsBlocState channelsProvider,
) =>
BetterStreamBuilder<bool>(
stream: channelsProvider.queryChannelsLoading,
initialData: false,
errorBuilder: (context, err) => Container(
color: StreamChatTheme.of(context)
.colorTheme
.accentRed
.withOpacity(.2),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Center(
child: Text(context.translations.loadingChannelsError),
),
stream: channelsProvider.queryChannelsLoading,
initialData: false,
errorBuilder: (context, err) {
final theme = StreamChatTheme.of(context);
return Container(
color: theme.colorTheme.textLowEmphasis.withOpacity(0.9),
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
context.translations.loadingChannelsError,
style: theme.textTheme.body.copyWith(
color: Colors.white,
),
),
builder: (context, data) => data
? const Center(
child: Padding(
padding: EdgeInsets.all(16),
child: CircularProgressIndicator(),
),
)
: const Offstage());
),
);
},
builder: (context, showLoading) {
if (!showLoading) return const Offstage();
return const Center(
child: Padding(
padding: EdgeInsets.all(16),
child: CircularProgressIndicator(),
),
);
},
);
Widget _separatorBuilder(context, i) {
final effect = StreamChatTheme.of(context).colorTheme.borderBottom;
@@ -56,7 +56,7 @@ class ChannelPreview extends StatelessWidget {
final Widget? subtitle;
/// Widget rendering the leading element, by default
/// it shows the [ChannelImage]
/// it shows the [ChannelAvatar]
final Widget? leading;
/// Widget rendering the trailing element,
@@ -82,20 +82,9 @@ class ChannelPreview extends StatelessWidget {
contentPadding: const EdgeInsets.symmetric(
horizontal: 8,
),
onTap: () {
if (onTap != null) {
onTap!(channel);
}
},
onLongPress: () {
if (onLongPress != null) {
onLongPress!(channel);
}
},
leading: leading ??
ChannelImage(
onTap: onImageTap,
),
onTap: () => onTap?.call(channel),
onLongPress: () => onLongPress?.call(channel),
leading: leading ?? ChannelAvatar(onTap: onImageTap),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
@@ -56,7 +56,7 @@ class DateDivider extends StatelessWidget {
child: Text(
dayInfo,
style: chatThemeData.textTheme.footnote.copyWith(
color: chatThemeData.colorTheme.white,
color: chatThemeData.colorTheme.barsBg,
),
),
),
@@ -40,8 +40,8 @@ class DeletedMessage extends StatelessWidget {
side: borderSide ??
BorderSide(
color: Theme.of(context).brightness == Brightness.dark
? chatThemeData.colorTheme.white.withAlpha(24)
: chatThemeData.colorTheme.black.withAlpha(24),
? chatThemeData.colorTheme.barsBg.withAlpha(24)
: chatThemeData.colorTheme.textHighEmphasis.withAlpha(24),
),
),
child: Padding(
@@ -6,8 +6,8 @@ import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:photo_view/photo_view.dart';
import 'package:stream_chat_flutter/src/image_footer.dart';
import 'package:stream_chat_flutter/src/image_header.dart';
import 'package:stream_chat_flutter/src/gallery_footer.dart';
import 'package:stream_chat_flutter/src/gallery_header.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:video_player/video_player.dart';
import 'package:stream_chat_flutter/src/extension.dart';
@@ -181,7 +181,7 @@ class _FullScreenMediaState extends State<FullScreenMedia>
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ImageHeader(
GalleryHeader(
userName: widget.userName,
// TODO: Fix this
sentAt: context.translations.sentAtText(
@@ -201,7 +201,7 @@ class _FullScreenMediaState extends State<FullScreenMedia>
},
),
if (widget.message.type != 'ephemeral')
ImageFooter(
GalleryFooter(
currentPage: _currentPage,
totalPages: widget.mediaAttachments.length,
mediaAttachments: widget.mediaAttachments,
@@ -14,9 +14,9 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Footer widget for media display
class ImageFooter extends StatefulWidget implements PreferredSizeWidget {
class GalleryFooter extends StatefulWidget implements PreferredSizeWidget {
/// Creates a channel header
const ImageFooter({
const GalleryFooter({
Key? key,
required this.message,
this.onBackPressed,
@@ -55,13 +55,13 @@ class ImageFooter extends StatefulWidget implements PreferredSizeWidget {
final ValueChanged<int>? mediaSelectedCallBack;
@override
_ImageFooterState createState() => _ImageFooterState();
_GalleryFooterState createState() => _GalleryFooterState();
@override
final Size preferredSize;
}
class _ImageFooterState extends State<ImageFooter> {
class _GalleryFooterState extends State<GalleryFooter> {
final TextEditingController _messageController = TextEditingController();
final FocusNode _messageFocusNode = FocusNode();
@@ -79,7 +79,7 @@ class _ImageFooterState extends State<ImageFooter> {
Widget build(BuildContext context) {
const showShareButton = !kIsWeb;
final mediaQueryData = MediaQuery.of(context);
final chatThemeData = StreamChatTheme.of(context);
final galleryFooterThemeData = GalleryFooterTheme.of(context);
return SizedBox.fromSize(
size: Size(
mediaQueryData.size.width,
@@ -89,7 +89,7 @@ class _ImageFooterState extends State<ImageFooter> {
context: context,
removeTop: true,
child: BottomAppBar(
color: chatThemeData.colorTheme.white,
color: galleryFooterThemeData.backgroundColor,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@@ -101,7 +101,7 @@ class _ImageFooterState extends State<ImageFooter> {
IconButton(
icon: StreamSvgIcon.iconShare(
size: 24,
color: chatThemeData.colorTheme.black,
color: galleryFooterThemeData.shareIconColor,
),
onPressed: () async {
final attachment =
@@ -137,7 +137,7 @@ class _ImageFooterState extends State<ImageFooter> {
children: <Widget>[
Text(
'${widget.currentPage + 1} of ${widget.totalPages}',
style: chatThemeData.textTheme.headlineBold,
style: galleryFooterThemeData.titleTextStyle,
),
],
),
@@ -145,7 +145,7 @@ class _ImageFooterState extends State<ImageFooter> {
),
IconButton(
icon: StreamSvgIcon.iconGrid(
color: chatThemeData.colorTheme.black,
color: galleryFooterThemeData.gridIconButtonColor,
),
onPressed: () => _showPhotosModal(context),
),
@@ -158,10 +158,11 @@ class _ImageFooterState extends State<ImageFooter> {
void _showPhotosModal(context) {
final chatThemeData = StreamChatTheme.of(context);
final galleryFooterThemeData = GalleryFooterTheme.of(context);
showModalBottomSheet(
context: context,
barrierColor: chatThemeData.colorTheme.overlay,
backgroundColor: chatThemeData.colorTheme.white,
barrierColor: galleryFooterThemeData.bottomSheetBarrierColor,
backgroundColor: galleryFooterThemeData.bottomSheetBackgroundColor,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
@@ -192,7 +193,8 @@ class _ImageFooterState extends State<ImageFooter> {
padding: const EdgeInsets.all(16),
child: Text(
context.translations.photosLabel,
style: chatThemeData.textTheme.headlineBold,
style:
galleryFooterThemeData.bottomSheetPhotosTextStyle,
),
),
),
@@ -200,7 +202,8 @@ class _ImageFooterState extends State<ImageFooter> {
alignment: Alignment.centerRight,
child: IconButton(
icon: StreamSvgIcon.close(
color: chatThemeData.colorTheme.black,
color:
galleryFooterThemeData.bottomSheetCloseIconColor,
),
onPressed: () => Navigator.maybePop(context),
),
@@ -263,7 +266,8 @@ class _ImageFooterState extends State<ImageFooter> {
boxShadow: [
BoxShadow(
blurRadius: 8,
color: chatThemeData.colorTheme.black
color: chatThemeData
.colorTheme.textHighEmphasis
.withOpacity(0.3),
),
],
@@ -5,9 +5,9 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Header/AppBar widget for media display screen
class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
class GalleryHeader extends StatelessWidget implements PreferredSizeWidget {
/// Creates a channel header
const ImageHeader({
const GalleryHeader({
Key? key,
required this.message,
this.currentIndex = 0,
@@ -51,7 +51,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
@override
Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
final galleryHeaderThemeData = GalleryHeaderTheme.of(context);
return AppBar(
textTheme: Theme.of(context).textTheme,
brightness: Theme.of(context).brightness,
@@ -59,18 +59,18 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
leading: showBackButton
? IconButton(
icon: StreamSvgIcon.close(
color: chatThemeData.colorTheme.black,
color: galleryHeaderThemeData.closeButtonColor,
size: 24,
),
onPressed: onBackPressed,
)
: const SizedBox(),
backgroundColor: chatThemeData.channelTheme.channelHeaderTheme.color,
backgroundColor: galleryHeaderThemeData.backgroundColor,
actions: <Widget>[
if (message.type != 'ephemeral')
IconButton(
icon: StreamSvgIcon.iconMenuPoint(
color: chatThemeData.colorTheme.black,
color: galleryHeaderThemeData.iconMenuPointColor,
),
onPressed: () {
_showMessageActionModalBottomSheet(context);
@@ -90,11 +90,11 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
children: <Widget>[
Text(
userName,
style: chatThemeData.textTheme.headlineBold,
style: galleryHeaderThemeData.titleTextStyle,
),
Text(
sentAt,
style: chatThemeData.channelPreviewTheme.subtitle,
style: galleryHeaderThemeData.subtitleTextStyle,
),
],
),
@@ -109,11 +109,13 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
void _showMessageActionModalBottomSheet(BuildContext context) async {
final channel = StreamChannel.of(context).channel;
final galleryHeaderThemeData =
StreamChatTheme.of(context).galleryHeaderTheme;
final result = await showDialog(
useRootNavigator: false,
context: context,
barrierColor: StreamChatTheme.of(context).colorTheme.overlay,
barrierColor: galleryHeaderThemeData.bottomSheetBarrierColor,
builder: (context) => StreamChannel(
channel: channel,
child: AttachmentActionsModal(
@@ -0,0 +1,155 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Widget for constructing a group of images
class GroupAvatar extends StatelessWidget {
/// Constructor for creating a [GroupAvatar]
const GroupAvatar({
Key? key,
required this.members,
this.constraints,
this.onTap,
this.borderRadius,
this.selected = false,
this.selectionColor,
this.selectionThickness = 4,
}) : super(key: key);
/// List of images to display
final List<Member> members;
/// Constraints on the widget
final BoxConstraints? constraints;
/// Callback when widget is tapped
final VoidCallback? onTap;
/// Highlights if selected
final bool selected;
/// [BorderRadius] to pass to the widget
final BorderRadius? borderRadius;
/// Color of selection if selected
final Color? selectionColor;
/// Thickness with which color of selection is shown
final double selectionThickness;
@override
Widget build(BuildContext context) {
final channel = StreamChannel.of(context).channel;
assert(channel.state != null, 'Channel ${channel.id} is not initialized');
final streamChatTheme = StreamChatTheme.of(context);
final colorTheme = streamChatTheme.colorTheme;
final previewTheme = streamChatTheme.channelPreviewTheme.avatarTheme;
Widget avatar = GestureDetector(
onTap: onTap,
child: ClipRRect(
borderRadius: borderRadius ?? previewTheme?.borderRadius,
child: Container(
constraints: constraints ?? previewTheme?.constraints,
decoration: BoxDecoration(color: colorTheme.accentPrimary),
child: Flex(
direction: Axis.vertical,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Flexible(
fit: FlexFit.tight,
child: Flex(
direction: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: members
.take(2)
.map(
(member) => Flexible(
fit: FlexFit.tight,
child: FittedBox(
fit: BoxFit.cover,
clipBehavior: Clip.antiAlias,
child: Transform.scale(
scale: 1.2,
child: BetterStreamBuilder<Member>(
stream: channel.state!.membersStream.map(
(members) => members.firstWhere(
(it) => it.userId == member.userId,
orElse: () => member,
),
),
initialData: member,
builder: (context, member) => UserAvatar(
user: member.user!,
borderRadius: BorderRadius.zero,
),
),
),
),
),
)
.toList(),
),
),
if (members.length > 2)
Flexible(
fit: FlexFit.tight,
child: Flex(
direction: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: members
.skip(2)
.take(2)
.map(
(member) => Flexible(
fit: FlexFit.tight,
child: FittedBox(
fit: BoxFit.cover,
clipBehavior: Clip.antiAlias,
child: Transform.scale(
scale: 1.2,
child: BetterStreamBuilder<Member>(
stream: channel.state!.membersStream.map(
(members) => members.firstWhere(
(it) => it.userId == member.userId,
orElse: () => member,
),
),
initialData: member,
builder: (context, member) => UserAvatar(
user: member.user!,
borderRadius: BorderRadius.zero,
),
),
),
),
),
)
.toList(),
),
),
],
),
),
),
);
if (selected) {
avatar = ClipRRect(
borderRadius: BorderRadius.circular(selectionThickness) +
(borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero),
child: Container(
constraints: constraints ?? previewTheme?.constraints,
color: selectionColor ?? colorTheme.accentPrimary,
child: Padding(
padding: EdgeInsets.all(selectionThickness),
child: avatar,
),
),
);
}
return avatar;
}
}
@@ -1,135 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Widget for constructing a group of images
class GroupImage extends StatelessWidget {
/// Constructor for creating a [GroupImage]
const GroupImage({
Key? key,
required this.images,
this.constraints,
this.onTap,
this.borderRadius,
this.selected = false,
this.selectionColor,
this.selectionThickness = 4,
}) : super(key: key);
/// List of images to display
final List<String> images;
/// Constraints on the widget
final BoxConstraints? constraints;
/// Callback when widget is tapped
final VoidCallback? onTap;
/// Highlights if selected
final bool selected;
/// [BorderRadius] to pass to the widget
final BorderRadius? borderRadius;
/// Color of selection if selected
final Color? selectionColor;
/// Thickness with which color of selection is shown
final double selectionThickness;
@override
Widget build(BuildContext context) {
Widget? avatar;
final streamChatTheme = StreamChatTheme.of(context);
avatar = GestureDetector(
onTap: onTap,
child: ClipRRect(
borderRadius: borderRadius ??
streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius,
child: Container(
constraints: constraints ??
streamChatTheme.ownMessageTheme.avatarTheme?.constraints,
decoration: BoxDecoration(
color: streamChatTheme.colorTheme.accentBlue,
),
child: Flex(
direction: Axis.vertical,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Flexible(
fit: FlexFit.tight,
child: Flex(
direction: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: images
.take(2)
.map((url) => Flexible(
fit: FlexFit.tight,
child: FittedBox(
fit: BoxFit.cover,
clipBehavior: Clip.antiAlias,
child: Transform.scale(
scale: 1.2,
child: CachedNetworkImage(
imageUrl: url,
fit: BoxFit.cover,
),
),
),
))
.toList(),
),
),
if (images.length > 2)
Flexible(
fit: FlexFit.tight,
child: Flex(
direction: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: images
.skip(2)
.map((url) => Flexible(
fit: FlexFit.tight,
child: FittedBox(
fit: BoxFit.cover,
clipBehavior: Clip.antiAlias,
child: Transform.scale(
scale: 1.2,
child: CachedNetworkImage(
imageUrl: url,
fit: BoxFit.cover,
),
),
),
))
.toList(),
),
),
],
),
),
),
);
if (selected) {
avatar = ClipRRect(
borderRadius: (borderRadius ??
streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius ??
BorderRadius.zero) +
BorderRadius.circular(selectionThickness),
child: Container(
color: selectionColor ?? streamChatTheme.colorTheme.accentBlue,
height: 64,
width: 64,
child: Padding(
padding: EdgeInsets.all(selectionThickness),
child: avatar,
),
),
);
}
return avatar;
}
}
@@ -46,8 +46,8 @@ class InfoTile extends StatelessWidget {
childAnchor: childAnchor ?? Alignment.bottomCenter,
portal: Container(
height: 25,
color:
backgroundColor ?? chatThemeData.colorTheme.grey.withOpacity(0.9),
color: backgroundColor ??
chatThemeData.colorTheme.textLowEmphasis.withOpacity(0.9),
child: Center(
child: Text(
message,
@@ -90,8 +90,8 @@ class _MediaListViewState extends State<MediaListView> {
? 1.0
: 0.0,
child: Container(
color:
chatThemeData.colorTheme.black.withOpacity(0.5),
color: chatThemeData.colorTheme.textHighEmphasis
.withOpacity(0.5),
alignment: Alignment.topRight,
padding: const EdgeInsets.only(
top: 8,
@@ -99,10 +99,11 @@ class _MediaListViewState extends State<MediaListView> {
),
child: CircleAvatar(
radius: 12,
backgroundColor: chatThemeData.colorTheme.white,
backgroundColor: chatThemeData.colorTheme.barsBg,
child: StreamSvgIcon.check(
size: 24,
color: chatThemeData.colorTheme.black,
color:
chatThemeData.colorTheme.textHighEmphasis,
),
),
),
@@ -124,7 +125,7 @@ class _MediaListViewState extends State<MediaListView> {
child: Text(
media.videoDuration.format(),
style: TextStyle(
color: chatThemeData.colorTheme.white,
color: chatThemeData.colorTheme.barsBg,
),
),
),
@@ -76,7 +76,7 @@ class MentionTile extends StatelessWidget {
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: chatThemeData.textTheme.footnoteBold.copyWith(
color: chatThemeData.colorTheme.grey,
color: chatThemeData.colorTheme.textLowEmphasis,
),
),
],
@@ -90,7 +90,7 @@ class MentionTile extends StatelessWidget {
left: 8,
),
child: StreamSvgIcon.mentions(
color: chatThemeData.colorTheme.accentBlue,
color: chatThemeData.colorTheme.accentPrimary,
),
),
],
@@ -165,7 +165,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
child: SizedBox(
width: mediaQueryData.size.width * 0.75,
child: Material(
color: streamChatThemeData.colorTheme.whiteSnow,
color: streamChatThemeData.colorTheme.appBg,
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
@@ -197,7 +197,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
].insertBetween(
Container(
height: 1,
color: streamChatThemeData.colorTheme.greyWhisper,
color: streamChatThemeData.colorTheme.borders,
),
),
),
@@ -270,7 +270,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
context,
title: context.translations.flagMessageLabel,
icon: StreamSvgIcon.flag(
color: streamChatThemeData.colorTheme.accentRed,
color: streamChatThemeData.colorTheme.accentError,
size: 24,
),
question: context.translations.flagMessageQuestion,
@@ -285,7 +285,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
await showInfoDialog(
context,
icon: StreamSvgIcon.flag(
color: theme.colorTheme.accentRed,
color: theme.colorTheme.accentError,
size: 24,
),
details: context.translations.flagMessageSuccessfulText,
@@ -298,7 +298,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
await showInfoDialog(
context,
icon: StreamSvgIcon.flag(
color: theme.colorTheme.accentRed,
color: theme.colorTheme.accentError,
size: 24,
),
details: context.translations.flagMessageSuccessfulText,
@@ -335,7 +335,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
context,
title: context.translations.deleteMessageLabel,
icon: StreamSvgIcon.flag(
color: StreamChatTheme.of(context).colorTheme.accentRed,
color: StreamChatTheme.of(context).colorTheme.accentError,
size: 24,
),
question: context.translations.deleteMessageQuestion,
@@ -361,7 +361,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
showInfoDialog(
context,
icon: StreamSvgIcon.error(
color: StreamChatTheme.of(context).colorTheme.accentRed,
color: StreamChatTheme.of(context).colorTheme.accentError,
size: 24,
),
details: context.translations.operationCouldNotBeCompletedText,
@@ -542,7 +542,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
child: Row(
children: [
StreamSvgIcon.circleUp(
color: streamChatThemeData.colorTheme.accentBlue,
color: streamChatThemeData.colorTheme.accentPrimary,
),
const SizedBox(width: 16),
Text(
@@ -589,7 +589,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
Padding(
padding: const EdgeInsets.all(8),
child: StreamSvgIcon.edit(
color: streamChatThemeData.colorTheme.greyGainsboro,
color: streamChatThemeData.colorTheme.disabled,
),
),
Text(
@@ -26,6 +26,18 @@ import 'package:substring_highlight/substring_highlight.dart';
import 'package:video_compress/video_compress.dart';
import 'package:stream_chat_flutter/src/extension.dart';
export 'package:video_compress/video_compress.dart' show VideoQuality;
/// A callback that can be passed to [MessageInput.onError].
///
/// This callback should not throw.
///
/// It exists merely for error reporting, and should not be used otherwise.
typedef ErrorListener = void Function(
Object error,
StackTrace? stackTrace,
);
/// Builder for attachment thumbnails
typedef AttachmentThumbnailBuilder = Widget Function(
BuildContext,
@@ -150,11 +162,20 @@ class MessageInput extends StatefulWidget {
this.showCommandsButton = true,
this.mentionsTileBuilder,
this.maxAttachmentSize = _kDefaultMaxAttachmentSize,
this.compressedVideoQuality = VideoQuality.DefaultQuality,
this.compressedVideoFrameRate = 30,
this.onError,
}) : super(key: key);
/// Message to edit
final Message? editMessage;
/// Video quality to use when compressing the videos
final VideoQuality compressedVideoQuality;
/// Frame rate to use when compressing the videos
final int compressedVideoFrameRate;
/// Max attachment size in bytes
/// Defaults to 20 MB
/// do not set it if you're using our default CDN
@@ -224,6 +245,9 @@ class MessageInput extends StatefulWidget {
/// Customize the tile for the mentions overlay
final MentionTileBuilder? mentionsTileBuilder;
/// A callback for error reporting
final ErrorListener? onError;
@override
MessageInputState createState() => MessageInputState();
@@ -331,7 +355,7 @@ class MessageInputState extends State<MessageInput> {
Padding(
padding: const EdgeInsets.all(8),
child: StreamSvgIcon.reply(
color: _streamChatTheme.colorTheme.greyGainsboro,
color: _streamChatTheme.colorTheme.disabled,
),
),
const Text(
@@ -398,7 +422,8 @@ class MessageInputState extends State<MessageInput> {
border: _sendAsDm
? null
: Border.all(
color: _streamChatTheme.colorTheme.black.withOpacity(.5),
color: _streamChatTheme.colorTheme.textHighEmphasis
.withOpacity(.5),
width: 2,
),
borderRadius: BorderRadius.circular(3),
@@ -407,8 +432,8 @@ class MessageInputState extends State<MessageInput> {
child: Material(
borderRadius: BorderRadius.circular(3),
color: _sendAsDm
? _streamChatTheme.colorTheme.accentBlue
: _streamChatTheme.colorTheme.white,
? _streamChatTheme.colorTheme.accentPrimary
: _streamChatTheme.colorTheme.barsBg,
child: InkWell(
onTap: () {
setState(() {
@@ -423,7 +448,7 @@ class MessageInputState extends State<MessageInput> {
: CrossFadeState.showSecond,
firstChild: StreamSvgIcon.check(
size: 16,
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
),
secondChild: const SizedBox(
height: 16,
@@ -439,7 +464,8 @@ class MessageInputState extends State<MessageInput> {
child: Text(
context.translations.alsoSendAsDirectMessageLabel,
style: _streamChatTheme.textTheme.footnote.copyWith(
color: _streamChatTheme.colorTheme.black.withOpacity(0.5),
color: _streamChatTheme.colorTheme.textHighEmphasis
.withOpacity(0.5),
),
),
),
@@ -579,7 +605,7 @@ class MessageInputState extends State<MessageInput> {
isDense: true,
hintText: _getHint(context),
hintStyle: _streamChatTheme.messageInputTheme.inputTextStyle!.copyWith(
color: _streamChatTheme.colorTheme.grey,
color: _streamChatTheme.colorTheme.textLowEmphasis,
),
border: const OutlineInputBorder(
borderSide: BorderSide(
@@ -617,7 +643,7 @@ class MessageInputState extends State<MessageInput> {
constraints: BoxConstraints.tight(const Size(64, 24)),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: _streamChatTheme.colorTheme.accentBlue,
color: _streamChatTheme.colorTheme.accentPrimary,
),
alignment: Alignment.center,
child: Row(
@@ -831,12 +857,12 @@ class MessageInputState extends State<MessageInput> {
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
clipBehavior: Clip.hardEdge,
child: Container(
constraints: BoxConstraints.loose(const Size.fromHeight(400)),
decoration: BoxDecoration(
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
borderRadius: BorderRadius.circular(8)),
child: ListView(
padding: const EdgeInsets.all(0),
@@ -852,14 +878,14 @@ class MessageInputState extends State<MessageInput> {
horizontal: 8,
),
child: StreamSvgIcon.lightning(
color: _streamChatTheme.colorTheme.accentBlue,
color: _streamChatTheme.colorTheme.accentPrimary,
),
),
Text(
context.translations.instantCommandsLabel,
style: TextStyle(
color:
_streamChatTheme.colorTheme.black.withOpacity(.5),
color: _streamChatTheme.colorTheme.textHighEmphasis
.withOpacity(.5),
),
)
],
@@ -899,7 +925,7 @@ class MessageInputState extends State<MessageInput> {
color: _streamChatTheme
// ignore: lines_longer_than_80_chars
.colorTheme
.grey,
.textLowEmphasis,
),
),
],
@@ -943,24 +969,29 @@ class MessageInputState extends State<MessageInput> {
switch (index) {
case 0:
return _attachments.isEmpty
? streamChatThemeData.colorTheme.accentBlue
? streamChatThemeData.colorTheme.accentPrimary
: (!_attachmentContainsFile
? streamChatThemeData.colorTheme.accentBlue
: streamChatThemeData.colorTheme.black.withOpacity(0.2));
? streamChatThemeData.colorTheme.accentPrimary
: streamChatThemeData.colorTheme.textHighEmphasis
.withOpacity(0.2));
case 1:
return _attachmentContainsFile
? streamChatThemeData.colorTheme.accentBlue
? streamChatThemeData.colorTheme.accentPrimary
: (_attachments.isEmpty
? streamChatThemeData.colorTheme.black.withOpacity(0.5)
: streamChatThemeData.colorTheme.black.withOpacity(0.2));
? streamChatThemeData.colorTheme.textHighEmphasis
.withOpacity(0.5)
: streamChatThemeData.colorTheme.textHighEmphasis
.withOpacity(0.2));
case 2:
return _attachmentContainsFile && _attachments.isNotEmpty
? streamChatThemeData.colorTheme.black.withOpacity(0.2)
: streamChatThemeData.colorTheme.black.withOpacity(0.5);
? streamChatThemeData.colorTheme.textHighEmphasis.withOpacity(0.2)
: streamChatThemeData.colorTheme.textHighEmphasis
.withOpacity(0.5);
case 3:
return _attachmentContainsFile && _attachments.isNotEmpty
? streamChatThemeData.colorTheme.black.withOpacity(0.2)
: streamChatThemeData.colorTheme.black.withOpacity(0.5);
? streamChatThemeData.colorTheme.textHighEmphasis.withOpacity(0.2)
: streamChatThemeData.colorTheme.textHighEmphasis
.withOpacity(0.5);
default:
return Colors.black;
}
@@ -970,7 +1001,7 @@ class MessageInputState extends State<MessageInput> {
duration: const Duration(milliseconds: 300),
height: _openFilePickerSection ? _filePickerSize : 0,
child: Material(
color: _streamChatTheme.colorTheme.whiteSmoke,
color: _streamChatTheme.colorTheme.inputBg,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
@@ -1033,7 +1064,7 @@ class MessageInputState extends State<MessageInput> {
},
child: DecoratedBox(
decoration: BoxDecoration(
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
@@ -1049,7 +1080,7 @@ class MessageInputState extends State<MessageInput> {
height: 4,
child: DecoratedBox(
decoration: BoxDecoration(
color: _streamChatTheme.colorTheme.whiteSmoke,
color: _streamChatTheme.colorTheme.inputBg,
borderRadius: BorderRadius.circular(4),
),
),
@@ -1063,7 +1094,7 @@ class MessageInputState extends State<MessageInput> {
Expanded(
child: DecoratedBox(
decoration: BoxDecoration(
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
borderRadius: BorderRadius.circular(8),
),
child: _PickerWidget(
@@ -1106,8 +1137,11 @@ class MessageInputState extends State<MessageInput> {
if (file.size! > widget.maxAttachmentSize) {
if (medium.type == AssetType.video && file.path != null) {
final mediaInfo = await (VideoService.compressVideo(file.path!)
as FutureOr<MediaInfo>);
final mediaInfo = await (VideoService.compressVideo(
file.path!,
frameRate: widget.compressedVideoFrameRate,
quality: widget.compressedVideoQuality,
) as FutureOr<MediaInfo>);
if (mediaInfo.filesize! > widget.maxAttachmentSize) {
_showErrorAlert(
@@ -1147,7 +1181,7 @@ class MessageInputState extends State<MessageInput> {
);
case 'ban':
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentBlue,
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: StreamSvgIcon.iconUserDelete(
size: 16,
@@ -1156,7 +1190,7 @@ class MessageInputState extends State<MessageInput> {
);
case 'flag':
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentBlue,
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: StreamSvgIcon.flag(
size: 14,
@@ -1165,7 +1199,7 @@ class MessageInputState extends State<MessageInput> {
);
case 'imgur':
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentBlue,
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: ClipOval(
child: StreamSvgIcon.imgur(
@@ -1175,7 +1209,7 @@ class MessageInputState extends State<MessageInput> {
);
case 'mute':
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentBlue,
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: StreamSvgIcon.mute(
size: 16,
@@ -1184,7 +1218,7 @@ class MessageInputState extends State<MessageInput> {
);
case 'unban':
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentBlue,
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: StreamSvgIcon.userAdd(
size: 16,
@@ -1193,7 +1227,7 @@ class MessageInputState extends State<MessageInput> {
);
case 'unmute':
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentBlue,
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: StreamSvgIcon.volumeUp(
size: 16,
@@ -1202,7 +1236,7 @@ class MessageInputState extends State<MessageInput> {
);
default:
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentBlue,
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: StreamSvgIcon.lightning(
size: 16,
@@ -1242,7 +1276,7 @@ class MessageInputState extends State<MessageInput> {
final child = Card(
margin: const EdgeInsets.all(8),
elevation: 2,
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
@@ -1250,7 +1284,7 @@ class MessageInputState extends State<MessageInput> {
child: Container(
constraints: BoxConstraints.loose(const Size.fromHeight(240)),
decoration: BoxDecoration(
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
),
child: FutureBuilder<List<Member>>(
future: queryMembers ?? Future.value(members),
@@ -1266,7 +1300,7 @@ class MessageInputState extends State<MessageInput> {
.where((it) => it.user != null)
.map(
(m) => Material(
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
child: InkWell(
onTap: () {
if (m.user != null) {
@@ -1353,7 +1387,7 @@ class MessageInputState extends State<MessageInput> {
child: Card(
margin: const EdgeInsets.all(8),
elevation: 2,
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
@@ -1368,7 +1402,7 @@ class MessageInputState extends State<MessageInput> {
offset: Offset(0, -4),
),
],
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
),
child: ListView.builder(
padding: const EdgeInsets.all(0),
@@ -1384,8 +1418,8 @@ class MessageInputState extends State<MessageInput> {
padding:
const EdgeInsets.symmetric(horizontal: 8),
child: StreamSvgIcon.smile(
color:
_streamChatTheme.colorTheme.accentBlue,
color: _streamChatTheme
.colorTheme.accentPrimary,
),
),
Flexible(
@@ -1394,7 +1428,8 @@ class MessageInputState extends State<MessageInput> {
query,
),
style: TextStyle(
color: _streamChatTheme.colorTheme.black
color: _streamChatTheme
.colorTheme.textHighEmphasis
.withOpacity(.5),
),
),
@@ -1566,11 +1601,12 @@ class MessageInputState extends State<MessageInput> {
onPressed: () {
setState(() => _attachments.remove(attachment.id));
},
fillColor: _streamChatTheme.colorTheme.black.withOpacity(.5),
fillColor:
_streamChatTheme.colorTheme.textHighEmphasis.withOpacity(.5),
child: Center(
child: StreamSvgIcon.close(
size: 24,
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
),
),
),
@@ -1605,8 +1641,8 @@ class MessageInputState extends State<MessageInput> {
errorWidget: (_, obj, trace) =>
getFileTypeImage(attachment.extraData['other'] as String?),
placeholder: (context, _) => Shimmer.fromColors(
baseColor: _streamChatTheme.colorTheme.greyGainsboro,
highlightColor: _streamChatTheme.colorTheme.whiteSmoke,
baseColor: _streamChatTheme.colorTheme.disabled,
highlightColor: _streamChatTheme.colorTheme.inputBg,
child: Image.asset(
'images/placeholder.png',
fit: BoxFit.cover,
@@ -1647,7 +1683,7 @@ class MessageInputState extends State<MessageInput> {
return IconButton(
icon: StreamSvgIcon.lightning(
color: s.isNotEmpty
? _streamChatTheme.colorTheme.greyGainsboro
? _streamChatTheme.colorTheme.disabled
: (_commandsOverlay != null
? _streamChatTheme.messageInputTheme.actionButtonColor
: _streamChatTheme.messageInputTheme.actionButtonIdleColor),
@@ -1823,11 +1859,11 @@ class MessageInputState extends State<MessageInput> {
}
if (camera) {
PickedFile? pickedFile;
XFile? pickedFile;
if (fileType == DefaultAttachmentTypes.image) {
pickedFile = await _imagePicker.getImage(source: ImageSource.camera);
pickedFile = await _imagePicker.pickImage(source: ImageSource.camera);
} else if (fileType == DefaultAttachmentTypes.video) {
pickedFile = await _imagePicker.getVideo(source: ImageSource.camera);
pickedFile = await _imagePicker.pickVideo(source: ImageSource.camera);
}
if (pickedFile == null) {
return;
@@ -1879,8 +1915,11 @@ class MessageInputState extends State<MessageInput> {
if (file.size! > widget.maxAttachmentSize) {
if (attachmentType == 'video' && file.path != null) {
final mediaInfo = await (VideoService.compressVideo(file.path!)
as FutureOr<MediaInfo>);
final mediaInfo = await (VideoService.compressVideo(
file.path!,
frameRate: widget.compressedVideoFrameRate,
quality: widget.compressedVideoQuality,
) as FutureOr<MediaInfo>);
if (mediaInfo.filesize! > widget.maxAttachmentSize) {
_showErrorAlert(
@@ -1957,7 +1996,7 @@ class MessageInputState extends State<MessageInput> {
}
/// Sends the current message
void sendMessage() async {
Future<void> sendMessage() async {
var text = textEditingController.text.trim();
if (text.isEmpty && _attachments.isEmpty) {
return;
@@ -1973,9 +2012,7 @@ class MessageInputState extends State<MessageInput> {
textEditingController.clear();
_attachments.clear();
if (widget.onQuotedMessageCleared != null) {
widget.onQuotedMessageCleared!();
}
widget.onQuotedMessageCleared?.call();
setState(() {
_messageIsPresent = false;
@@ -1987,7 +2024,6 @@ class MessageInputState extends State<MessageInput> {
_mentionsOverlay?.remove();
_mentionsOverlay = null;
Future sendingFuture;
Message message;
if (widget.editMessage != null) {
message = widget.editMessage!.copyWith(
@@ -2025,33 +2061,39 @@ class MessageInputState extends State<MessageInput> {
_mentionedUsers.clear();
if (widget.editMessage == null ||
widget.editMessage!.status == MessageSendingStatus.failed ||
widget.editMessage!.status == MessageSendingStatus.sending) {
sendingFuture = channel.sendMessage(message);
} else {
sendingFuture = channel.updateMessage(message);
}
try {
Future sendingFuture;
if (widget.editMessage == null ||
widget.editMessage!.status == MessageSendingStatus.failed ||
widget.editMessage!.status == MessageSendingStatus.sending) {
sendingFuture = channel.sendMessage(message);
} else {
sendingFuture = channel.updateMessage(message);
}
if (!shouldUnfocus) {
FocusScope.of(context).requestFocus(_focusNode);
}
if (!shouldUnfocus) {
FocusScope.of(context).requestFocus(_focusNode);
}
return sendingFuture.then((resp) {
final resp = await sendingFuture;
if (resp.message?.type == 'error') {
_parseExistingMessage(message);
}
if (widget.onMessageSent != null) {
widget.onMessageSent!(resp.message);
widget.onMessageSent?.call(resp.message);
} catch (e, stk) {
if (widget.onError != null) {
widget.onError?.call(e, stk);
} else {
rethrow;
}
});
}
}
StreamSubscription? _keyboardListener;
void _showErrorAlert(String description) {
showModalBottomSheet(
backgroundColor: _streamChatTheme.colorTheme.white,
backgroundColor: _streamChatTheme.colorTheme.barsBg,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
@@ -2065,7 +2107,7 @@ class MessageInputState extends State<MessageInput> {
height: 26,
),
StreamSvgIcon.error(
color: _streamChatTheme.colorTheme.accentRed,
color: _streamChatTheme.colorTheme.accentError,
size: 24,
),
const SizedBox(
@@ -2089,7 +2131,8 @@ class MessageInputState extends State<MessageInput> {
height: 36,
),
Container(
color: _streamChatTheme.colorTheme.black.withOpacity(.08),
color:
_streamChatTheme.colorTheme.textHighEmphasis.withOpacity(.08),
height: 1,
),
Row(
@@ -2101,8 +2144,8 @@ class MessageInputState extends State<MessageInput> {
},
child: Text(
context.translations.okLabel,
style: _streamChatTheme.textTheme.bodyBold
.copyWith(color: _streamChatTheme.colorTheme.accentBlue),
style: _streamChatTheme.textTheme.bodyBold.copyWith(
color: _streamChatTheme.colorTheme.accentPrimary),
),
),
],
@@ -2243,12 +2286,12 @@ class __PickerWidgetState extends State<_PickerWidget> {
},
child: Container(
constraints: const BoxConstraints.expand(),
color: widget.streamChatTheme.colorTheme.whiteSmoke,
color: widget.streamChatTheme.colorTheme.inputBg,
alignment: Alignment.center,
child: Text(
context.translations.addMoreFilesLabel,
style: TextStyle(
color: widget.streamChatTheme.colorTheme.accentBlue,
color: widget.streamChatTheme.colorTheme.accentPrimary,
fontWeight: FontWeight.bold,
),
),
@@ -2266,7 +2309,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
PhotoManager.openSetting();
},
child: Container(
color: widget.streamChatTheme.colorTheme.whiteSmoke,
color: widget.streamChatTheme.colorTheme.inputBg,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -2275,12 +2318,13 @@ class __PickerWidgetState extends State<_PickerWidget> {
'svgs/icon_picture_empty_state.svg',
package: 'stream_chat_flutter',
height: 140,
color: widget.streamChatTheme.colorTheme.greyGainsboro,
color: widget.streamChatTheme.colorTheme.disabled,
),
Text(
context.translations.enablePhotoAndVideoAccessMessage,
style: widget.streamChatTheme.textTheme.body.copyWith(
color: widget.streamChatTheme.colorTheme.grey),
color:
widget.streamChatTheme.colorTheme.textLowEmphasis),
textAlign: TextAlign.center,
),
const SizedBox(height: 6),
@@ -2288,7 +2332,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
child: Text(
context.translations.allowGalleryAccessMessage,
style: widget.streamChatTheme.textTheme.bodyBold.copyWith(
color: widget.streamChatTheme.colorTheme.accentBlue,
color: widget.streamChatTheme.colorTheme.accentPrimary,
),
),
),
@@ -143,7 +143,6 @@ class MessageListView extends StatefulWidget {
this.parentMessage,
this.threadBuilder,
this.onThreadTap,
this.onReplyTap,
this.dateDividerBuilder,
this.scrollPhysics = const ClampingScrollPhysics(),
this.initialScrollIndex,
@@ -153,7 +152,6 @@ class MessageListView extends StatefulWidget {
this.onMessageSwiped,
this.highlightInitialMessage = false,
this.messageHighlightColor,
this.onShowMessage,
this.showConnectionStateTile = false,
this.headerBuilder,
this.footerBuilder,
@@ -161,16 +159,11 @@ class MessageListView extends StatefulWidget {
this.emptyBuilder,
this.systemMessageBuilder,
this.messageListBuilder,
this.errorWidgetBuilder,
this.errorBuilder,
this.messageFilter,
this.customAttachmentBuilders,
this.onMessageTap,
this.onSystemMessageTap,
this.onAttachmentTap,
this.onLinkTap,
this.pinPermissions = const [],
this.textBuilder,
this.usernameBuilder,
this.showFloatingDateDivider = true,
this.threadSeparatorBuilder,
this.messageListController,
@@ -223,9 +216,6 @@ class MessageListView extends StatefulWidget {
/// Called when message item gets swiped
final OnMessageSwiped? onMessageSwiped;
///
final ReplyTapCallback? onReplyTap;
/// If true the list will highlight the initialMessage if there is any.
///
/// Also See [StreamChannel]
@@ -234,9 +224,6 @@ class MessageListView extends StatefulWidget {
/// Color used while highlighting initial message
final Color? messageHighlightColor;
/// Callback when show message is tapped
final ShowMessageCallback? onShowMessage;
/// Flag for showing tile on header
final bool showConnectionStateTile;
@@ -263,16 +250,11 @@ class MessageListView extends StatefulWidget {
/// This parameter can be used to display an error message to
/// users in the event
/// of a connection failure.
final ErrorBuilder? errorWidgetBuilder;
final ErrorBuilder? errorBuilder;
/// Predicate used to filter messages
final bool Function(Message)? messageFilter;
/// Attachment builders for the default message widget
/// Please change this in the [MessageWidget] if you are using a
/// custom implementation
final Map<String, AttachmentBuilder>? customAttachmentBuilders;
/// Called when any message is tapped except a system message
/// (use [onSystemMessageTap] instead)
final OnMessageTap? onMessageTap;
@@ -280,18 +262,6 @@ class MessageListView extends StatefulWidget {
/// Called when system message is tapped
final OnMessageTap? onSystemMessageTap;
/// Customize onTap on attachment
final void Function(Message message, Attachment attachment)? onAttachmentTap;
/// Customize the MessageWidget textBuilder
final Widget Function(BuildContext context, Message message)? textBuilder;
/// Customize the MessageWidget usernameBuilder
final Widget Function(BuildContext context, Message message)? usernameBuilder;
/// Callback for when link is tapped
final void Function(String link)? onLinkTap;
/// A List of user types that have permission to pin messages
final List<String> pinPermissions;
@@ -370,19 +340,21 @@ class _MessageListViewState extends State<MessageListView> {
child: Text(
context.translations.emptyChatMessagesText,
style: _streamTheme.textTheme.footnote.copyWith(
color: _streamTheme.colorTheme.black.withOpacity(.5)),
color: _streamTheme.colorTheme.textHighEmphasis
.withOpacity(.5)),
),
),
messageListBuilder: widget.messageListBuilder ??
(context, list) => _buildListView(list),
messageListController: _messageListController,
parentMessage: widget.parentMessage,
errorWidgetBuilder: widget.errorWidgetBuilder ??
errorBuilder: widget.errorBuilder ??
(BuildContext context, Object error) => Center(
child: Text(
context.translations.genericErrorText,
style: _streamTheme.textTheme.footnote.copyWith(
color: _streamTheme.colorTheme.black.withOpacity(.5)),
color: _streamTheme.colorTheme.textHighEmphasis
.withOpacity(.5)),
),
),
);
@@ -712,7 +684,7 @@ class _MessageListViewState extends State<MessageListView> {
clipBehavior: Clip.none,
children: [
FloatingActionButton(
backgroundColor: _streamTheme.colorTheme.white,
backgroundColor: _streamTheme.colorTheme.barsBg,
onPressed: () {
if (unreadCount > 0) {
streamChannel!.channel.markRead();
@@ -731,7 +703,7 @@ class _MessageListViewState extends State<MessageListView> {
}
},
child: StreamSvgIcon.down(
color: _streamTheme.colorTheme.black,
color: _streamTheme.colorTheme.textHighEmphasis,
),
),
if (showUnreadCount)
@@ -823,7 +795,6 @@ class _MessageListViewState extends State<MessageListView> {
showUsername: !isMyMessage,
padding: const EdgeInsets.all(8),
showSendingIndicator: false,
onThreadTap: _onThreadTap,
borderRadiusGeometry: BorderRadius.only(
topLeft: const Radius.circular(16),
bottomLeft:
@@ -841,7 +812,6 @@ class _MessageListViewState extends State<MessageListView> {
messageTheme: isMyMessage
? _streamTheme.ownMessageTheme
: _streamTheme.otherMessageTheme,
onShowMessage: widget.onShowMessage,
onReturnAction: (action) {
switch (action) {
case ReturnActionType.none:
@@ -852,16 +822,12 @@ class _MessageListViewState extends State<MessageListView> {
break;
}
},
customAttachmentBuilders: widget.customAttachmentBuilders,
onMessageTap: (message) {
if (widget.onMessageTap != null) {
widget.onMessageTap!(message);
}
FocusScope.of(context).unfocus();
},
textBuilder: widget.textBuilder,
usernameBuilder: widget.usernameBuilder,
onLinkTap: widget.onLinkTap,
showPinButton: currentUserMember != null &&
widget.pinPermissions.contains(currentUserMember.role),
);
@@ -899,7 +865,7 @@ class _MessageListViewState extends State<MessageListView> {
final userId = StreamChat.of(context).user!.id;
final isMyMessage = message.user!.id == userId;
final nextMessage = index - 3 >= 0 ? messages[index - 3] : null;
final nextMessage = index - 1 >= 0 ? messages[index - 1] : null;
final isNextUserSame =
nextMessage != null && message.user!.id == nextMessage.user!.id;
@@ -1004,7 +970,6 @@ class _MessageListViewState extends State<MessageListView> {
showFlagButton: !isMyMessage,
borderSide: borderSide,
onThreadTap: _onThreadTap,
onReplyTap: widget.onReplyTap,
attachmentBorderRadiusGeometry: BorderRadius.only(
topLeft: Radius.circular(attachmentBorderRadius),
bottomLeft: isMyMessage
@@ -1055,7 +1020,6 @@ class _MessageListViewState extends State<MessageListView> {
: _streamTheme.otherMessageTheme,
readList: readList,
allRead: allRead,
onShowMessage: widget.onShowMessage,
onReturnAction: (action) {
switch (action) {
case ReturnActionType.none:
@@ -1066,17 +1030,12 @@ class _MessageListViewState extends State<MessageListView> {
break;
}
},
customAttachmentBuilders: widget.customAttachmentBuilders,
onMessageTap: (message) {
if (widget.onMessageTap != null) {
widget.onMessageTap!(message);
}
FocusScope.of(context).unfocus();
},
onAttachmentTap: widget.onAttachmentTap,
textBuilder: widget.textBuilder,
usernameBuilder: widget.usernameBuilder,
onLinkTap: widget.onLinkTap,
showPinButton: widget.pinPermissions.contains(currentUserMember.role),
);
@@ -1108,7 +1067,7 @@ class _MessageListViewState extends State<MessageListView> {
widget.onMessageSwiped?.call(message);
},
backgroundIcon: StreamSvgIcon.reply(
color: _streamTheme.colorTheme.accentBlue,
color: _streamTheme.colorTheme.accentPrimary,
),
child: child,
),
@@ -1124,7 +1083,7 @@ class _MessageListViewState extends State<MessageListView> {
child = TweenAnimationBuilder<Color?>(
tween: ColorTween(
begin: highlightColor,
end: colorTheme.white.withOpacity(0),
end: colorTheme.barsBg.withOpacity(0),
),
duration: const Duration(seconds: 3),
onEnd: () => initialMessageHighlightComplete = true,
@@ -1254,7 +1213,7 @@ class _LoadingIndicator extends StatelessWidget {
stream: stream,
initialData: false,
errorBuilder: (context, error) => Container(
color: streamTheme.colorTheme.accentRed.withOpacity(.2),
color: streamTheme.colorTheme.accentError.withOpacity(.2),
child: Center(
child: Text(context.translations.loadingMessagesError),
),
@@ -143,7 +143,7 @@ class MessageReactionsModal extends StatelessWidget {
Widget _buildReactionCard(BuildContext context, User? user) {
final chatThemeData = StreamChatTheme.of(context);
return Card(
color: chatThemeData.colorTheme.white,
color: chatThemeData.colorTheme.barsBg,
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
@@ -225,7 +225,7 @@ class MessageReactionsModal extends StatelessWidget {
messageTheme.reactionsBorderColor ?? Colors.transparent,
backgroundColor: messageTheme.reactionsBackgroundColor ??
Colors.transparent,
maskColor: chatThemeData.colorTheme.white,
maskColor: chatThemeData.colorTheme.barsBg,
tailCirclesSpacing: 1,
highlightOwnReactions: false,
),
@@ -201,7 +201,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
Widget _separatorBuilder(BuildContext context, int index) => Container(
height: 1,
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
color: StreamChatTheme.of(context).colorTheme.borders,
);
Widget _listItemBuilder(
@@ -226,7 +226,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
return Container(
color: StreamChatTheme.of(context)
.colorTheme
.accentRed
.accentError
.withOpacity(.2),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
@@ -296,7 +296,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
child: Text(
context.translations.resultCountText(items.length),
style: TextStyle(
color: chatThemeData.colorTheme.grey,
color: chatThemeData.colorTheme.textLowEmphasis,
),
),
),
@@ -223,7 +223,7 @@ class MessageWidget extends StatefulWidget {
final border = RoundedRectangleBorder(
side: attachmentBorderSide ??
BorderSide(
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
color: StreamChatTheme.of(context).colorTheme.borders,
),
borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero,
);
@@ -750,7 +750,7 @@ class _MessageWidgetState extends State<MessageWidget>
child: CustomPaint(
painter: ReactionBubblePainter(
_streamChatTheme
.colorTheme.white,
.colorTheme.barsBg,
Colors.transparent,
Colors.transparent,
tailCirclesSpace: 1,
@@ -836,14 +836,14 @@ class _MessageWidgetState extends State<MessageWidget>
mainAxisSize: MainAxisSize.min,
children: [
StreamSvgIcon.eye(
color: chatThemeData.colorTheme.grey,
color: chatThemeData.colorTheme.textLowEmphasis,
size: 16,
),
const SizedBox(width: 8),
Text(
context.translations.onlyVisibleToYouText,
style: chatThemeData.textTheme.footnote
.copyWith(color: chatThemeData.colorTheme.grey),
.copyWith(color: chatThemeData.colorTheme.textLowEmphasis),
),
],
);
@@ -1222,7 +1222,7 @@ class _MessageWidgetState extends State<MessageWidget>
Text(
widget.readList!.length.toString(),
style: style.copyWith(
color: _streamChatTheme.colorTheme.accentBlue,
color: _streamChatTheme.colorTheme.accentPrimary,
),
),
const SizedBox(width: 2),
@@ -1299,7 +1299,7 @@ class _MessageWidgetState extends State<MessageWidget>
currentUser: currentUser,
),
style: TextStyle(
color: _streamChatTheme.colorTheme.grey,
color: _streamChatTheme.colorTheme.textLowEmphasis,
fontSize: 13,
fontWeight: FontWeight.w400,
),
@@ -1317,7 +1317,7 @@ class _MessageWidgetState extends State<MessageWidget>
}
if (hasUrlAttachments) {
return _streamChatTheme.colorTheme.blueAlice;
return _streamChatTheme.colorTheme.linkBg;
}
if (isOnlyEmoji) {
@@ -1373,7 +1373,7 @@ class _ThreadParticipants extends StatelessWidget {
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
),
padding: const EdgeInsets.all(1),
child: UserAvatar(
@@ -1402,7 +1402,7 @@ class _ThreadReplyPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color ?? StreamChatTheme.of(context!).colorTheme.greyGainsboro
..color = color ?? StreamChatTheme.of(context!).colorTheme.disabled
..style = PaintingStyle.stroke
..strokeWidth = 1
..strokeCap = StrokeCap.round;
@@ -46,11 +46,11 @@ class OptionListTile extends StatelessWidget {
return Column(
children: [
Container(
color: separatorColor ?? chatThemeData.colorTheme.greyGainsboro,
color: separatorColor ?? chatThemeData.colorTheme.disabled,
height: 1,
),
Material(
color: tileColor ?? chatThemeData.colorTheme.white,
color: tileColor ?? chatThemeData.colorTheme.barsBg,
child: SizedBox(
height: 63,
child: InkWell(
@@ -154,7 +154,7 @@ class QuotedMessageWidget extends StatelessWidget {
color: _getBackgroundColor(context),
border: showBorder
? Border.all(
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
color: StreamChatTheme.of(context).colorTheme.disabled,
)
: null,
borderRadius: BorderRadius.only(
@@ -280,7 +280,7 @@ class QuotedMessageWidget extends StatelessWidget {
Color? _getBackgroundColor(BuildContext context) {
if (_containsScrapeUrl) {
return StreamChatTheme.of(context).colorTheme.blueAlice;
return StreamChatTheme.of(context).colorTheme.linkBg;
}
return messageTheme.messageBackgroundColor;
}
@@ -141,8 +141,8 @@ class ReactionBubble extends StatelessWidget {
Icons.help_outline_rounded,
size: 16,
color: (!highlightOwnReactions || reaction.user?.id == userId)
? chatThemeData.colorTheme.accentBlue
: chatThemeData.colorTheme.black.withOpacity(.5),
? chatThemeData.colorTheme.accentPrimary
: chatThemeData.colorTheme.textHighEmphasis.withOpacity(.5),
),
);
}
@@ -49,7 +49,7 @@ class _ReactionPickerState extends State<ReactionPicker>
final child = Material(
borderRadius: BorderRadius.circular(24),
color: chatThemeData.colorTheme.white,
color: chatThemeData.colorTheme.barsBg,
clipBehavior: Clip.hardEdge,
child: Padding(
padding: const EdgeInsets.symmetric(
@@ -25,7 +25,7 @@ class SendingIndicator extends StatelessWidget {
if (isMessageRead) {
return StreamSvgIcon.checkAll(
size: size,
color: StreamChatTheme.of(context).colorTheme.accentBlue,
color: StreamChatTheme.of(context).colorTheme.accentPrimary,
);
}
if (message.status == MessageSendingStatus.sent) {
@@ -101,8 +101,8 @@ class StreamChatState extends State<StreamChat> {
return Theme(
data: materialTheme.copyWith(
primaryIconTheme: streamTheme.primaryIconTheme,
accentColor: streamTheme.colorTheme.accentBlue,
scaffoldBackgroundColor: streamTheme.colorTheme.white,
accentColor: streamTheme.colorTheme.accentPrimary,
scaffoldBackgroundColor: streamTheme.colorTheme.barsBg,
),
child: StreamChatCore(
client: client,
@@ -1,4 +1,5 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/channel_header.dart';
import 'package:stream_chat_flutter/src/channel_preview.dart';
@@ -54,10 +55,11 @@ class StreamChatThemeData {
MessageTheme? otherMessageTheme,
MessageTheme? ownMessageTheme,
MessageInputTheme? messageInputTheme,
Widget Function(BuildContext, Channel)? defaultChannelImage,
Widget Function(BuildContext, User)? defaultUserImage,
IconThemeData? primaryIconTheme,
List<ReactionIcon>? reactionIcons,
GalleryHeaderThemeData? imageHeaderTheme,
GalleryFooterThemeData? imageFooterTheme,
}) {
brightness ??= colorTheme?.brightness ?? Brightness.light;
final isDark = brightness == Brightness.dark;
@@ -76,10 +78,11 @@ class StreamChatThemeData {
otherMessageTheme: otherMessageTheme,
ownMessageTheme: ownMessageTheme,
messageInputTheme: messageInputTheme,
defaultChannelImage: defaultChannelImage,
defaultUserImage: defaultUserImage,
primaryIconTheme: primaryIconTheme,
reactionIcons: reactionIcons,
galleryHeaderTheme: imageHeaderTheme,
galleryFooterTheme: imageFooterTheme,
);
return defaultData.merge(customizedData);
@@ -103,10 +106,11 @@ class StreamChatThemeData {
required this.otherMessageTheme,
required this.ownMessageTheme,
required this.messageInputTheme,
required this.defaultChannelImage,
required this.defaultUserImage,
required this.primaryIconTheme,
required this.reactionIcons,
required this.galleryHeaderTheme,
required this.galleryFooterTheme,
});
/// Create a theme from a Material [Theme]
@@ -114,7 +118,7 @@ class StreamChatThemeData {
final defaultTheme = StreamChatThemeData(brightness: theme.brightness);
final customizedTheme = StreamChatThemeData.fromColorAndTextTheme(
defaultTheme.colorTheme.copyWith(
accentBlue: theme.accentColor,
accentPrimary: theme.accentColor,
),
defaultTheme.textTheme,
);
@@ -136,6 +140,14 @@ class StreamChatThemeData {
/// Theme of the chat widgets dedicated to a channel
final ChannelTheme channelTheme;
/// The default style for [GalleryHeader]s below the overall
/// [StreamChatTheme].
final GalleryHeaderThemeData galleryHeaderTheme;
/// The default style for [GalleryFooter]s below the overall
/// [StreamChatTheme].
final GalleryFooterThemeData galleryFooterTheme;
/// Theme of the current user messages
final MessageTheme ownMessageTheme;
@@ -145,9 +157,6 @@ class StreamChatThemeData {
/// Theme dedicated to the [MessageInput] widget
final MessageInputTheme messageInputTheme;
/// The widget that will be built when the channel image is unavailable
final Widget Function(BuildContext, Channel) defaultChannelImage;
/// The widget that will be built when the user image is unavailable
final Widget Function(BuildContext, User) defaultUserImage;
@@ -167,11 +176,12 @@ class StreamChatThemeData {
MessageTheme? ownMessageTheme,
MessageTheme? otherMessageTheme,
MessageInputTheme? messageInputTheme,
Widget Function(BuildContext, Channel)? defaultChannelImage,
Widget Function(BuildContext, User)? defaultUserImage,
IconThemeData? primaryIconTheme,
ChannelListHeaderTheme? channelListHeaderTheme,
List<ReactionIcon>? reactionIcons,
GalleryHeaderThemeData? galleryHeaderTheme,
GalleryFooterThemeData? galleryFooterTheme,
}) =>
StreamChatThemeData.raw(
channelListHeaderTheme:
@@ -179,7 +189,6 @@ class StreamChatThemeData {
textTheme: this.textTheme.merge(textTheme),
colorTheme: this.colorTheme.merge(colorTheme),
primaryIconTheme: this.primaryIconTheme.merge(primaryIconTheme),
defaultChannelImage: defaultChannelImage ?? this.defaultChannelImage,
defaultUserImage: defaultUserImage ?? this.defaultUserImage,
channelPreviewTheme:
this.channelPreviewTheme.merge(channelPreviewTheme),
@@ -188,6 +197,8 @@ class StreamChatThemeData {
otherMessageTheme: this.otherMessageTheme.merge(otherMessageTheme),
messageInputTheme: this.messageInputTheme.merge(messageInputTheme),
reactionIcons: reactionIcons ?? this.reactionIcons,
galleryHeaderTheme: galleryHeaderTheme ?? this.galleryHeaderTheme,
galleryFooterTheme: galleryFooterTheme ?? this.galleryFooterTheme,
);
/// Merge themes
@@ -199,7 +210,6 @@ class StreamChatThemeData {
textTheme: textTheme.merge(other.textTheme),
colorTheme: colorTheme.merge(other.colorTheme),
primaryIconTheme: other.primaryIconTheme,
defaultChannelImage: other.defaultChannelImage,
defaultUserImage: other.defaultUserImage,
channelPreviewTheme: channelPreviewTheme.merge(other.channelPreviewTheme),
channelTheme: channelTheme.merge(other.channelTheme),
@@ -207,6 +217,8 @@ class StreamChatThemeData {
otherMessageTheme: otherMessageTheme.merge(other.otherMessageTheme),
messageInputTheme: messageInputTheme.merge(other.messageInputTheme),
reactionIcons: other.reactionIcons,
galleryHeaderTheme: galleryHeaderTheme.merge(other.galleryHeaderTheme),
galleryFooterTheme: galleryFooterTheme.merge(other.galleryFooterTheme),
);
}
@@ -216,22 +228,11 @@ class StreamChatThemeData {
ColorTheme colorTheme,
TextTheme textTheme,
) {
final accentColor = colorTheme.accentBlue;
final iconTheme = IconThemeData(color: colorTheme.black.withOpacity(.5));
return StreamChatThemeData.raw(
textTheme: textTheme,
colorTheme: colorTheme,
primaryIconTheme: iconTheme,
defaultChannelImage: (context, channel) => const SizedBox(),
defaultUserImage: (context, user) => Center(
child: CachedNetworkImage(
filterQuality: FilterQuality.high,
imageUrl: getRandomPicUrl(user),
fit: BoxFit.cover,
),
),
channelPreviewTheme: ChannelPreviewTheme(
unreadCounterColor: colorTheme.accentRed,
final accentColor = colorTheme.accentPrimary;
final iconTheme =
IconThemeData(color: colorTheme.textHighEmphasis.withOpacity(.5));
final channelTheme = ChannelTheme(
channelHeaderTheme: ChannelHeaderTheme(
avatarTheme: AvatarTheme(
borderRadius: BorderRadius.circular(20),
constraints: const BoxConstraints.tightFor(
@@ -239,15 +240,43 @@ class StreamChatThemeData {
width: 40,
),
),
title: textTheme.bodyBold,
color: colorTheme.barsBg,
title: textTheme.headlineBold,
subtitle: textTheme.footnote.copyWith(
color: const Color(0xff7A7A7A),
),
lastMessageAt: textTheme.footnote.copyWith(
color: colorTheme.black.withOpacity(.5),
),
indicatorIconSize: 16,
),
);
final channelPreviewTheme = ChannelPreviewTheme(
unreadCounterColor: colorTheme.accentError,
avatarTheme: AvatarTheme(
borderRadius: BorderRadius.circular(20),
constraints: const BoxConstraints.tightFor(
height: 40,
width: 40,
),
),
title: textTheme.bodyBold,
subtitle: textTheme.footnote.copyWith(
color: const Color(0xff7A7A7A),
),
lastMessageAt: textTheme.footnote.copyWith(
color: colorTheme.textHighEmphasis.withOpacity(.5),
),
indicatorIconSize: 16,
);
return StreamChatThemeData.raw(
textTheme: textTheme,
colorTheme: colorTheme,
primaryIconTheme: iconTheme,
defaultUserImage: (context, user) => Center(
child: CachedNetworkImage(
filterQuality: FilterQuality.high,
imageUrl: getRandomPicUrl(user),
fit: BoxFit.cover,
),
),
channelPreviewTheme: channelPreviewTheme,
channelListHeaderTheme: ChannelListHeaderTheme(
avatarTheme: AvatarTheme(
borderRadius: BorderRadius.circular(20),
@@ -256,35 +285,22 @@ class StreamChatThemeData {
width: 40,
),
),
color: colorTheme.white,
color: colorTheme.barsBg,
title: textTheme.headlineBold,
),
channelTheme: ChannelTheme(
channelHeaderTheme: ChannelHeaderTheme(
avatarTheme: AvatarTheme(
borderRadius: BorderRadius.circular(20),
constraints: const BoxConstraints.tightFor(
height: 40,
width: 40,
),
),
color: colorTheme.white,
title: textTheme.headlineBold,
subtitle: textTheme.footnote.copyWith(
color: const Color(0xff7A7A7A),
),
),
),
channelTheme: channelTheme,
ownMessageTheme: MessageTheme(
messageAuthor: textTheme.footnote.copyWith(color: colorTheme.grey),
messageAuthor:
textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis),
messageText: textTheme.body,
createdAt: textTheme.footnote.copyWith(color: colorTheme.grey),
createdAt:
textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis),
replies: textTheme.footnoteBold.copyWith(color: accentColor),
messageBackgroundColor: colorTheme.greyGainsboro,
reactionsBackgroundColor: colorTheme.white,
reactionsBorderColor: colorTheme.greyWhisper,
reactionsMaskColor: colorTheme.whiteSnow,
messageBorderColor: colorTheme.greyGainsboro,
messageBackgroundColor: colorTheme.disabled,
reactionsBackgroundColor: colorTheme.barsBg,
reactionsBorderColor: colorTheme.borders,
reactionsMaskColor: colorTheme.appBg,
messageBorderColor: colorTheme.disabled,
avatarTheme: AvatarTheme(
borderRadius: BorderRadius.circular(20),
constraints: const BoxConstraints.tightFor(
@@ -297,18 +313,20 @@ class StreamChatThemeData {
),
),
otherMessageTheme: MessageTheme(
reactionsBackgroundColor: colorTheme.greyGainsboro,
reactionsBorderColor: colorTheme.white,
reactionsMaskColor: colorTheme.whiteSnow,
reactionsBackgroundColor: colorTheme.disabled,
reactionsBorderColor: colorTheme.barsBg,
reactionsMaskColor: colorTheme.appBg,
messageText: textTheme.body,
createdAt: textTheme.footnote.copyWith(color: colorTheme.grey),
messageAuthor: textTheme.footnote.copyWith(color: colorTheme.grey),
createdAt:
textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis),
messageAuthor:
textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis),
replies: textTheme.footnoteBold.copyWith(color: accentColor),
messageLinks: TextStyle(
color: accentColor,
),
messageBackgroundColor: colorTheme.white,
messageBorderColor: colorTheme.greyWhisper,
messageBackgroundColor: colorTheme.barsBg,
messageBorderColor: colorTheme.borders,
avatarTheme: AvatarTheme(
borderRadius: BorderRadius.circular(20),
constraints: const BoxConstraints.tightFor(
@@ -320,23 +338,23 @@ class StreamChatThemeData {
messageInputTheme: MessageInputTheme(
borderRadius: BorderRadius.circular(20),
sendAnimationDuration: const Duration(milliseconds: 300),
actionButtonColor: colorTheme.accentBlue,
actionButtonIdleColor: colorTheme.grey,
expandButtonColor: colorTheme.accentBlue,
sendButtonColor: colorTheme.accentBlue,
sendButtonIdleColor: colorTheme.greyGainsboro,
inputBackground: colorTheme.white,
actionButtonColor: colorTheme.accentPrimary,
actionButtonIdleColor: colorTheme.textLowEmphasis,
expandButtonColor: colorTheme.accentPrimary,
sendButtonColor: colorTheme.accentPrimary,
sendButtonIdleColor: colorTheme.disabled,
inputBackground: colorTheme.barsBg,
inputTextStyle: textTheme.body,
idleBorderGradient: LinearGradient(
colors: [
colorTheme.greyGainsboro,
colorTheme.greyGainsboro,
colorTheme.disabled,
colorTheme.disabled,
],
),
activeBorderGradient: LinearGradient(
colors: [
colorTheme.greyGainsboro,
colorTheme.greyGainsboro,
colorTheme.disabled,
colorTheme.disabled,
],
),
),
@@ -347,7 +365,7 @@ class StreamChatThemeData {
final theme = StreamChatTheme.of(context);
return StreamSvgIcon.loveReaction(
color: highlighted
? theme.colorTheme.accentBlue
? theme.colorTheme.accentPrimary
: theme.primaryIconTheme.color!.withOpacity(.5),
size: size,
);
@@ -359,7 +377,7 @@ class StreamChatThemeData {
final theme = StreamChatTheme.of(context);
return StreamSvgIcon.thumbsUpReaction(
color: highlighted
? theme.colorTheme.accentBlue
? theme.colorTheme.accentPrimary
: theme.primaryIconTheme.color!.withOpacity(.5),
size: size,
);
@@ -371,7 +389,7 @@ class StreamChatThemeData {
final theme = StreamChatTheme.of(context);
return StreamSvgIcon.thumbsDownReaction(
color: highlighted
? theme.colorTheme.accentBlue
? theme.colorTheme.accentPrimary
: theme.primaryIconTheme.color!.withOpacity(.5),
size: size,
);
@@ -383,7 +401,7 @@ class StreamChatThemeData {
final theme = StreamChatTheme.of(context);
return StreamSvgIcon.lolReaction(
color: highlighted
? theme.colorTheme.accentBlue
? theme.colorTheme.accentPrimary
: theme.primaryIconTheme.color!.withOpacity(.5),
size: size,
);
@@ -395,13 +413,31 @@ class StreamChatThemeData {
final theme = StreamChatTheme.of(context);
return StreamSvgIcon.wutReaction(
color: highlighted
? theme.colorTheme.accentBlue
? theme.colorTheme.accentPrimary
: theme.primaryIconTheme.color!.withOpacity(.5),
size: size,
);
},
),
],
galleryHeaderTheme: GalleryHeaderThemeData(
closeButtonColor: colorTheme.textHighEmphasis,
backgroundColor: channelTheme.channelHeaderTheme.color,
iconMenuPointColor: colorTheme.textHighEmphasis,
titleTextStyle: textTheme.headlineBold,
subtitleTextStyle: channelPreviewTheme.subtitle,
bottomSheetBarrierColor: colorTheme.overlay,
),
galleryFooterTheme: GalleryFooterThemeData(
backgroundColor: colorTheme.barsBg,
shareIconColor: colorTheme.textHighEmphasis,
titleTextStyle: textTheme.headlineBold,
gridIconButtonColor: colorTheme.textHighEmphasis,
bottomSheetBarrierColor: colorTheme.overlay,
bottomSheetBackgroundColor: colorTheme.barsBg,
bottomSheetPhotosTextStyle: textTheme.headlineBold,
bottomSheetCloseIconColor: colorTheme.textHighEmphasis,
),
);
}
}
@@ -572,17 +608,17 @@ class TextTheme {
class ColorTheme {
/// Initialise with light theme
ColorTheme.light({
this.black = const Color(0xff000000),
this.grey = const Color(0xff7a7a7a),
this.greyGainsboro = const Color(0xffdbdbdb),
this.greyWhisper = const Color(0xffecebeb),
this.whiteSmoke = const Color(0xfff2f2f2),
this.whiteSnow = const Color(0xfffcfcfc),
this.white = const Color(0xffffffff),
this.blueAlice = const Color(0xffe9f2ff),
this.accentBlue = const Color(0xff005FFF),
this.accentRed = const Color(0xffFF3842),
this.accentGreen = const Color(0xff20E070),
this.textHighEmphasis = const Color(0xff000000),
this.textLowEmphasis = const Color(0xff7a7a7a),
this.disabled = const Color(0xffdbdbdb),
this.borders = const Color(0xffecebeb),
this.inputBg = const Color(0xfff2f2f2),
this.appBg = const Color(0xfffcfcfc),
this.barsBg = const Color(0xffffffff),
this.linkBg = const Color(0xffe9f2ff),
this.accentPrimary = const Color(0xff005FFF),
this.accentError = const Color(0xffFF3842),
this.accentInfo = const Color(0xff20E070),
this.highlight = const Color(0xfffbf4dd),
this.overlay = const Color.fromRGBO(0, 0, 0, 0.2),
this.overlayDark = const Color.fromRGBO(0, 0, 0, 0.6),
@@ -604,17 +640,17 @@ class ColorTheme {
/// Initialise with dark theme
ColorTheme.dark({
this.black = const Color(0xffffffff),
this.grey = const Color(0xff7a7a7a),
this.greyGainsboro = const Color(0xff2d2f2f),
this.greyWhisper = const Color(0xff1c1e22),
this.whiteSmoke = const Color(0xff13151b),
this.whiteSnow = const Color(0xff070A0D),
this.white = const Color(0xff101418),
this.blueAlice = const Color(0xff00193D),
this.accentBlue = const Color(0xff005FFF),
this.accentRed = const Color(0xffFF3742),
this.accentGreen = const Color(0xff20E070),
this.textHighEmphasis = const Color(0xffffffff),
this.textLowEmphasis = const Color(0xff7a7a7a),
this.disabled = const Color(0xff2d2f2f),
this.borders = const Color(0xff1c1e22),
this.inputBg = const Color(0xff13151b),
this.appBg = const Color(0xff070A0D),
this.barsBg = const Color(0xff101418),
this.linkBg = const Color(0xff00193D),
this.accentPrimary = const Color(0xff005FFF),
this.accentError = const Color(0xffFF3742),
this.accentInfo = const Color(0xff20E070),
this.borderTop = const Effect(
sigmaX: 0,
sigmaY: -1,
@@ -657,37 +693,37 @@ class ColorTheme {
}) : brightness = Brightness.dark;
///
final Color black;
final Color textHighEmphasis;
///
final Color grey;
final Color textLowEmphasis;
///
final Color greyGainsboro;
final Color disabled;
///
final Color greyWhisper;
final Color borders;
///
final Color whiteSmoke;
final Color inputBg;
///
final Color whiteSnow;
final Color appBg;
///
final Color white;
final Color barsBg;
///
final Color blueAlice;
final Color linkBg;
///
final Color accentBlue;
final Color accentPrimary;
///
final Color accentRed;
final Color accentError;
///
final Color accentGreen;
final Color accentInfo;
///
final Effect borderTop;
@@ -719,17 +755,17 @@ class ColorTheme {
/// Copy with theme
ColorTheme copyWith({
Brightness brightness = Brightness.light,
Color? black,
Color? grey,
Color? greyGainsboro,
Color? greyWhisper,
Color? whiteSmoke,
Color? whiteSnow,
Color? white,
Color? blueAlice,
Color? accentBlue,
Color? accentRed,
Color? accentGreen,
Color? textHighEmphasis,
Color? textLowEmphasis,
Color? disabled,
Color? borders,
Color? inputBg,
Color? appBg,
Color? barsBg,
Color? linkBg,
Color? accentPrimary,
Color? accentError,
Color? accentInfo,
Effect? borderTop,
Effect? borderBottom,
Effect? shadowIconButton,
@@ -741,17 +777,17 @@ class ColorTheme {
}) =>
brightness == Brightness.light
? ColorTheme.light(
black: black ?? this.black,
grey: grey ?? this.grey,
greyGainsboro: greyGainsboro ?? this.greyGainsboro,
greyWhisper: greyWhisper ?? this.greyWhisper,
whiteSmoke: whiteSmoke ?? this.whiteSmoke,
whiteSnow: whiteSnow ?? this.whiteSnow,
white: white ?? this.white,
blueAlice: blueAlice ?? this.blueAlice,
accentBlue: accentBlue ?? this.accentBlue,
accentRed: accentRed ?? this.accentRed,
accentGreen: accentGreen ?? this.accentGreen,
textHighEmphasis: textHighEmphasis ?? this.textHighEmphasis,
textLowEmphasis: textLowEmphasis ?? this.textLowEmphasis,
disabled: disabled ?? this.disabled,
borders: borders ?? this.borders,
inputBg: inputBg ?? this.inputBg,
appBg: appBg ?? this.appBg,
barsBg: barsBg ?? this.barsBg,
linkBg: linkBg ?? this.linkBg,
accentPrimary: accentPrimary ?? this.accentPrimary,
accentError: accentError ?? this.accentError,
accentInfo: accentInfo ?? this.accentInfo,
borderTop: borderTop ?? this.borderTop,
borderBottom: borderBottom ?? this.borderBottom,
shadowIconButton: shadowIconButton ?? this.shadowIconButton,
@@ -762,17 +798,17 @@ class ColorTheme {
bgGradient: bgGradient ?? this.bgGradient,
)
: ColorTheme.dark(
black: black ?? this.black,
grey: grey ?? this.grey,
greyGainsboro: greyGainsboro ?? this.greyGainsboro,
greyWhisper: greyWhisper ?? this.greyWhisper,
whiteSmoke: whiteSmoke ?? this.whiteSmoke,
whiteSnow: whiteSnow ?? this.whiteSnow,
white: white ?? this.white,
blueAlice: blueAlice ?? this.blueAlice,
accentBlue: accentBlue ?? this.accentBlue,
accentRed: accentRed ?? this.accentRed,
accentGreen: accentGreen ?? this.accentGreen,
textHighEmphasis: textHighEmphasis ?? this.textHighEmphasis,
textLowEmphasis: textLowEmphasis ?? this.textLowEmphasis,
disabled: disabled ?? this.disabled,
borders: borders ?? this.borders,
inputBg: inputBg ?? this.inputBg,
appBg: appBg ?? this.appBg,
barsBg: barsBg ?? this.barsBg,
linkBg: linkBg ?? this.linkBg,
accentPrimary: accentPrimary ?? this.accentPrimary,
accentError: accentError ?? this.accentError,
accentInfo: accentInfo ?? this.accentInfo,
borderTop: borderTop ?? this.borderTop,
borderBottom: borderBottom ?? this.borderBottom,
shadowIconButton: shadowIconButton ?? this.shadowIconButton,
@@ -787,17 +823,17 @@ class ColorTheme {
ColorTheme merge(ColorTheme? other) {
if (other == null) return this;
return copyWith(
black: other.black,
grey: other.grey,
greyGainsboro: other.greyGainsboro,
greyWhisper: other.greyWhisper,
whiteSmoke: other.whiteSmoke,
whiteSnow: other.whiteSnow,
white: other.white,
blueAlice: other.blueAlice,
accentBlue: other.accentBlue,
accentRed: other.accentRed,
accentGreen: other.accentGreen,
textHighEmphasis: other.textHighEmphasis,
textLowEmphasis: other.textLowEmphasis,
disabled: other.disabled,
borders: other.borders,
inputBg: other.inputBg,
appBg: other.appBg,
barsBg: other.barsBg,
linkBg: other.linkBg,
accentPrimary: other.accentPrimary,
accentError: other.accentError,
accentInfo: other.accentInfo,
highlight: other.highlight,
overlay: other.overlay,
overlayDark: other.overlayDark,
@@ -1282,3 +1318,393 @@ class Effect {
blur: blur ?? this.blur,
);
}
/// Overrides the default style of [GalleryHeader] descendants.
///
/// See also:
///
/// * [GalleryHeaderThemeData], which is used to configure this theme.
class GalleryHeaderTheme extends InheritedTheme {
/// Creates an [GalleryHeaderTheme].
///
/// The [data] parameter must not be null.
const GalleryHeaderTheme({
Key? key,
required this.data,
required Widget child,
}) : super(key: key, child: child);
/// The configuration of this theme.
final GalleryHeaderThemeData data;
/// The closest instance of this class that encloses the given context.
///
/// If there is no enclosing [GalleryHeaderTheme] widget, then
/// [StreamChatThemeData.galleryHeaderTheme] is used.
///
/// Typical usage is as follows:
///
/// ```dart
/// ImageHeaderTheme theme = ImageHeaderTheme.of(context);
/// ```
static GalleryHeaderThemeData of(BuildContext context) {
final galleryHeaderTheme =
context.dependOnInheritedWidgetOfExactType<GalleryHeaderTheme>();
return galleryHeaderTheme?.data ??
StreamChatTheme.of(context).galleryHeaderTheme;
}
@override
Widget wrap(BuildContext context, Widget child) =>
GalleryHeaderTheme(data: data, child: child);
@override
bool updateShouldNotify(GalleryHeaderTheme oldWidget) =>
data != oldWidget.data;
}
/// A style that overrides the default appearance of [GalleryHeader]s when used
/// with [GalleryHeaderTheme] or with the overall [StreamChatTheme]'s
/// [StreamChatThemeData.galleryHeaderTheme].
///
/// See also:
///
/// * [GalleryHeaderTheme], the theme which is configured with this class.
/// * [StreamChatThemeData.galleryHeaderTheme], which can be used to override
/// the default style for [GalleryHeader]s below the overall [StreamChatTheme].
class GalleryHeaderThemeData with Diagnosticable {
/// Creates an [GalleryHeaderThemeData].
const GalleryHeaderThemeData({
this.closeButtonColor,
this.backgroundColor,
this.iconMenuPointColor,
this.titleTextStyle,
this.subtitleTextStyle,
this.bottomSheetBarrierColor,
});
/// The color of the "close" button.
///
/// Defaults to [ColorTheme.textHighEmphasis].
final Color? closeButtonColor;
/// The background color of the [GalleryHeader] widget.
///
/// Defaults to [ChannelHeaderTheme.color].
final Color? backgroundColor;
/// Defaults to [ColorTheme.textHighEmphasis].
final Color? iconMenuPointColor;
/// The [TextStyle] to use for the [GalleryHeader] title text.
///
/// Defaults to [TextTheme.headlineBold].
final TextStyle? titleTextStyle;
/// The [TextStyle] to use for the [GalleryHeader] subtitle text.
///
/// Defaults to [ChannelPreviewTheme.subtitle].
final TextStyle? subtitleTextStyle;
///
final Color? bottomSheetBarrierColor;
/// Copies this [GalleryHeaderThemeData] to another.
GalleryHeaderThemeData copyWith({
Color? closeButtonColor,
Color? backgroundColor,
Color? iconMenuPointColor,
TextStyle? titleTextStyle,
TextStyle? subtitleTextStyle,
Color? bottomSheetBarrierColor,
}) =>
GalleryHeaderThemeData(
closeButtonColor: closeButtonColor ?? this.closeButtonColor,
backgroundColor: backgroundColor ?? this.backgroundColor,
iconMenuPointColor: iconMenuPointColor ?? this.iconMenuPointColor,
titleTextStyle: titleTextStyle ?? this.titleTextStyle,
subtitleTextStyle: subtitleTextStyle ?? this.subtitleTextStyle,
bottomSheetBarrierColor:
bottomSheetBarrierColor ?? this.bottomSheetBarrierColor,
);
/// Linearly interpolate between two [GalleryHeader] themes.
///
/// All the properties must be non-null.
GalleryHeaderThemeData lerp(
GalleryHeaderThemeData a,
GalleryHeaderThemeData b,
double t,
) =>
GalleryHeaderThemeData(
closeButtonColor: Color.lerp(a.closeButtonColor, b.closeButtonColor, t),
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
iconMenuPointColor:
Color.lerp(a.iconMenuPointColor, b.iconMenuPointColor, t),
titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t),
subtitleTextStyle:
TextStyle.lerp(a.subtitleTextStyle, b.subtitleTextStyle, t),
bottomSheetBarrierColor:
Color.lerp(a.bottomSheetBarrierColor, b.bottomSheetBarrierColor, t),
);
/// Merges one [GalleryHeaderThemeData] with the another
GalleryHeaderThemeData merge(GalleryHeaderThemeData? other) {
if (other == null) return this;
return copyWith(
closeButtonColor: other.closeButtonColor,
backgroundColor: other.backgroundColor,
iconMenuPointColor: other.iconMenuPointColor,
titleTextStyle: other.titleTextStyle,
subtitleTextStyle: other.subtitleTextStyle,
bottomSheetBarrierColor: other.bottomSheetBarrierColor,
);
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is GalleryHeaderThemeData &&
runtimeType == other.runtimeType &&
closeButtonColor == other.closeButtonColor &&
backgroundColor == other.backgroundColor &&
iconMenuPointColor == other.iconMenuPointColor &&
titleTextStyle == other.titleTextStyle &&
subtitleTextStyle == other.subtitleTextStyle &&
bottomSheetBarrierColor == other.bottomSheetBarrierColor;
@override
int get hashCode =>
closeButtonColor.hashCode ^
backgroundColor.hashCode ^
iconMenuPointColor.hashCode ^
titleTextStyle.hashCode ^
subtitleTextStyle.hashCode ^
bottomSheetBarrierColor.hashCode;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(ColorProperty('closeButtonColor', closeButtonColor))
..add(ColorProperty('backgroundColor', backgroundColor))
..add(ColorProperty('iconMenuPointColor', iconMenuPointColor))
..add(DiagnosticsProperty('titleTextStyle', titleTextStyle))
..add(DiagnosticsProperty('subtitleTextStyle', subtitleTextStyle))
..add(ColorProperty('bottomSheetBarrierColor', bottomSheetBarrierColor));
}
}
/// Overrides the default style of [GalleryFooter] descendants.
///
/// See also:
///
/// * [GalleryFooterThemeData], which is used to configure this theme.
class GalleryFooterTheme extends InheritedTheme {
/// Creates an [GalleryFooterTheme].
///
/// The [data] parameter must not be null.
const GalleryFooterTheme({
Key? key,
required this.data,
required Widget child,
}) : super(key: key, child: child);
/// The configuration of this theme.
final GalleryFooterThemeData data;
/// The closest instance of this class that encloses the given context.
///
/// If there is no enclosing [GalleryFooterTheme] widget, then
/// [StreamChatThemeData.galleryFooterTheme] is used.
///
/// Typical usage is as follows:
///
/// ```dart
/// ImageFooterTheme theme = ImageFooterTheme.of(context);
/// ```
static GalleryFooterThemeData of(BuildContext context) {
final imageFooterTheme =
context.dependOnInheritedWidgetOfExactType<GalleryFooterTheme>();
return imageFooterTheme?.data ??
StreamChatTheme.of(context).galleryFooterTheme;
}
@override
Widget wrap(BuildContext context, Widget child) =>
GalleryFooterTheme(data: data, child: child);
@override
bool updateShouldNotify(GalleryFooterTheme oldWidget) =>
data != oldWidget.data;
}
/// A style that overrides the default appearance of [GalleryFooter]s when used
/// with [GalleryFooterTheme] or with the overall [StreamChatTheme]'s
/// [StreamChatThemeData.galleryFooterTheme].
///
/// See also:
///
/// * [GalleryFooterTheme], the theme which is configured with this class.
/// * [StreamChatThemeData.galleryFooterTheme], which can be used to override
/// the default style for [GalleryFooter]s below the overall [StreamChatTheme].
class GalleryFooterThemeData with Diagnosticable {
/// Creates an [GalleryFooterThemeData].
const GalleryFooterThemeData({
this.backgroundColor,
this.shareIconColor,
this.titleTextStyle,
this.gridIconButtonColor,
this.bottomSheetBarrierColor,
this.bottomSheetBackgroundColor,
this.bottomSheetPhotosTextStyle,
this.bottomSheetCloseIconColor,
});
/// The background color for the [GalleryFooter] widget.
///
/// Defaults to [ColorTheme.barsBg].
final Color? backgroundColor;
/// The color for the "share" icon.
///
/// Defaults to [ColorTheme.textHighEmphasis].
final Color? shareIconColor;
/// The [TextStyle] to use for the [GalleryFooter] title text.
///
/// Defaults to [TextTheme.headlineBold].
final TextStyle? titleTextStyle;
/// The color to use for the "grid" icon.
///
/// Defaults to [ColorTheme.textHighEmphasis].
final Color? gridIconButtonColor;
/// The color to use behind the bottom sheet.
///
/// Defaults to [ColorTheme.overlay].
final Color? bottomSheetBarrierColor;
/// The background color to use for the bottom sheet.
///
/// Defaults to [ColorTheme.barsBg].
final Color? bottomSheetBackgroundColor;
/// The [TextStyle] to use for the "photos" text in the bottom sheet.
///
/// Defaults to [TextTheme.headlineBold].
final TextStyle? bottomSheetPhotosTextStyle;
/// The color to use for the "close" icon.
///
/// Defaults to [ColorTheme.textHighEmphasis].
final Color? bottomSheetCloseIconColor;
/// Copies this [GalleryFooterThemeData] to another.
GalleryFooterThemeData copyWith({
Color? backgroundColor,
Color? shareIconColor,
TextStyle? titleTextStyle,
Color? gridIconButtonColor,
Color? bottomSheetBarrierColor,
Color? bottomSheetBackgroundColor,
TextStyle? bottomSheetPhotosTextStyle,
Color? bottomSheetCloseIconColor,
}) =>
GalleryFooterThemeData(
backgroundColor: backgroundColor ?? this.backgroundColor,
shareIconColor: shareIconColor ?? this.shareIconColor,
titleTextStyle: titleTextStyle ?? this.titleTextStyle,
gridIconButtonColor: gridIconButtonColor ?? this.gridIconButtonColor,
bottomSheetBarrierColor:
bottomSheetBarrierColor ?? this.bottomSheetBarrierColor,
bottomSheetBackgroundColor:
bottomSheetBackgroundColor ?? this.bottomSheetBackgroundColor,
bottomSheetPhotosTextStyle:
bottomSheetPhotosTextStyle ?? this.bottomSheetPhotosTextStyle,
bottomSheetCloseIconColor:
bottomSheetCloseIconColor ?? this.bottomSheetCloseIconColor,
);
/// Linearly interpolate between two [GalleryFooter] themes.
///
/// All the properties must be non-null.
GalleryFooterThemeData lerp(
GalleryFooterThemeData a,
GalleryFooterThemeData b,
double t,
) =>
GalleryFooterThemeData(
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
shareIconColor: Color.lerp(a.shareIconColor, b.shareIconColor, t),
titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t),
gridIconButtonColor:
Color.lerp(a.gridIconButtonColor, b.gridIconButtonColor, t),
bottomSheetBarrierColor:
Color.lerp(a.bottomSheetBarrierColor, b.bottomSheetBarrierColor, t),
bottomSheetBackgroundColor: Color.lerp(
a.bottomSheetBackgroundColor, b.bottomSheetBackgroundColor, t),
bottomSheetPhotosTextStyle: TextStyle.lerp(
a.bottomSheetPhotosTextStyle, b.bottomSheetPhotosTextStyle, t),
bottomSheetCloseIconColor: Color.lerp(
a.bottomSheetCloseIconColor, b.bottomSheetCloseIconColor, t),
);
/// Merges one [GalleryFooterThemeData] with the another
GalleryFooterThemeData merge(GalleryFooterThemeData? other) {
if (other == null) return this;
return copyWith(
backgroundColor: other.backgroundColor,
bottomSheetBarrierColor: other.bottomSheetBarrierColor,
bottomSheetBackgroundColor: other.bottomSheetBackgroundColor,
bottomSheetCloseIconColor: other.bottomSheetCloseIconColor,
bottomSheetPhotosTextStyle: other.bottomSheetPhotosTextStyle,
gridIconButtonColor: other.gridIconButtonColor,
titleTextStyle: other.titleTextStyle,
shareIconColor: other.shareIconColor,
);
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is GalleryFooterThemeData &&
runtimeType == other.runtimeType &&
backgroundColor == other.backgroundColor &&
shareIconColor == other.shareIconColor &&
titleTextStyle == other.titleTextStyle &&
gridIconButtonColor == other.gridIconButtonColor &&
bottomSheetBarrierColor == other.bottomSheetBarrierColor &&
bottomSheetBackgroundColor == other.bottomSheetBackgroundColor &&
bottomSheetPhotosTextStyle == other.bottomSheetPhotosTextStyle &&
bottomSheetCloseIconColor == other.bottomSheetCloseIconColor;
@override
int get hashCode =>
backgroundColor.hashCode ^
shareIconColor.hashCode ^
titleTextStyle.hashCode ^
gridIconButtonColor.hashCode ^
bottomSheetBarrierColor.hashCode ^
bottomSheetBackgroundColor.hashCode ^
bottomSheetPhotosTextStyle.hashCode ^
bottomSheetCloseIconColor.hashCode;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(ColorProperty('backgroundColor', backgroundColor))
..add(ColorProperty('shareIconColor', shareIconColor))
..add(DiagnosticsProperty('titleTextStyle', titleTextStyle))
..add(ColorProperty('gridIconButtonColor', gridIconButtonColor))
..add(ColorProperty('bottomSheetBarrierColor', bottomSheetBarrierColor))
..add(ColorProperty(
'bottomSheetBackgroundColor', bottomSheetBackgroundColor))
..add(DiagnosticsProperty(
'bottomSheetPhotosTextStyle', bottomSheetPhotosTextStyle))
..add(ColorProperty(
'bottomSheetCloseIconColor', bottomSheetCloseIconColor));
}
}
@@ -151,9 +151,8 @@ class _SwipeableState extends State<Swipeable> with TickerProviderStateMixin {
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: StreamChatTheme.of(context)
.colorTheme
.greyGainsboro,
color:
StreamChatTheme.of(context).colorTheme.disabled,
),
),
child: widget.backgroundIcon,
@@ -32,7 +32,7 @@ class SystemMessage extends StatelessWidget {
textAlign: TextAlign.center,
softWrap: true,
style: theme.textTheme.captionBold.copyWith(
color: theme.colorTheme.grey,
color: theme.colorTheme.textLowEmphasis,
),
),
);
@@ -59,7 +59,7 @@ class UploadProgressIndicator extends StatelessWidget {
'${_percentage.toInt()}%',
style: textStyle ??
theme.textTheme.footnote.copyWith(
color: theme.colorTheme.white,
color: theme.colorTheme.barsBg,
),
),
],
@@ -60,7 +60,7 @@ class UrlAttachment extends StatelessWidget {
borderRadius: const BorderRadius.only(
topRight: Radius.circular(16),
),
color: chatThemeData.colorTheme.blueAlice,
color: chatThemeData.colorTheme.linkBg,
),
child: Padding(
padding: const EdgeInsets.only(
@@ -71,7 +71,7 @@ class UrlAttachment extends StatelessWidget {
child: Text(
hostDisplayName,
style: chatThemeData.textTheme.bodyBold.copyWith(
color: chatThemeData.colorTheme.accentBlue,
color: chatThemeData.colorTheme.accentPrimary,
),
),
),
@@ -70,7 +70,7 @@ class UserAvatar extends StatelessWidget {
constraints: constraints ??
streamChatTheme.ownMessageTheme.avatarTheme?.constraints,
decoration: BoxDecoration(
color: streamChatTheme.colorTheme.accentBlue,
color: streamChatTheme.colorTheme.accentPrimary,
),
child: hasImage
? CachedNetworkImage(
@@ -95,7 +95,7 @@ class UserAvatar extends StatelessWidget {
child: Container(
constraints: constraints ??
streamChatTheme.ownMessageTheme.avatarTheme?.constraints,
color: selectionColor ?? streamChatTheme.colorTheme.accentBlue,
color: selectionColor ?? streamChatTheme.colorTheme.accentPrimary,
child: Padding(
padding: EdgeInsets.all(selectionThickness),
child: avatar,
@@ -115,7 +115,7 @@ class UserAvatar extends StatelessWidget {
alignment: onlineIndicatorAlignment,
child: Material(
type: MaterialType.circle,
color: streamChatTheme.colorTheme.white,
color: streamChatTheme.colorTheme.barsBg,
child: Container(
margin: const EdgeInsets.all(2),
constraints: onlineIndicatorConstraints ??
@@ -125,7 +125,7 @@ class UserAvatar extends StatelessWidget {
),
child: Material(
shape: const CircleBorder(),
color: streamChatTheme.colorTheme.accentGreen,
color: streamChatTheme.colorTheme.accentInfo,
),
),
),
@@ -76,7 +76,7 @@ class UserItem extends StatelessWidget {
),
trailing: selected
? StreamSvgIcon.checkSend(
color: chatThemeData.colorTheme.accentBlue,
color: chatThemeData.colorTheme.accentPrimary,
)
: null,
title: Text(
@@ -94,8 +94,8 @@ class UserItem extends StatelessWidget {
? context.translations.userOnlineText
: context.translations.userLastOnlineText +
Jiffy(user.lastActive).fromNow(),
style: chatTheme.textTheme.footnote
.copyWith(color: chatTheme.colorTheme.black.withOpacity(.5)),
style: chatTheme.textTheme.footnote.copyWith(
color: chatTheme.colorTheme.textHighEmphasis.withOpacity(.5)),
);
}
}
@@ -287,7 +287,7 @@ class _UserListViewState extends State<UserListView>
final chatThemeData = StreamChatTheme.of(context);
return Container(
key: ValueKey<String>('HEADER-$header'),
color: chatThemeData.colorTheme.black.withOpacity(0.05),
color: chatThemeData.colorTheme.textHighEmphasis.withOpacity(0.05),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
child: Text(
@@ -295,7 +295,7 @@ class _UserListViewState extends State<UserListView>
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14.5,
color: chatThemeData.colorTheme.grey,
color: chatThemeData.colorTheme.textLowEmphasis,
),
),
),
@@ -387,7 +387,7 @@ class _UserListViewState extends State<UserListView>
return Container(
color: StreamChatTheme.of(context)
.colorTheme
.accentRed
.accentError
.withOpacity(.2),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
@@ -410,6 +410,6 @@ class _UserListViewState extends State<UserListView>
Widget _separatorBuilder(context, i) => Container(
height: 1,
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
color: StreamChatTheme.of(context).colorTheme.borders,
);
}
@@ -29,7 +29,7 @@ Future<bool?> showConfirmationDialog(
final chatThemeData = StreamChatTheme.of(context);
return showModalBottomSheet(
useRootNavigator: false,
backgroundColor: chatThemeData.colorTheme.white,
backgroundColor: chatThemeData.colorTheme.barsBg,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
@@ -73,7 +73,7 @@ Future<bool?> showConfirmationDialog(
child: Text(
cancelText,
style: chatThemeData.textTheme.bodyBold.copyWith(
color: chatThemeData.colorTheme.black
color: chatThemeData.colorTheme.textHighEmphasis
.withOpacity(0.5)),
),
),
@@ -89,7 +89,7 @@ Future<bool?> showConfirmationDialog(
child: Text(
okText,
style: chatThemeData.textTheme.bodyBold.copyWith(
color: chatThemeData.colorTheme.accentRed),
color: chatThemeData.colorTheme.accentError),
),
),
),
@@ -114,7 +114,8 @@ Future<bool?> showInfoDialog(
final chatThemeData = StreamChatTheme.of(context);
return showModalBottomSheet(
useRootNavigator: false,
backgroundColor: theme?.colorTheme.white ?? chatThemeData.colorTheme.white,
backgroundColor:
theme?.colorTheme.barsBg ?? chatThemeData.colorTheme.barsBg,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
@@ -145,8 +146,8 @@ Future<bool?> showInfoDialog(
height: 36,
),
Container(
color: theme?.colorTheme.black.withOpacity(.08) ??
chatThemeData.colorTheme.black.withOpacity(.08),
color: theme?.colorTheme.textHighEmphasis.withOpacity(.08) ??
chatThemeData.colorTheme.textHighEmphasis.withOpacity(.08),
height: 1,
),
Center(
@@ -157,8 +158,8 @@ Future<bool?> showInfoDialog(
child: Text(
okText,
style: TextStyle(
color: theme?.colorTheme.black.withOpacity(0.5) ??
chatThemeData.colorTheme.accentBlue,
color: theme?.colorTheme.textHighEmphasis.withOpacity(0.5) ??
chatThemeData.colorTheme.accentPrimary,
fontWeight: FontWeight.w400,
),
),
@@ -16,21 +16,25 @@ class IVideoService {
/// compress video from [path]
/// compress video from [path] return [Future<MediaInfo>]
///
/// you can choose its quality by [quality],
/// determine whether to delete his source file by [deleteOrigin]
/// optional parameters [startTime] [duration] [includeAudio] [frameRate]
/// you can choose its [quality] and [frameRate]
///
/// ## example
/// ```dart
/// final info = await _flutterVideoCompress.compressVideo(
/// file.path,
/// deleteOrigin: true,
/// );
/// debugPrint(info.toJson());
/// ```
Future<MediaInfo?> compressVideo(String path) async => _lock.synchronized(
Future<MediaInfo?> compressVideo(
String path, {
int frameRate = 30,
VideoQuality quality = VideoQuality.DefaultQuality,
}) async =>
_lock.synchronized(
() => VideoCompress.compressVideo(
path,
frameRate: frameRate,
quality: quality,
),
);
@@ -94,8 +94,8 @@ class _VideoThumbnailImageState extends State<VideoThumbnailImage> {
constraints: const BoxConstraints.expand(),
child: widget.placeholderBuilder?.call(context) ??
Shimmer.fromColors(
baseColor: _streamChatTheme.colorTheme.greyGainsboro,
highlightColor: _streamChatTheme.colorTheme.whiteSmoke,
baseColor: _streamChatTheme.colorTheme.disabled,
highlightColor: _streamChatTheme.colorTheme.inputBg,
child: Image.asset(
'images/placeholder.png',
fit: BoxFit.cover,
@@ -2,8 +2,8 @@ export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
export 'src/attachment/attachment.dart';
export 'src/back_button.dart';
export 'src/channel_avatar.dart';
export 'src/channel_header.dart';
export 'src/channel_image.dart';
export 'src/channel_list_header.dart';
export 'src/channel_list_view.dart';
export 'src/channel_name.dart';
@@ -12,8 +12,8 @@ export 'src/connection_status_builder.dart';
export 'src/date_divider.dart';
export 'src/deleted_message.dart';
export 'src/full_screen_media.dart';
export 'src/image_footer.dart';
export 'src/image_header.dart';
export 'src/gallery_footer.dart';
export 'src/gallery_header.dart';
export 'src/info_tile.dart';
export 'src/localization/stream_chat_localizations.dart';
export 'src/mention_tile.dart';
+3 -3
View File
@@ -1,7 +1,7 @@
name: stream_chat_flutter
homepage: https://github.com/GetStream/stream-chat-flutter
description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter.
version: 2.0.0-nullsafety.8
version: 2.0.0
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -26,7 +26,7 @@ dependencies:
flutter_svg: ^0.22.0
http_parser: ^4.0.0
image_gallery_saver: ^1.6.9
image_picker: ^0.8.0
image_picker: ^0.8.2
jiffy: ^4.1.0
lottie: ^1.0.1
meta: ^1.3.0
@@ -37,7 +37,7 @@ dependencies:
scrollable_positioned_list: ^0.2.0-nullsafety.0
share_plus: ^2.0.3
shimmer: ^2.0.0
stream_chat_flutter_core: ^2.0.0-nullsafety.8
stream_chat_flutter_core: ^2.0.0
substring_highlight: ^1.0.26
synchronized: ^3.0.0
url_launcher: ^6.0.3
@@ -14,10 +14,12 @@ void main() {
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final user = OwnUser(id: 'user-id');
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(() => clientState.user).thenReturn(user);
when(() => clientState.userStream).thenAnswer((_) => Stream.value(user));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -63,7 +65,7 @@ void main() {
));
expect(find.text('test'), findsOneWidget);
expect(find.byType(ChannelImage), findsOneWidget);
expect(find.byType(ChannelAvatar), findsOneWidget);
expect(find.byType(StreamBackButton), findsOneWidget);
expect(find.byType(ChannelInfo), findsOneWidget);
},
@@ -76,10 +78,12 @@ void main() {
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final user = OwnUser(id: 'user-id');
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(() => clientState.user).thenReturn(user);
when(() => clientState.userStream).thenAnswer((_) => Stream.value(user));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -141,10 +145,12 @@ void main() {
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final user = OwnUser(id: 'user-id');
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(() => clientState.user).thenReturn(user);
when(() => clientState.userStream).thenAnswer((_) => Stream.value(user));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -207,10 +213,12 @@ void main() {
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final user = OwnUser(id: 'user-id');
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(() => clientState.user).thenReturn(user);
when(() => clientState.userStream).thenAnswer((_) => Stream.value(user));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -263,7 +271,7 @@ void main() {
expect(find.text('test'), findsNothing);
expect(find.byType(StreamBackButton), findsNothing);
expect(find.byType(ChannelImage), findsNothing);
expect(find.byType(ChannelAvatar), findsNothing);
expect(find.byType(ChannelInfo), findsNothing);
expect(find.text('leading'), findsOneWidget);
expect(find.text('title'), findsOneWidget);
@@ -281,10 +289,12 @@ void main() {
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final user = OwnUser(id: 'user-id');
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(() => clientState.user).thenReturn(user);
when(() => clientState.userStream).thenAnswer((_) => Stream.value(user));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -346,10 +356,12 @@ void main() {
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final user = OwnUser(id: 'user-id');
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(() => clientState.user).thenReturn(user);
when(() => clientState.userStream).thenAnswer((_) => Stream.value(user));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -403,7 +415,7 @@ void main() {
));
await tester.tap(find.byType(StreamBackButton));
await tester.tap(find.byType(ChannelImage));
await tester.tap(find.byType(ChannelAvatar));
await tester.tap(find.byType(ChannelName));
expect(backPressed, true);
@@ -2,7 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:stream_chat_flutter/src/group_image.dart';
import 'package:stream_chat_flutter/src/group_avatar.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
@@ -35,7 +35,7 @@ void main() {
child: StreamChannel(
channel: channel,
child: const Scaffold(
body: ChannelImage(),
body: ChannelAvatar(),
),
),
),
@@ -74,7 +74,7 @@ void main() {
userId: 'user-id2',
user: User(
id: 'user-id2',
extraData: {
extraData: const {
'image': 'testimage',
},
),
@@ -85,7 +85,7 @@ void main() {
userId: 'user-id2',
user: User(
id: 'user-id2',
extraData: {
extraData: const {
'image': 'testimage',
},
),
@@ -98,7 +98,7 @@ void main() {
when(() => clientState.usersStream).thenAnswer((i) => Stream.value({
'user-id2': User(
id: 'user-id2',
extraData: {
extraData: const {
'image': 'testimage',
},
),
@@ -113,7 +113,7 @@ void main() {
child: StreamChannel(
channel: channel,
child: const Scaffold(
body: ChannelImage(),
body: ChannelAvatar(),
),
),
),
@@ -132,9 +132,10 @@ void main() {
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final currentUser = OwnUser(id: 'user-id');
when(() => client.state).thenReturn(clientState);
when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(() => clientState.user).thenReturn(currentUser);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({
@@ -143,41 +144,12 @@ void main() {
when(() => channel.extraData).thenReturn({
'name': 'test',
});
when(() => channelState.membersStream).thenAnswer((i) => Stream.value([
Member(
userId: 'user-id',
user: User(
id: 'user-id',
extraData: {
'image': 'testimage1',
},
),
),
Member(
userId: 'user-id2',
user: User(
id: 'user-id2',
extraData: {
'image': 'testimage2',
},
),
),
Member(
userId: 'user-id3',
user: User(
id: 'user-id3',
extraData: {
'image': 'testimage3',
},
),
),
]));
when(() => channelState.members).thenReturn([
final members = [
Member(
userId: 'user-id',
user: User(
id: 'user-id',
extraData: {
extraData: const {
'image': 'testimage1',
},
),
@@ -186,7 +158,7 @@ void main() {
userId: 'user-id2',
user: User(
id: 'user-id2',
extraData: {
extraData: const {
'image': 'testimage2',
},
),
@@ -195,12 +167,15 @@ void main() {
userId: 'user-id3',
user: User(
id: 'user-id3',
extraData: {
extraData: const {
'image': 'testimage3',
},
),
),
]);
];
when(() => channelState.members).thenReturn(members);
when(() => channelState.membersStream)
.thenAnswer((_) => Stream.value(members));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
@@ -208,17 +183,18 @@ void main() {
child: StreamChannel(
channel: channel,
child: const Scaffold(
body: ChannelImage(),
body: ChannelAvatar(),
),
),
),
));
final image = tester.widget<GroupImage>(find.byType(GroupImage));
expect(image.images, [
'testimage2',
'testimage3',
]);
final image = tester.widget<GroupAvatar>(find.byType(GroupAvatar));
final otherMembers = members.where((it) => it.userId != currentUser.id);
expect(
image.members.map((it) => it.user?.id),
otherMembers.map((it) => it.user?.id),
);
},
);
@@ -249,7 +225,7 @@ void main() {
child: StreamChannel(
channel: channel,
child: const Scaffold(
body: ChannelImage(
body: ChannelAvatar(
selected: true,
),
),
@@ -13,11 +13,13 @@ void main() {
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final user = OwnUser(id: 'user-id');
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => channel.cid).thenReturn('cid');
when(() => client.state).thenReturn(clientState);
when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(() => clientState.user).thenReturn(user);
when(() => clientState.userStream).thenAnswer((_) => Stream.value(user));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
@@ -78,7 +80,7 @@ void main() {
expect(find.text('test name'), findsOneWidget);
expect(find.text('1'), findsOneWidget);
expect(find.text('hello'), findsOneWidget);
expect(find.byType(ChannelImage), findsOneWidget);
expect(find.byType(ChannelAvatar), findsOneWidget);
},
);
}
@@ -70,6 +70,7 @@ void main() {
StreamChat(
streamChatThemeData: theme,
client: client,
connectivityStream: Stream.value(ConnectivityResult.mobile),
child: StreamChannel(
showLoading: false,
channel: channel,
@@ -79,7 +80,6 @@ void main() {
),
),
),
connectivityStream: Stream.value(ConnectivityResult.mobile),
),
),
surfaceSize: const Size.square(200),
@@ -123,6 +123,7 @@ void main() {
StreamChat(
streamChatThemeData: theme,
client: client,
connectivityStream: Stream.value(ConnectivityResult.mobile),
child: StreamChannel(
showLoading: false,
channel: channel,
@@ -132,7 +133,6 @@ void main() {
),
),
),
connectivityStream: Stream.value(ConnectivityResult.mobile),
),
),
surfaceSize: const Size.square(200),
@@ -176,6 +176,7 @@ void main() {
StreamChat(
streamChatThemeData: theme,
client: client,
connectivityStream: Stream.value(ConnectivityResult.mobile),
child: StreamChannel(
showLoading: false,
channel: channel,
@@ -189,7 +190,6 @@ void main() {
),
),
),
connectivityStream: Stream.value(ConnectivityResult.mobile),
),
),
surfaceSize: const Size.square(200),
@@ -54,12 +54,12 @@ void main() {
)
]));
when(() => channelState.typingEvents).thenAnswer((i) => {
User(id: 'other-user', extraData: {'name': 'demo'}):
User(id: 'other-user', extraData: const {'name': 'demo'}):
Event(type: EventType.typingStart),
});
when(() => channelState.typingEventsStream)
.thenAnswer((i) => Stream.value({
User(id: 'other-user', extraData: {'name': 'demo'}):
User(id: 'other-user', extraData: const {'name': 'demo'}):
Event(type: EventType.typingStart),
}));
@@ -0,0 +1,185 @@
import 'package:flutter/material.dart' hide TextTheme;
import 'package:flutter_test/flutter_test.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:mocktail/mocktail.dart';
class MockStreamChatClient extends Mock implements StreamChatClient {}
void main() {
test('GalleryFooterThemeData copyWith, ==, hashCode basics', () {
expect(const GalleryFooterThemeData(),
const GalleryFooterThemeData().copyWith());
expect(const GalleryFooterThemeData().hashCode,
const GalleryFooterThemeData().copyWith().hashCode);
});
test(
'''Light GalleryFooterThemeData lerps completely to dark GalleryFooterThemeData''',
() {
expect(
const GalleryFooterThemeData().lerp(_galleryFooterThemeDataControl,
_galleryFooterThemeDataControlDark, 1),
_galleryFooterThemeDataControlDark);
});
test(
'''Light GalleryFooterThemeData lerps halfway to dark GalleryFooterThemeData''',
() {
expect(
const GalleryFooterThemeData().lerp(_galleryFooterThemeDataControl,
_galleryFooterThemeDataControlDark, 0.5),
_galleryFooterThemeDataControlMidLerp);
});
test(
'''Dark GalleryFooterThemeData lerps completely to light GalleryFooterThemeData''',
() {
expect(
const GalleryFooterThemeData().lerp(_galleryFooterThemeDataControlDark,
_galleryFooterThemeDataControl, 1),
_galleryFooterThemeDataControl);
});
test('Merging dark and light themes results in a dark theme', () {
expect(
_galleryFooterThemeDataControl
.merge(_galleryFooterThemeDataControlDark),
_galleryFooterThemeDataControlDark);
});
test('Merging dark and light themes results in a dark theme', () {
expect(
_galleryFooterThemeDataControlDark
.merge(_galleryFooterThemeDataControl),
_galleryFooterThemeDataControl);
});
testWidgets(
'Passing no GalleryFooterThemeData returns default light theme values',
(WidgetTester tester) async {
late BuildContext _context;
await tester.pumpWidget(
MaterialApp(
builder: (context, child) => StreamChat(
client: MockStreamChatClient(),
child: child,
),
home: Builder(
builder: (context) {
_context = context;
return Scaffold(
appBar: GalleryFooter(
message: Message(),
),
);
},
),
),
);
final imageFooterTheme = GalleryFooterTheme.of(_context);
expect(imageFooterTheme.backgroundColor,
_galleryFooterThemeDataControl.backgroundColor);
expect(imageFooterTheme.shareIconColor,
_galleryFooterThemeDataControl.shareIconColor);
expect(imageFooterTheme.titleTextStyle,
_galleryFooterThemeDataControl.titleTextStyle);
expect(imageFooterTheme.gridIconButtonColor,
_galleryFooterThemeDataControl.gridIconButtonColor);
expect(imageFooterTheme.bottomSheetBarrierColor,
_galleryFooterThemeDataControl.bottomSheetBarrierColor);
expect(imageFooterTheme.bottomSheetBackgroundColor,
_galleryFooterThemeDataControl.bottomSheetBackgroundColor);
expect(imageFooterTheme.bottomSheetCloseIconColor,
_galleryFooterThemeDataControl.bottomSheetCloseIconColor);
expect(imageFooterTheme.bottomSheetPhotosTextStyle,
_galleryFooterThemeDataControl.bottomSheetPhotosTextStyle);
});
testWidgets(
'Passing no GalleryFooterThemeData returns default dark theme values',
(WidgetTester tester) async {
late BuildContext _context;
await tester.pumpWidget(
MaterialApp(
builder: (context, child) => StreamChat(
client: MockStreamChatClient(),
streamChatThemeData: StreamChatThemeData.dark(),
child: child,
),
home: Builder(
builder: (context) {
_context = context;
return Scaffold(
appBar: GalleryFooter(
message: Message(),
),
);
},
),
),
);
final imageFooterTheme = GalleryFooterTheme.of(_context);
expect(imageFooterTheme.backgroundColor,
_galleryFooterThemeDataControlDark.backgroundColor);
expect(imageFooterTheme.shareIconColor,
_galleryFooterThemeDataControlDark.shareIconColor);
expect(imageFooterTheme.titleTextStyle,
_galleryFooterThemeDataControlDark.titleTextStyle);
expect(imageFooterTheme.gridIconButtonColor,
_galleryFooterThemeDataControlDark.gridIconButtonColor);
expect(imageFooterTheme.bottomSheetBarrierColor,
_galleryFooterThemeDataControlDark.bottomSheetBarrierColor);
expect(imageFooterTheme.bottomSheetBackgroundColor,
_galleryFooterThemeDataControlDark.bottomSheetBackgroundColor);
expect(imageFooterTheme.bottomSheetCloseIconColor,
_galleryFooterThemeDataControlDark.bottomSheetCloseIconColor);
expect(imageFooterTheme.bottomSheetPhotosTextStyle,
_galleryFooterThemeDataControlDark.bottomSheetPhotosTextStyle);
});
}
// Light theme control
final _galleryFooterThemeDataControl = GalleryFooterThemeData(
backgroundColor: ColorTheme.light().barsBg,
shareIconColor: ColorTheme.light().textHighEmphasis,
titleTextStyle: TextTheme.light().headlineBold,
gridIconButtonColor: ColorTheme.light().textHighEmphasis,
bottomSheetBackgroundColor: ColorTheme.light().barsBg,
bottomSheetBarrierColor: ColorTheme.light().overlay,
bottomSheetCloseIconColor: ColorTheme.light().textHighEmphasis,
bottomSheetPhotosTextStyle: TextTheme.light().headlineBold,
);
// Mid-lerp theme control
const _galleryFooterThemeDataControlMidLerp = GalleryFooterThemeData(
backgroundColor: Color(0xff87898b),
shareIconColor: Color(0xff7f7f7f),
titleTextStyle: TextStyle(
color: Color(0xff7f7f7f),
fontSize: 16,
fontWeight: FontWeight.bold,
),
gridIconButtonColor: Color(0xff7f7f7f),
bottomSheetBarrierColor: Color(0x4c000000),
bottomSheetBackgroundColor: Color(0xff87898b),
bottomSheetPhotosTextStyle: TextStyle(
color: Color(0xff7f7f7f),
fontSize: 16,
fontWeight: FontWeight.bold,
),
bottomSheetCloseIconColor: Color(0xff7f7f7f),
);
// Dark theme control
final _galleryFooterThemeDataControlDark = GalleryFooterThemeData(
backgroundColor: ColorTheme.dark().barsBg,
shareIconColor: ColorTheme.dark().textHighEmphasis,
titleTextStyle: TextTheme.dark().headlineBold,
gridIconButtonColor: ColorTheme.dark().textHighEmphasis,
bottomSheetBackgroundColor: ColorTheme.dark().barsBg,
bottomSheetBarrierColor: ColorTheme.dark().overlay,
bottomSheetCloseIconColor: ColorTheme.dark().textHighEmphasis,
bottomSheetPhotosTextStyle: TextTheme.dark().headlineBold,
);
@@ -0,0 +1,190 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:mocktail/mocktail.dart';
class MockStreamChatClient extends Mock implements StreamChatClient {}
void main() {
test('GalleryHeaderThemeData copyWith, ==, hashCode basics', () {
expect(const GalleryHeaderThemeData(),
const GalleryHeaderThemeData().copyWith());
expect(const GalleryHeaderThemeData().hashCode,
const GalleryHeaderThemeData().copyWith().hashCode);
});
test(
'''Light GalleryHeaderThemeData lerps completely to dark GalleryHeaderThemeData''',
() {
expect(
const GalleryHeaderThemeData().lerp(_galleryHeaderThemeDataControl,
_galleryHeaderThemeDataDarkControl, 1),
_galleryHeaderThemeDataDarkControl);
});
test(
'''Light GalleryHeaderThemeData lerps halfway to dark GalleryHeaderThemeData''',
() {
expect(
const GalleryHeaderThemeData().lerp(_galleryHeaderThemeDataControl,
_galleryHeaderThemeDataDarkControl, 0.5),
_galleryHeaderThemeDataHalfLerpControl);
});
test(
'''Dark GalleryHeaderThemeData lerps completely to light GalleryHeaderThemeData''',
() {
expect(
const GalleryHeaderThemeData().lerp(_galleryHeaderThemeDataDarkControl,
_galleryHeaderThemeDataControl, 1),
_galleryHeaderThemeDataControl);
});
test('Merging dark and light themes results in a dark theme', () {
expect(
_galleryHeaderThemeDataControl
.merge(_galleryHeaderThemeDataDarkControl),
_galleryHeaderThemeDataDarkControl);
});
test('Merging dark and light themes results in a dark theme', () {
expect(
_galleryHeaderThemeDataDarkControl
.merge(_galleryHeaderThemeDataControl),
_galleryHeaderThemeDataControl);
});
testWidgets(
'Passing no GalleryHeaderThemeData returns default light theme values',
(WidgetTester tester) async {
late BuildContext _context;
await tester.pumpWidget(
MaterialApp(
builder: (context, child) => StreamChat(
client: MockStreamChatClient(),
child: child,
),
home: Builder(
builder: (context) {
_context = context;
return Scaffold(
appBar: GalleryHeader(
message: Message(),
),
);
},
),
),
);
final imageHeaderTheme = GalleryHeaderTheme.of(_context);
expect(imageHeaderTheme.closeButtonColor,
_galleryHeaderThemeDataControl.closeButtonColor);
expect(imageHeaderTheme.backgroundColor,
_galleryHeaderThemeDataControl.backgroundColor);
expect(imageHeaderTheme.iconMenuPointColor,
_galleryHeaderThemeDataControl.iconMenuPointColor);
expect(imageHeaderTheme.titleTextStyle,
_galleryHeaderThemeDataControl.titleTextStyle);
expect(imageHeaderTheme.subtitleTextStyle,
_galleryHeaderThemeDataControl.subtitleTextStyle);
expect(imageHeaderTheme.bottomSheetBarrierColor,
_galleryHeaderThemeDataControl.bottomSheetBarrierColor);
});
testWidgets(
'Passing no GalleryHeaderThemeData returns default dark theme values',
(WidgetTester tester) async {
late BuildContext _context;
await tester.pumpWidget(
MaterialApp(
builder: (context, child) => StreamChat(
client: MockStreamChatClient(),
streamChatThemeData: StreamChatThemeData.dark(),
child: child,
),
home: Builder(
builder: (context) {
_context = context;
return Scaffold(
appBar: GalleryHeader(
message: Message(),
),
);
},
),
),
);
final imageHeaderTheme = GalleryHeaderTheme.of(_context);
expect(imageHeaderTheme.closeButtonColor,
_galleryHeaderThemeDataDarkControl.closeButtonColor);
expect(imageHeaderTheme.backgroundColor,
_galleryHeaderThemeDataDarkControl.backgroundColor);
expect(imageHeaderTheme.iconMenuPointColor,
_galleryHeaderThemeDataDarkControl.iconMenuPointColor);
expect(imageHeaderTheme.titleTextStyle,
_galleryHeaderThemeDataDarkControl.titleTextStyle);
expect(imageHeaderTheme.subtitleTextStyle,
_galleryHeaderThemeDataDarkControl.subtitleTextStyle);
expect(imageHeaderTheme.bottomSheetBarrierColor,
_galleryHeaderThemeDataDarkControl.bottomSheetBarrierColor);
});
}
// Light theme test control.
final _galleryHeaderThemeDataControl = GalleryHeaderThemeData(
closeButtonColor: const Color(0xff000000),
backgroundColor: const Color(0xffffffff),
iconMenuPointColor: const Color(0xff000000),
titleTextStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black,
),
subtitleTextStyle: const TextStyle(
fontSize: 12,
color: Colors.black,
).copyWith(
color: const Color(0xff7A7A7A),
),
bottomSheetBarrierColor: const Color.fromRGBO(0, 0, 0, 0.2),
);
// Light theme test control.
final _galleryHeaderThemeDataHalfLerpControl = GalleryHeaderThemeData(
closeButtonColor: const Color(0xff7f7f7f),
backgroundColor: const Color(0xff87898b),
iconMenuPointColor: const Color(0xff7f7f7f),
titleTextStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xff7f7f7f),
),
subtitleTextStyle: const TextStyle(
fontSize: 12,
color: Color(0xff7a7a7a),
).copyWith(
color: const Color(0xff7A7A7A),
),
bottomSheetBarrierColor: const Color(0x4c000000),
);
// Dark theme test control.
final _galleryHeaderThemeDataDarkControl = GalleryHeaderThemeData(
closeButtonColor: const Color(0xffffffff),
backgroundColor: const Color(0xff101418),
iconMenuPointColor: const Color(0xffffffff),
titleTextStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
subtitleTextStyle: const TextStyle(
fontSize: 12,
color: Colors.white,
).copyWith(
color: const Color(0xff7A7A7A),
),
bottomSheetBarrierColor: const Color.fromRGBO(0, 0, 0, 0.4),
);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

@@ -37,7 +37,7 @@ void main() {
child: WillPopScope(
onWillPop: () async => false,
child: Scaffold(
body: ImageFooter(
body: GalleryFooter(
message: Message(),
),
),
@@ -79,7 +79,8 @@ void main() {
'name': 'test',
});
final messageText = '''a message.
const messageText = '''
a message.
with multiple lines
and a list:
- a. okasd
@@ -47,6 +47,7 @@ void main() {
StreamChat(
client: client,
streamChatThemeData: theme,
connectivityStream: Stream.value(ConnectivityResult.mobile),
child: SizedBox(
child: ReactionBubble(
reactions: [
@@ -60,7 +61,6 @@ void main() {
maskColor: theme.ownMessageTheme.reactionsMaskColor!,
),
),
connectivityStream: Stream.value(ConnectivityResult.mobile),
),
surfaceSize: const Size(100, 100),
);
@@ -83,6 +83,7 @@ void main() {
StreamChat(
client: client,
streamChatThemeData: StreamChatThemeData.fromTheme(themeData),
connectivityStream: Stream.value(ConnectivityResult.mobile),
child: Container(
color: Colors.black,
child: ReactionBubble(
@@ -97,7 +98,6 @@ void main() {
maskColor: theme.ownMessageTheme.reactionsMaskColor!,
),
),
connectivityStream: Stream.value(ConnectivityResult.mobile),
),
surfaceSize: const Size(100, 100),
);
@@ -89,6 +89,7 @@ void main() {
)(
StreamChat(
client: client,
connectivityStream: Stream.value(ConnectivityResult.mobile),
child: StreamChannel(
showLoading: false,
channel: channel,
@@ -100,7 +101,6 @@ void main() {
),
),
),
connectivityStream: Stream.value(ConnectivityResult.mobile),
),
),
surfaceSize: const Size.square(200),
@@ -141,6 +141,7 @@ void main() {
)(
StreamChat(
client: client,
connectivityStream: Stream.value(ConnectivityResult.mobile),
child: StreamChannel(
showLoading: false,
channel: channel,
@@ -152,7 +153,6 @@ void main() {
),
),
),
connectivityStream: Stream.value(ConnectivityResult.mobile),
),
),
surfaceSize: const Size.square(200),
@@ -54,12 +54,12 @@ void main() {
]));
when(() => channelState.typingEvents).thenAnswer((i) => {
User(id: 'other-user', extraData: {'name': 'demo'}):
User(id: 'other-user', extraData: const {'name': 'demo'}):
Event(type: EventType.typingStart),
});
when(() => channelState.typingEventsStream)
.thenAnswer((i) => Stream.value({
User(id: 'other-user', extraData: {'name': 'demo'}):
User(id: 'other-user', extraData: const {'name': 'demo'}):
Event(type: EventType.typingStart),
}));