add reply action

This commit is contained in:
Salvatore Giordano
2020-02-27 18:37:51 +01:00
parent 541cafb49f
commit 15c13a7dc9
6 changed files with 144 additions and 76 deletions
+3 -10
View File
@@ -22,15 +22,6 @@ class MyApp extends StatelessWidget {
return MaterialApp( return MaterialApp(
home: Container( home: Container(
child: StreamChat( child: StreamChat(
streamChatThemeData: StreamChatThemeData(
accentColor: Colors.red,
channelTheme: ChannelTheme(
inputGradient: LinearGradient(colors: [
Colors.red,
Colors.yellow,
]),
),
),
client: client, client: client,
child: ChannelListPage(), child: ChannelListPage(),
), ),
@@ -104,7 +95,9 @@ class ThreadPage extends StatelessWidget {
parentMessage: parent, parentMessage: parent,
), ),
), ),
MessageInput(), MessageInput(
parentMessage: parent,
),
], ],
), ),
); );
+7 -4
View File
@@ -8,11 +8,11 @@ class MessageInput extends StatefulWidget {
MessageInput({ MessageInput({
Key key, Key key,
this.onMessageSent, this.onMessageSent,
this.parent, this.parentMessage,
}) : super(key: key); }) : super(key: key);
final void Function(Message) onMessageSent; final void Function(Message) onMessageSent;
final Message parent; final Message parentMessage;
@override @override
_MessageInputState createState() => _MessageInputState(); _MessageInputState createState() => _MessageInputState();
@@ -45,7 +45,10 @@ class _MessageInputState extends State<MessageInput> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: StreamChatTheme.of(context).channelTheme.inputBackground, 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: _typingStarted
? Colors.transparent
: Colors.black.withOpacity(.2)),
), ),
child: Flex( child: Flex(
direction: Axis.horizontal, direction: Axis.horizontal,
@@ -127,7 +130,7 @@ class _MessageInputState extends State<MessageInput> {
.channel .channel
.sendMessage( .sendMessage(
Message( Message(
parentId: widget.parent?.id, parentId: widget.parentMessage?.id,
text: text, text: text,
), ),
) )
+18 -8
View File
@@ -40,7 +40,6 @@ class _MessageListViewState extends State<MessageListView> {
bool _topWasVisible = false; bool _topWasVisible = false;
List<Message> _messages = []; List<Message> _messages = [];
List<Message> _newMessageList = []; List<Message> _newMessageList = [];
Function _onThreadTap; Function _onThreadTap;
@override @override
@@ -79,9 +78,10 @@ class _MessageListViewState extends State<MessageListView> {
key: ValueKey<String>( key: ValueKey<String>(
'PARENT-MESSAGE-${widget.parentMessage.id}'), 'PARENT-MESSAGE-${widget.parentMessage.id}'),
previousMessage: null, previousMessage: null,
message: widget.parentMessage.copyWith(replyCount: 0), message: widget.parentMessage,
nextMessage: null, nextMessage: null,
onThreadTap: _onThreadTap, onThreadTap: _onThreadTap,
isParent: true,
), ),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 32), padding: const EdgeInsets.symmetric(horizontal: 32),
@@ -306,7 +306,7 @@ class _MessageListViewState extends State<MessageListView> {
void _getOnThreadTap() { void _getOnThreadTap() {
if (widget.onThreadTap != null) { if (widget.onThreadTap != null) {
_onThreadTap = (message) { _onThreadTap = (Message message) {
widget.onThreadTap( widget.onThreadTap(
message, message,
widget.threadBuilder != null widget.threadBuilder != null
@@ -314,14 +314,24 @@ class _MessageListViewState extends State<MessageListView> {
: null); : null);
}; };
} else if (widget.threadBuilder != null) { } else if (widget.threadBuilder != null) {
_onThreadTap = (message) { _onThreadTap = (Message message) {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (_) { MaterialPageRoute(builder: (_) {
return StreamChannel( return StreamBuilder<Message>(
channel: StreamChannel.of(context).channel, stream: StreamChannel.of(context)
child: widget.threadBuilder(context, message), .channel
); .state
.messagesStream
.map((messages) =>
messages.firstWhere((m) => m.id == message.id)),
initialData: message,
builder: (_, snapshot) {
return StreamChannel(
channel: StreamChannel.of(context).channel,
child: widget.threadBuilder(context, snapshot.data),
);
});
}), }),
); );
}; };
+93 -51
View File
@@ -1,3 +1,5 @@
import 'dart:math';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:chewie/chewie.dart'; import 'package:chewie/chewie.dart';
import 'package:date_format/date_format.dart'; import 'package:date_format/date_format.dart';
@@ -21,12 +23,14 @@ class MessageWidget extends StatefulWidget {
@required this.message, @required this.message,
@required this.nextMessage, @required this.nextMessage,
this.onThreadTap, this.onThreadTap,
this.isParent = false,
}) : super(key: key); }) : super(key: key);
final Message previousMessage; final Message previousMessage;
final Message message; final Message message;
final Message nextMessage; final Message nextMessage;
final void Function(Message) onThreadTap; final void Function(Message) onThreadTap;
final bool isParent;
@override @override
_MessageWidgetState createState() => _MessageWidgetState(); _MessageWidgetState createState() => _MessageWidgetState();
@@ -63,7 +67,7 @@ class _MessageWidgetState extends State<MessageWidget>
isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
_buildBubble(context, isMyMessage, isLastUser), _buildBubble(context, isMyMessage, isLastUser),
_buildThreadIndicator(context), _buildThreadIndicator(context, isMyMessage),
isNextUser ? SizedBox() : _buildTimestamp(isMyMessage, alignment), isNextUser ? SizedBox() : _buildTimestamp(isMyMessage, alignment),
], ],
), ),
@@ -125,10 +129,33 @@ class _MessageWidgetState extends State<MessageWidget>
); );
} }
Widget _buildThreadIndicator(BuildContext context) { Widget _buildThreadIndicator(BuildContext context, bool isMyMessage) {
var row = [
Text(
'Replies: ${widget.message.replyCount}',
style:
Theme.of(context).textTheme.subtitle.copyWith(color: Colors.blue),
),
Transform(
transform: Matrix4.rotationY(isMyMessage ? 0 : pi),
alignment: Alignment.center,
child: Icon(
Icons.subdirectory_arrow_left,
color: Colors.black12,
),
),
];
if (!isMyMessage) {
row = row.reversed.toList();
}
return widget.message.replyCount > 0 return widget.message.replyCount > 0
? GestureDetector( ? GestureDetector(
onTap: () { onTap: () {
if (widget.isParent) {
return;
}
if (widget.onThreadTap != null) { if (widget.onThreadTap != null) {
widget.onThreadTap(widget.message); widget.onThreadTap(widget.message);
} }
@@ -136,19 +163,7 @@ class _MessageWidgetState extends State<MessageWidget>
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0), padding: const EdgeInsets.symmetric(vertical: 2.0),
child: Row( child: Row(
children: <Widget>[ children: row,
Text(
'Replies: ${widget.message.replyCount}',
style: Theme.of(context)
.textTheme
.subtitle
.copyWith(color: Colors.blue),
),
Icon(
Icons.subdirectory_arrow_left,
color: Colors.black12,
),
],
), ),
), ),
) )
@@ -332,9 +347,16 @@ class _MessageWidgetState extends State<MessageWidget>
void _showMessageBottomSheet(BuildContext context, bool isMyMessage) { void _showMessageBottomSheet(BuildContext context, bool isMyMessage) {
showModalBottomSheet( showModalBottomSheet(
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(32),
topRight: Radius.circular(32),
),
),
context: context, context: context,
builder: (context) { builder: (context) {
final fontSize = 40.0; final fontSize = 30.0;
final textStyle = TextStyle( final textStyle = TextStyle(
fontSize: fontSize, fontSize: fontSize,
); );
@@ -342,42 +364,49 @@ class _MessageWidgetState extends State<MessageWidget>
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[ children: <Widget>[
Row( Container(
crossAxisAlignment: CrossAxisAlignment.start, color: Colors.black87,
mainAxisAlignment: MainAxisAlignment.center, child: Row(
mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start,
children: reactionToEmoji.keys.map((reactionType) { mainAxisAlignment: MainAxisAlignment.center,
final ownReactionIndex = widget.message.ownReactions mainAxisSize: MainAxisSize.min,
.indexWhere((reaction) => reaction.type == reactionType); children: reactionToEmoji.keys.map((reactionType) {
return Column( final ownReactionIndex = widget.message.ownReactions
mainAxisSize: MainAxisSize.min, .indexWhere(
mainAxisAlignment: MainAxisAlignment.start, (reaction) => reaction.type == reactionType);
children: <Widget>[ return Column(
IconButton( mainAxisSize: MainAxisSize.min,
iconSize: fontSize + 10, mainAxisAlignment: MainAxisAlignment.start,
icon: Text( children: <Widget>[
reactionToEmoji[reactionType], IconButton(
style: textStyle, iconSize: fontSize + 10,
icon: Text(
reactionToEmoji[reactionType],
style: textStyle,
),
onPressed: () {
if (ownReactionIndex != -1) {
removeReaction(reactionType);
} else {
sendReaction(reactionType);
}
},
), ),
onPressed: () { ownReactionIndex != -1
if (ownReactionIndex != -1) { ? Padding(
removeReaction(reactionType); padding: const EdgeInsets.only(bottom: 4.0),
} else { child: Text(
sendReaction(reactionType); widget.message.ownReactions[ownReactionIndex]
} .score
}, .toString(),
), style: TextStyle(color: Colors.white),
ownReactionIndex != -1 ),
? Padding( )
padding: const EdgeInsets.only(bottom: 4.0), : SizedBox(),
child: Text(widget ],
.message.ownReactions[ownReactionIndex].score );
.toString()), }).toList(),
) ),
: SizedBox(),
],
);
}).toList(),
), ),
isMyMessage isMyMessage
? FlatButton( ? FlatButton(
@@ -399,6 +428,19 @@ class _MessageWidgetState extends State<MessageWidget>
}, },
) )
: SizedBox(), : SizedBox(),
FlatButton(
child: Padding(
padding: const EdgeInsets.all(28.0),
child: Text(
'Start a thread',
style: Theme.of(context).textTheme.headline,
),
),
onPressed: () {
Navigator.pop(context);
widget.onThreadTap(widget.message);
},
),
], ],
); );
}); });
+6 -1
View File
@@ -69,10 +69,15 @@ class StreamChannelState extends State<StreamChannel> {
Future<void> getReplies(String parentId) async { Future<void> getReplies(String parentId) async {
_queryMessageController.add(true); _queryMessageController.add(true);
print('PARENT $parentId');
String firstId; String firstId;
if (widget.channel.state.threads.containsKey(parentId)) { if (widget.channel.state.threads.containsKey(parentId)) {
firstId = widget.channel.state.threads[parentId].first.id; final thread = widget.channel.state.threads[parentId];
if (thread != null && thread.isNotEmpty) {
firstId = thread?.first?.id;
}
} }
return widget.channel return widget.channel
+17 -2
View File
@@ -51,6 +51,8 @@ class StreamChatState extends State<StreamChat> {
builder: (context) => Theme( builder: (context) => Theme(
data: Theme.of(context).copyWith( data: Theme.of(context).copyWith(
accentColor: StreamChatTheme.of(context).accentColor, accentColor: StreamChatTheme.of(context).accentColor,
scaffoldBackgroundColor: theme.primaryColor,
backgroundColor: theme.primaryColor,
), ),
child: WillPopScope( child: WillPopScope(
onWillPop: () async { onWillPop: () async {
@@ -82,8 +84,21 @@ class StreamChatState extends State<StreamChat> {
final theme = defaultTheme.copyWith( final theme = defaultTheme.copyWith(
primaryColor: widget.streamChatThemeData?.primaryColor, primaryColor: widget.streamChatThemeData?.primaryColor,
channelTheme: defaultTheme.channelTheme.copyWith( channelTheme: defaultTheme.channelTheme.copyWith(
channelHeaderTheme: channelHeaderTheme: defaultTheme.channelTheme.channelHeaderTheme
widget.streamChatThemeData?.channelTheme?.channelHeaderTheme, .copyWith(
color: widget.streamChatThemeData?.channelTheme
?.channelHeaderTheme?.color,
lastMessageAt: widget.streamChatThemeData?.channelTheme
?.channelHeaderTheme?.lastMessageAt,
title: widget.streamChatThemeData?.channelTheme
?.channelHeaderTheme?.title,
avatarTheme:
defaultTheme.channelPreviewTheme.avatarTheme.copyWith(
constraints: widget.streamChatThemeData?.channelTheme
?.channelHeaderTheme?.avatarTheme?.constraints,
borderRadius: widget.streamChatThemeData?.channelTheme
?.channelHeaderTheme?.avatarTheme?.borderRadius,
)),
inputBackground: inputBackground:
widget.streamChatThemeData?.channelTheme?.inputBackground, widget.streamChatThemeData?.channelTheme?.inputBackground,
messageInputButtonIconTheme: widget messageInputButtonIconTheme: widget