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:
@@ -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`
|
||||
|
||||
@@ -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 [](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(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
+32
-14
@@ -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(),
|
||||
),
|
||||
+42
-19
@@ -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.
|
||||
///
|
||||
/// Let’s find out how we can change our application chat screen to display the list of conversations and navigate between them.
|
||||
/// Let’s find out how we can change our application chat screen to display
|
||||
/// the list of conversations and navigate between them.
|
||||
///
|
||||
/// > Note: the SDK uses Flutter’s [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 Flutter’s [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(),
|
||||
),
|
||||
+44
-22
@@ -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 you’ve 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.
|
||||
///
|
||||
/// Let’s see how we can make some changes to the SDK’s 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(),
|
||||
),
|
||||
+40
-25
@@ -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 thread’s 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 thread’s 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(
|
||||
+44
-23
@@ -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,
|
||||
),
|
||||
),
|
||||
+52
-35
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
+5
-5
@@ -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';
|
||||
|
||||
/// 
|
||||
/// 
|
||||
///
|
||||
/// 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';
|
||||
|
||||
/// 
|
||||
/// 
|
||||
///
|
||||
/// 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,
|
||||
|
||||
+18
-14
@@ -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),
|
||||
),
|
||||
],
|
||||
+11
-9
@@ -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';
|
||||
|
||||
@@ -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),
|
||||
}));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user