add stream chat theme draft

This commit is contained in:
Salvatore Giordano
2020-02-27 17:09:20 +01:00
parent a1308fb380
commit 541cafb49f
17 changed files with 647 additions and 128 deletions
+18 -7
View File
@@ -9,14 +9,25 @@ void main() async {
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoid2lsZC1icmVlemUtNyJ9.VM2EX1EXOfgqa-bTH_3JzeY0T99ngWzWahSauP3dBMo', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoid2lsZC1icmVlemUtNyJ9.VM2EX1EXOfgqa-bTH_3JzeY0T99ngWzWahSauP3dBMo',
); );
runApp( runApp(MyApp(client));
StreamChat( }
client: client,
child: MaterialApp( class MyApp extends StatelessWidget {
home: ChannelListPage(), final Client client;
MyApp(this.client);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Container(
child: StreamChat(
client: client,
child: ChannelListPage(),
),
), ),
), );
); }
} }
class ChannelListPage extends StatelessWidget { class ChannelListPage extends StatelessWidget {
+18 -7
View File
@@ -9,14 +9,25 @@ void main() async {
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoid2lsZC1icmVlemUtNyJ9.VM2EX1EXOfgqa-bTH_3JzeY0T99ngWzWahSauP3dBMo', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoid2lsZC1icmVlemUtNyJ9.VM2EX1EXOfgqa-bTH_3JzeY0T99ngWzWahSauP3dBMo',
); );
runApp( runApp(MyApp(client));
StreamChat( }
client: client,
child: MaterialApp( class MyApp extends StatelessWidget {
home: ChannelListPage(), final Client client;
MyApp(this.client);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Container(
child: StreamChat(
client: client,
child: ChannelListPage(),
),
), ),
), );
); }
} }
class ChannelListPage extends StatelessWidget { class ChannelListPage extends StatelessWidget {
+18 -7
View File
@@ -9,14 +9,25 @@ void main() async {
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoid2lsZC1icmVlemUtNyJ9.VM2EX1EXOfgqa-bTH_3JzeY0T99ngWzWahSauP3dBMo', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoid2lsZC1icmVlemUtNyJ9.VM2EX1EXOfgqa-bTH_3JzeY0T99ngWzWahSauP3dBMo',
); );
runApp( runApp(MyApp(client));
StreamChat( }
client: client,
child: MaterialApp( class MyApp extends StatelessWidget {
home: ChannelListPage(), final Client client;
MyApp(this.client);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Container(
child: StreamChat(
client: client,
child: ChannelListPage(),
),
), ),
), );
); }
} }
class ChannelListPage extends StatelessWidget { class ChannelListPage extends StatelessWidget {
+4 -4
View File
@@ -18,10 +18,10 @@ void main() async {
channel.watch(); channel.watch();
runApp( runApp(
StreamChat( MaterialApp(
client: client, home: StreamChat(
child: MaterialApp( client: client,
home: StreamChannel( child: StreamChannel(
channel: channel, channel: channel,
child: ChannelPage(), child: ChannelPage(),
), ),
+27 -7
View File
@@ -9,14 +9,34 @@ void main() async {
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoid2lsZC1icmVlemUtNyJ9.VM2EX1EXOfgqa-bTH_3JzeY0T99ngWzWahSauP3dBMo', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoid2lsZC1icmVlemUtNyJ9.VM2EX1EXOfgqa-bTH_3JzeY0T99ngWzWahSauP3dBMo',
); );
runApp( runApp(MyApp(client));
StreamChat( }
client: client,
child: MaterialApp( class MyApp extends StatelessWidget {
home: ChannelListPage(), final Client client;
MyApp(this.client);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Container(
child: StreamChat(
streamChatThemeData: StreamChatThemeData(
accentColor: Colors.red,
channelTheme: ChannelTheme(
inputGradient: LinearGradient(colors: [
Colors.red,
Colors.yellow,
]),
),
),
client: client,
child: ChannelListPage(),
),
), ),
), );
); }
} }
class ChannelListPage extends StatelessWidget { class ChannelListPage extends StatelessWidget {
+12 -1
View File
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/channel_name.dart'; import 'package:stream_chat_flutter/src/channel_name.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:timeago/timeago.dart' as timeago; import 'package:timeago/timeago.dart' as timeago;
import './channel_name.dart'; import './channel_name.dart';
@@ -22,7 +23,10 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final channel = StreamChannel.of(context).channel; final channel = StreamChannel.of(context).channel;
return AppBar( return AppBar(
elevation: 1,
leading: showBackButton ? _buildBackButton(context) : Container(), leading: showBackButton ? _buildBackButton(context) : Container(),
backgroundColor:
StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color,
actions: <Widget>[ actions: <Widget>[
Padding( Padding(
padding: const EdgeInsets.only(right: 10.0), padding: const EdgeInsets.only(right: 10.0),
@@ -39,6 +43,10 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
children: <Widget>[ children: <Widget>[
ChannelName( ChannelName(
channel: channel, channel: channel,
textStyle: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.title,
), ),
_buildLastActive(context, channel), _buildLastActive(context, channel),
], ],
@@ -54,7 +62,10 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
return (snapshot.data != null) return (snapshot.data != null)
? Text( ? Text(
'Active ${timeago.format(snapshot.data)}', 'Active ${timeago.format(snapshot.data)}',
style: Theme.of(context).textTheme.caption, style: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.lastMessageAt,
) )
: Container(); : Container();
}, },
+28 -11
View File
@@ -1,16 +1,17 @@
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
class ChannelImage extends StatelessWidget { class ChannelImage extends StatelessWidget {
const ChannelImage({ const ChannelImage({
Key key, Key key,
@required this.channel, @required this.channel,
this.radius = 20, this.size = 40,
}) : super(key: key); }) : super(key: key);
final Channel channel; final Channel channel;
final double radius; final double size;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -18,16 +19,32 @@ class ChannelImage extends StatelessWidget {
stream: channel.extraDataStream, stream: channel.extraDataStream,
initialData: channel.extraData, initialData: channel.extraData,
builder: (context, snapshot) { builder: (context, snapshot) {
return CircleAvatar( return Container(
radius: radius, padding: const EdgeInsets.all(4),
backgroundImage: snapshot.data?.containsKey('image') ?? false constraints: StreamChatTheme.of(context)
? CachedNetworkImageProvider(snapshot.data['image']) .channelPreviewTheme
: null, .avatarTheme
.constraints,
decoration: BoxDecoration(
borderRadius: StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
.borderRadius,
color: StreamChatTheme.of(context).accentColor,
),
child: snapshot.data?.containsKey('image') ?? false child: snapshot.data?.containsKey('image') ?? false
? null ? CachedNetworkImage(
: Text(snapshot.data?.containsKey('name') ?? false imageUrl: snapshot.data['image'],
? snapshot.data['name'][0] errorWidget: (_, __, ___) {
: ''), return Center(
child: Text(snapshot.data?.containsKey('name') ?? false
? snapshot.data['name'][0]
: ''),
);
},
fit: BoxFit.cover,
)
: SizedBox(),
); );
}); });
} }
+2 -2
View File
@@ -161,7 +161,7 @@ class _ChannelListViewState extends State<ChannelListView> {
} }
} }
Widget _buildQueryProgressIndicator(context, StreamChat streamChat) { Widget _buildQueryProgressIndicator(context, StreamChatState streamChat) {
return StreamBuilder<bool>( return StreamBuilder<bool>(
stream: streamChat.queryChannelsLoading, stream: streamChat.queryChannelsLoading,
initialData: false, initialData: false,
@@ -184,7 +184,7 @@ class _ChannelListViewState extends State<ChannelListView> {
); );
} }
void _listenChannelPagination(StreamChat streamChat) { void _listenChannelPagination(StreamChatState streamChat) {
if (widget._scrollController.position.maxScrollExtent == if (widget._scrollController.position.maxScrollExtent ==
widget._scrollController.position.pixels) { widget._scrollController.position.pixels) {
streamChat.queryChannels( streamChat.queryChannels(
+13 -10
View File
@@ -5,22 +5,25 @@ class ChannelName extends StatelessWidget {
const ChannelName({ const ChannelName({
Key key, Key key,
@required this.channel, @required this.channel,
this.textStyle,
}) : super(key: key); }) : super(key: key);
final Channel channel; final Channel channel;
final TextStyle textStyle;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return StreamBuilder<Map<String, dynamic>>( return StreamBuilder<Map<String, dynamic>>(
stream: channel.extraDataStream, stream: channel.extraDataStream,
initialData: channel.extraData, initialData: channel.extraData,
builder: (context, snapshot) { builder: (context, snapshot) {
return Text( return Text(
snapshot.data != null snapshot.data != null
? (snapshot.data['name'] ?? channel.cid) ? (snapshot.data['name'] ?? channel.cid)
: channel.cid, : channel.cid,
style: Theme.of(context).textTheme.body2, style: textStyle,
); );
}); },
);
} }
} }
+16 -6
View File
@@ -26,6 +26,7 @@ class ChannelPreview extends StatelessWidget {
channel: channel, channel: channel,
), ),
title: ChannelName( title: ChannelName(
textStyle: StreamChatTheme.of(context).channelPreviewTheme.title,
channel: channel, channel: channel,
), ),
subtitle: _buildSubtitle(), subtitle: _buildSubtitle(),
@@ -62,7 +63,7 @@ class ChannelPreview extends StatelessWidget {
return Text( return Text(
stringDate, stringDate,
style: Theme.of(context).textTheme.caption, style: StreamChatTheme.of(context).channelPreviewTheme.lastMessageAt,
); );
}, },
); );
@@ -115,9 +116,14 @@ class ChannelPreview extends StatelessWidget {
text, text,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.caption.copyWith( style:
color: Colors.black.withOpacity(opacity), StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
), color: StreamChatTheme.of(context)
.channelPreviewTheme
.subtitle
.color
.withOpacity(opacity),
),
); );
}, },
); );
@@ -127,8 +133,12 @@ class ChannelPreview extends StatelessWidget {
return Text( return Text(
'${typings.map((u) => u.extraData.containsKey('name') ? u.extraData['name'] : u.id).join(',')} ${typings.length == 1 ? 'is' : 'are'} typing...', '${typings.map((u) => u.extraData.containsKey('name') ? u.extraData['name'] : u.id).join(',')} ${typings.length == 1 ? 'is' : 'are'} typing...',
maxLines: 1, maxLines: 1,
style: Theme.of(context).textTheme.caption.copyWith( style: StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
color: Colors.black.withOpacity(opacity), color: StreamChatTheme.of(context)
.channelPreviewTheme
.subtitle
.color
.withOpacity(opacity),
), ),
); );
} }
+16 -13
View File
@@ -1,7 +1,6 @@
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'stream_channel.dart'; import 'stream_channel.dart';
@@ -34,17 +33,17 @@ class _MessageInputState extends State<MessageInput> {
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0), borderRadius: BorderRadius.circular(10.0),
gradient: _typingStarted gradient: _typingStarted
? LinearGradient(colors: [Color(0xFF00AEFF), Color(0xFF0076FF)]) ? StreamChatTheme.of(context).channelTheme.inputGradient
: null, : null,
), ),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).canvasColor, color: Colors.white,
borderRadius: BorderRadius.circular(10.0), borderRadius: BorderRadius.circular(10.0),
), ),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black.withAlpha(5), color: StreamChatTheme.of(context).channelTheme.inputBackground,
borderRadius: BorderRadius.circular(10.0), borderRadius: BorderRadius.circular(10.0),
border: Border.all(color: Colors.black.withOpacity(.2)), border: Border.all(color: Colors.black.withOpacity(.2)),
), ),
@@ -84,7 +83,7 @@ class _MessageInputState extends State<MessageInput> {
? CrossFadeState.showFirst ? CrossFadeState.showFirst
: CrossFadeState.showSecond, : CrossFadeState.showSecond,
firstChild: _buildSendButton(context), firstChild: _buildSendButton(context),
secondChild: Container(), secondChild: SizedBox(),
duration: Duration(milliseconds: 300), duration: Duration(milliseconds: 300),
alignment: Alignment.center, alignment: Alignment.center,
), ),
@@ -96,13 +95,17 @@ class _MessageInputState extends State<MessageInput> {
); );
} }
IconButton _buildSendButton(BuildContext context) { Widget _buildSendButton(BuildContext context) {
return IconButton( return IconTheme(
onPressed: () { data:
_sendMessage(context); StreamChatTheme.of(context).channelTheme.messageInputButtonIconTheme,
}, child: IconButton(
icon: Icon( onPressed: () {
Icons.send, _sendMessage(context);
},
icon: Icon(
Icons.send,
),
), ),
); );
} }
+2 -1
View File
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_widgets/flutter_widgets.dart'; import 'package:flutter_widgets/flutter_widgets.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import '../stream_chat_flutter.dart';
import 'message_widget.dart'; import 'message_widget.dart';
import 'stream_channel.dart'; import 'stream_channel.dart';
@@ -87,7 +88,7 @@ class _MessageListViewState extends State<MessageListView> {
child: Container( child: Container(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Text( child: Text(
'Start of a new thread', 'Start of thread',
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
color: Theme.of(context).primaryColorLight, color: Theme.of(context).primaryColorLight,
+27 -10
View File
@@ -7,6 +7,7 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/stream_channel.dart'; import 'package:stream_chat_flutter/src/stream_channel.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/user_avatar.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:video_player/video_player.dart'; import 'package:video_player/video_player.dart';
@@ -41,7 +42,7 @@ class _MessageWidgetState extends State<MessageWidget>
super.build(context); super.build(context);
final streamChat = StreamChat.of(context); final streamChat = StreamChat.of(context);
final currentUserId = streamChat.user.id; final currentUserId = streamChat.client.state.user.id;
final messageUserId = widget.message.user.id; final messageUserId = widget.message.user.id;
final previousUserId = widget.previousMessage?.user?.id; final previousUserId = widget.previousMessage?.user?.id;
final nextUserId = widget.nextMessage?.user?.id; final nextUserId = widget.nextMessage?.user?.id;
@@ -116,7 +117,9 @@ class _MessageWidgetState extends State<MessageWidget>
), ),
child: Text( child: Text(
'This message was deleted...', 'This message was deleted...',
style: TextStyle(fontStyle: FontStyle.italic), style: StreamChatTheme.of(context).messageTheme.messageText.copyWith(
fontStyle: FontStyle.italic,
),
), ),
), ),
); );
@@ -203,10 +206,13 @@ class _MessageWidgetState extends State<MessageWidget>
Text( Text(
attachment.title, attachment.title,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: Theme.of(context) style: StreamChatTheme.of(context)
.textTheme .messageTheme
.subtitle .messageText
.copyWith(color: Colors.blue), .copyWith(
color: Colors.blue,
fontWeight: FontWeight.bold,
),
), ),
Text( Text(
Uri.parse(attachment.thumbUrl) Uri.parse(attachment.thumbUrl)
@@ -218,8 +224,9 @@ class _MessageWidgetState extends State<MessageWidget>
.reversed .reversed
.join('.'), .join('.'),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: style: StreamChatTheme.of(context)
Theme.of(context).textTheme.caption, .messageTheme
.createdAt,
), ),
], ],
), ),
@@ -298,7 +305,12 @@ class _MessageWidgetState extends State<MessageWidget>
_launchURL(link); _launchURL(link);
}, },
styleSheet: styleSheet:
MarkdownStyleSheet.fromTheme(Theme.of(context)), MarkdownStyleSheet.fromTheme(Theme.of(context))
.copyWith(
p: StreamChatTheme.of(context)
.messageTheme
.messageText,
),
), ),
), ),
), ),
@@ -547,6 +559,7 @@ class _MessageWidgetState extends State<MessageWidget>
padding: const EdgeInsets.only(top: 5.0), padding: const EdgeInsets.only(top: 5.0),
child: Text( child: Text(
formatDate(widget.message.createdAt.toLocal(), [HH, ':', nn]), formatDate(widget.message.createdAt.toLocal(), [HH, ':', nn]),
style: StreamChatTheme.of(context).messageTheme.createdAt,
), ),
); );
} }
@@ -560,7 +573,11 @@ class _MessageWidgetState extends State<MessageWidget>
topRight: Radius.circular((isMyMessage && isLastUser) ? 2 : 16), topRight: Radius.circular((isMyMessage && isLastUser) ? 2 : 16),
bottomRight: Radius.circular(isMyMessage ? 2 : 16), bottomRight: Radius.circular(isMyMessage ? 2 : 16),
), ),
color: isMyMessage ? Color(0xffebebeb) : Colors.white, color: isMyMessage
? StreamChatTheme.of(context).messageTheme.ownMessageBackgroundColor
: StreamChatTheme.of(context)
.messageTheme
.otherMessageBackgroundColor,
); );
} }
+204 -32
View File
@@ -4,20 +4,210 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
class StreamChat extends InheritedWidget { class StreamChat extends StatefulWidget {
final Client client; final Client client;
final List<StreamSubscription> _subscriptions = []; final Widget child;
final StreamChatThemeData streamChatThemeData;
StreamChat({ StreamChat({
Key key, Key key,
@required this.client, @required this.client,
@required Widget child, @required this.child,
this.streamChatThemeData,
}) : super( }) : super(
key: key, key: key,
child: child, );
) {
_subscriptions.add(client.on('message.new').listen((Event e) { @override
StreamChatState createState() => StreamChatState();
static StreamChatState of(BuildContext context) {
StreamChatState streamChatState;
streamChatState = context.findAncestorStateOfType<StreamChatState>();
if (streamChatState == null) {
throw Exception(
'You must have a StreamChat widget at the top of your widget tree');
}
return streamChatState;
}
}
class StreamChatState extends State<StreamChat> {
final List<StreamSubscription> _subscriptions = [];
Client get client => widget.client;
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
@override
Widget build(BuildContext context) {
final theme = _computeTheme(context);
return StreamChatTheme(
data: theme,
child: Builder(
builder: (context) => Theme(
data: Theme.of(context).copyWith(
accentColor: StreamChatTheme.of(context).accentColor,
),
child: WillPopScope(
onWillPop: () async {
if (_navigatorKey.currentState.canPop()) {
_navigatorKey.currentState.pop();
return false;
} else {
return true;
}
},
child: Navigator(
initialRoute: '/',
key: _navigatorKey,
onGenerateRoute: (settings) {
return MaterialPageRoute(
settings: settings,
builder: (_) => widget.child,
);
},
),
),
),
),
);
}
StreamChatThemeData _computeTheme(BuildContext context) {
final defaultTheme = _getDefaultTheme(context);
final theme = defaultTheme.copyWith(
primaryColor: widget.streamChatThemeData?.primaryColor,
channelTheme: defaultTheme.channelTheme.copyWith(
channelHeaderTheme:
widget.streamChatThemeData?.channelTheme?.channelHeaderTheme,
inputBackground:
widget.streamChatThemeData?.channelTheme?.inputBackground,
messageInputButtonIconTheme: widget
.streamChatThemeData?.channelTheme?.messageInputButtonIconTheme,
inputGradient: widget.streamChatThemeData?.channelTheme?.inputGradient,
messageInputButtonTheme:
widget.streamChatThemeData?.channelTheme?.messageInputButtonTheme,
),
messageTheme: defaultTheme.messageTheme.copyWith(
createdAt: widget.streamChatThemeData?.messageTheme?.createdAt,
messageText: widget.streamChatThemeData?.messageTheme?.messageText,
otherMessageBackgroundColor: widget
.streamChatThemeData?.messageTheme?.otherMessageBackgroundColor,
ownMessageBackgroundColor:
widget.streamChatThemeData?.messageTheme?.ownMessageBackgroundColor,
fontFamily: widget.streamChatThemeData?.messageTheme?.fontFamily,
messageAuthor: widget.streamChatThemeData?.messageTheme?.messageAuthor,
messageMention:
widget.streamChatThemeData?.messageTheme?.messageMention,
avatarTheme: defaultTheme.messageTheme.avatarTheme.copyWith(
constraints: widget
.streamChatThemeData?.messageTheme?.avatarTheme?.constraints,
borderRadius: widget
.streamChatThemeData?.messageTheme?.avatarTheme?.borderRadius,
),
),
accentColor: widget.streamChatThemeData?.accentColor,
secondaryColor: widget.streamChatThemeData?.secondaryColor,
channelPreviewTheme: defaultTheme.channelPreviewTheme.copyWith(
avatarTheme: defaultTheme.channelPreviewTheme.avatarTheme.copyWith(
constraints: widget.streamChatThemeData?.channelPreviewTheme
?.avatarTheme?.constraints,
borderRadius: widget.streamChatThemeData?.channelPreviewTheme
?.avatarTheme?.borderRadius,
),
title: widget.streamChatThemeData?.channelPreviewTheme?.title,
lastMessageAt:
widget.streamChatThemeData?.channelPreviewTheme?.lastMessageAt,
subtitle: widget.streamChatThemeData?.channelPreviewTheme?.subtitle,
),
);
return theme;
}
StreamChatThemeData _getDefaultTheme(BuildContext context) {
final accentColor = Color(0xff006cff);
return StreamChatThemeData(
accentColor: accentColor,
primaryColor: Colors.white,
channelPreviewTheme: ChannelPreviewTheme(
avatarTheme: AvatarTheme(
borderRadius: BorderRadius.circular(20),
constraints: BoxConstraints.tightFor(
height: 40,
width: 40,
),
),
title: TextStyle(
fontSize: 14,
color: Colors.black,
),
subtitle: TextStyle(
fontSize: 13,
color: Colors.black,
),
lastMessageAt: TextStyle(
fontSize: 11,
color: Colors.black.withOpacity(.5),
),
),
channelTheme: ChannelTheme(
messageInputButtonIconTheme: Theme.of(context).iconTheme.copyWith(
color: accentColor,
),
channelHeaderTheme: ChannelHeaderTheme(
avatarTheme: AvatarTheme(
borderRadius: BorderRadius.circular(20),
constraints: BoxConstraints.tightFor(
height: 40,
width: 40,
),
),
color: Colors.white,
title: TextStyle(
fontSize: 14,
color: Colors.black,
),
lastMessageAt: TextStyle(
fontSize: 11,
color: Colors.black.withOpacity(.5),
),
),
inputBackground: Colors.black.withAlpha(12),
inputGradient: LinearGradient(colors: [
Color(0xFF00AEFF),
Color(0xFF0076FF),
]),
),
messageTheme: MessageTheme(
messageText: TextStyle(
fontSize: 15,
color: Colors.black,
),
createdAt: TextStyle(
color: Colors.black.withOpacity(.5),
fontSize: 11,
),
ownMessageBackgroundColor: Color(0xffebebeb),
otherMessageBackgroundColor: Colors.white,
avatarTheme: AvatarTheme(
borderRadius: BorderRadius.circular(20),
constraints: BoxConstraints.tightFor(
height: 32,
width: 32,
),
),
),
);
}
@override
void initState() {
super.initState();
_subscriptions.add(widget.client.on('message.new').listen((Event e) {
final index = channels.indexWhere((c) => c.cid == e.cid); final index = channels.indexWhere((c) => c.cid == e.cid);
if (index > 0) { if (index > 0) {
final channel = channels.removeAt(index); final channel = channels.removeAt(index);
@@ -27,12 +217,14 @@ class StreamChat extends InheritedWidget {
})); }));
} }
User get user => client.state.user; User get user => widget.client.state.user;
Stream<User> get userStream => client.state.userStream; Stream<User> get userStream => widget.client.state.userStream;
Stream<List<Channel>> get channelsStream => _channelsController.stream; Stream<List<Channel>> get channelsStream => _channelsController.stream;
final BehaviorSubject<List<Channel>> _channelsController = BehaviorSubject(); final BehaviorSubject<List<Channel>> _channelsController = BehaviorSubject();
final List<Channel> channels = []; final List<Channel> channels = [];
final BehaviorSubject<bool> _queryChannelsLoadingController = final BehaviorSubject<bool> _queryChannelsLoadingController =
@@ -53,7 +245,7 @@ class StreamChat extends InheritedWidget {
_queryChannelsLoadingController.sink.add(true); _queryChannelsLoadingController.sink.add(true);
try { try {
final res = await client.queryChannels( final res = await widget.client.queryChannels(
filter: filter, filter: filter,
sort: sortOptions, sort: sortOptions,
options: options, options: options,
@@ -71,32 +263,12 @@ class StreamChat extends InheritedWidget {
channels.clear(); channels.clear();
} }
@override
void dispose() { void dispose() {
client.dispose(); widget.client.dispose();
_subscriptions.forEach((s) => s.cancel()); _subscriptions.forEach((s) => s.cancel());
_queryChannelsLoadingController.close(); _queryChannelsLoadingController.close();
_channelsController.close(); _channelsController.close();
} super.dispose();
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
static StreamChat of(BuildContext context, [bool listen = false]) {
StreamChat streamChat;
if (listen) {
streamChat = context.dependOnInheritedWidgetOfExactType<StreamChat>();
} else {
streamChat = context.findAncestorWidgetOfExactType<StreamChat>();
}
if (streamChat == null) {
throw Exception(
'You must have a StreamChat widget at the top of your widget tree');
}
return streamChat;
} }
} }
+217
View File
@@ -0,0 +1,217 @@
import 'package:flutter/material.dart';
class StreamChatTheme extends InheritedWidget {
final StreamChatThemeData data;
StreamChatTheme({
Key key,
@required this.data,
Widget child,
}) : super(
key: key,
child: child,
);
@override
bool updateShouldNotify(StreamChatTheme old) {
return data != old.data;
}
static StreamChatThemeData of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<StreamChatTheme>().data;
}
}
class StreamChatThemeData {
final Color primaryColor;
final Color secondaryColor;
final Color accentColor;
final ChannelPreviewTheme channelPreviewTheme;
final ChannelTheme channelTheme;
final MessageTheme messageTheme;
StreamChatThemeData({
this.primaryColor,
this.secondaryColor,
this.accentColor,
this.channelPreviewTheme,
this.channelTheme,
this.messageTheme,
});
factory StreamChatThemeData.fromTheme(ThemeData theme) {
return StreamChatThemeData(
accentColor: theme.accentColor,
primaryColor: theme.colorScheme.primary,
secondaryColor: theme.colorScheme.secondary,
);
}
StreamChatThemeData copyWith({
Color primaryColor,
Color secondaryColor,
Color accentColor,
ChannelPreviewTheme channelPreviewTheme,
ChannelTheme channelTheme,
MessageTheme messageTheme,
}) =>
StreamChatThemeData(
primaryColor: primaryColor ?? this.primaryColor,
secondaryColor: secondaryColor ?? this.secondaryColor,
accentColor: accentColor ?? this.accentColor,
channelPreviewTheme: channelPreviewTheme ?? this.channelPreviewTheme,
channelTheme: channelTheme ?? this.channelTheme,
messageTheme: messageTheme ?? this.messageTheme,
);
}
class ChannelTheme {
final ChannelHeaderTheme channelHeaderTheme;
final IconThemeData messageInputButtonIconTheme;
final ButtonThemeData messageInputButtonTheme;
final Gradient inputGradient;
final Color inputBackground;
ChannelTheme({
this.channelHeaderTheme,
this.messageInputButtonIconTheme,
this.messageInputButtonTheme,
this.inputBackground,
this.inputGradient,
});
ChannelTheme copyWith({
ChannelHeaderTheme channelHeaderTheme,
IconThemeData messageInputButtonIconTheme,
ButtonThemeData messageInputButtonTheme,
Gradient inputGradient,
Color inputBackground,
}) =>
ChannelTheme(
channelHeaderTheme: channelHeaderTheme ?? this.channelHeaderTheme,
messageInputButtonIconTheme:
messageInputButtonIconTheme ?? this.messageInputButtonIconTheme,
messageInputButtonTheme:
messageInputButtonTheme ?? this.messageInputButtonTheme,
inputGradient: inputGradient ?? this.inputGradient,
inputBackground: inputBackground ?? this.inputBackground,
);
}
class AvatarTheme {
final BoxConstraints constraints;
final BorderRadius borderRadius;
AvatarTheme({
this.constraints,
this.borderRadius,
});
AvatarTheme copyWith({
BoxConstraints constraints,
BorderRadius borderRadius,
}) =>
AvatarTheme(
constraints: constraints ?? this.constraints,
borderRadius: borderRadius ?? this.borderRadius,
);
}
class MessageTheme {
final TextStyle messageText;
final TextStyle messageAuthor;
final TextStyle messageMention;
final TextStyle createdAt;
final String fontFamily;
final Color ownMessageBackgroundColor;
final Color otherMessageBackgroundColor;
final AvatarTheme avatarTheme;
const MessageTheme({
this.messageText,
this.messageAuthor,
this.messageMention,
this.fontFamily,
this.ownMessageBackgroundColor,
this.otherMessageBackgroundColor,
this.avatarTheme,
this.createdAt,
});
MessageTheme copyWith({
TextStyle messageText,
TextStyle messageAuthor,
TextStyle messageMention,
TextStyle createdAt,
String fontFamily,
Color ownMessageBackgroundColor,
Color otherMessageBackgroundColor,
AvatarTheme avatarTheme,
}) =>
MessageTheme(
messageText: messageText ?? this.messageText,
messageAuthor: messageAuthor ?? this.messageAuthor,
messageMention: messageMention ?? this.messageMention,
createdAt: createdAt ?? this.createdAt,
fontFamily: fontFamily ?? this.fontFamily,
ownMessageBackgroundColor:
ownMessageBackgroundColor ?? this.ownMessageBackgroundColor,
otherMessageBackgroundColor:
otherMessageBackgroundColor ?? this.otherMessageBackgroundColor,
avatarTheme: avatarTheme ?? this.avatarTheme,
);
}
class ChannelPreviewTheme {
final TextStyle title;
final TextStyle subtitle;
final TextStyle lastMessageAt;
final AvatarTheme avatarTheme;
const ChannelPreviewTheme({
this.title,
this.subtitle,
this.lastMessageAt,
this.avatarTheme,
});
ChannelPreviewTheme copyWith({
TextStyle title,
TextStyle subtitle,
TextStyle lastMessageAt,
AvatarTheme avatarTheme,
}) =>
ChannelPreviewTheme(
title: title ?? this.title,
subtitle: subtitle ?? this.subtitle,
lastMessageAt: lastMessageAt ?? this.lastMessageAt,
avatarTheme: avatarTheme ?? this.avatarTheme,
);
}
class ChannelHeaderTheme {
final TextStyle title;
final TextStyle lastMessageAt;
final AvatarTheme avatarTheme;
final Color color;
const ChannelHeaderTheme({
this.title,
this.lastMessageAt,
this.avatarTheme,
this.color,
});
ChannelHeaderTheme copyWith({
TextStyle title,
TextStyle lastMessageAt,
AvatarTheme avatarTheme,
Color color,
}) =>
ChannelHeaderTheme(
title: title ?? this.title,
lastMessageAt: lastMessageAt ?? this.lastMessageAt,
avatarTheme: avatarTheme ?? this.avatarTheme,
color: color ?? this.color,
);
}
+24 -10
View File
@@ -2,6 +2,8 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import '../stream_chat_flutter.dart';
class UserAvatar extends StatelessWidget { class UserAvatar extends StatelessWidget {
const UserAvatar({ const UserAvatar({
Key key, Key key,
@@ -14,16 +16,28 @@ class UserAvatar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CircleAvatar( return Container(
radius: radius, padding: const EdgeInsets.all(4),
backgroundImage: user.extraData.containsKey('image') constraints:
? CachedNetworkImageProvider(user.extraData['image'] as String) StreamChatTheme.of(context).messageTheme.avatarTheme.constraints,
: null, decoration: BoxDecoration(
child: user.extraData.containsKey('image') borderRadius:
? null StreamChatTheme.of(context).messageTheme.avatarTheme.borderRadius,
: Text(user?.extraData?.containsKey('name') ?? false color: StreamChatTheme.of(context).accentColor,
? user.extraData['name'][0] ),
: ''), child: user.extraData?.containsKey('image') ?? false
? CachedNetworkImage(
imageUrl: user.extraData['image'],
errorWidget: (_, __, ___) {
return Center(
child: Text(user.extraData?.containsKey('name') ?? false
? user.extraData['name'][0]
: ''),
);
},
fit: BoxFit.cover,
)
: SizedBox(),
); );
} }
} }
+1
View File
@@ -10,3 +10,4 @@ export 'src/message_list_view.dart';
export 'src/message_widget.dart'; export 'src/message_widget.dart';
export 'src/stream_channel.dart'; export 'src/stream_channel.dart';
export 'src/stream_chat.dart'; export 'src/stream_chat.dart';
export 'src/stream_chat_theme.dart';