add commands

This commit is contained in:
Salvatore Giordano
2020-03-03 17:06:36 +01:00
parent 0b156fa9c4
commit e2f29a15bf
2 changed files with 332 additions and 126 deletions
+121 -3
View File
@@ -4,6 +4,7 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:keyboard_visibility/keyboard_visibility.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/message_list_view.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
@@ -82,6 +83,10 @@ class _MessageInputState extends State<MessageInput> {
final _focusNode = FocusNode(); final _focusNode = FocusNode();
bool _modalOn = false;
OverlayEntry _commandsOverlay;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeArea( return SafeArea(
@@ -140,6 +145,19 @@ class _MessageInputState extends State<MessageInput> {
setState(() { setState(() {
_messageIsPresent = s.trim().isNotEmpty; _messageIsPresent = s.trim().isNotEmpty;
}); });
if (s.startsWith('/')) {
_modalOn = true;
_commandsOverlay?.remove();
_commandsOverlay = null;
_commandsOverlay = _buildOverlayEntry();
Overlay.of(context).insert(_commandsOverlay);
} else if (!s.startsWith('/') && _modalOn) {
_modalOn = false;
_commandsOverlay?.remove();
_commandsOverlay = null;
}
}, },
onTap: () { onTap: () {
setState(() { setState(() {
@@ -175,6 +193,79 @@ class _MessageInputState extends State<MessageInput> {
); );
} }
OverlayEntry _buildOverlayEntry() {
final text = _textController.text;
final commands = StreamChannel.of(context)
.channel
.config
.commands
.where((c) => c.name.startsWith(text.substring(1)))
.toList();
RenderBox renderBox = context.findRenderObject();
final size = renderBox.size;
final offset = renderBox.localToGlobal(Offset.zero);
return OverlayEntry(builder: (_) {
return Positioned(
top: offset.dy - size.height,
left: 0,
right: 0,
child: Material(
color: StreamChatTheme.of(context).primaryColor,
child: Flex(
mainAxisSize: MainAxisSize.min,
direction: Axis.vertical,
children: <Widget>[
ListView(
padding: const EdgeInsets.all(0),
shrinkWrap: true,
children: commands
.map((c) => ListTile(
title: Text.rich(
TextSpan(
text: '${c.name}',
style: TextStyle(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: ' ${c.args}',
style: TextStyle(
fontWeight: FontWeight.w300,
),
),
],
),
),
subtitle: Text(c.description),
onTap: () {
_setCommand(c);
},
))
.toList(),
),
],
),
),
);
});
}
void _setCommand(Command c) {
setState(() {
_textController.value = TextEditingValue(
text: '/${c.name}',
selection: TextSelection.fromPosition(
TextPosition(
offset: c.name.length + 1,
),
),
);
});
_modalOn = false;
_commandsOverlay?.remove();
_commandsOverlay = null;
}
Gradient _getGradient(BuildContext context) { Gradient _getGradient(BuildContext context) {
if (_typingStarted) { if (_typingStarted) {
if (widget.editMessage == null) { if (widget.editMessage == null) {
@@ -438,6 +529,12 @@ class _MessageInputState extends State<MessageInput> {
_messageIsPresent = false; _messageIsPresent = false;
_typingStarted = false; _typingStarted = false;
}); });
_modalOn = false;
_commandsOverlay?.remove();
_commandsOverlay = null;
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
if (widget.editMessage != null) { if (widget.editMessage != null) {
@@ -486,10 +583,24 @@ class _MessageInputState extends State<MessageInput> {
}); });
} }
int _keyboardListener;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_keyboardListener = KeyboardVisibilityNotification().addNewListener(
onChange: (_) {
if (_modalOn) {
_commandsOverlay?.remove();
WidgetsBinding.instance.addPostFrameCallback((_) {
_commandsOverlay = _buildOverlayEntry();
Overlay.of(context).insert(_commandsOverlay);
});
}
},
);
if (widget.editMessage != null) { if (widget.editMessage != null) {
_textController = TextEditingController(text: widget.editMessage.text); _textController = TextEditingController(text: widget.editMessage.text);
@@ -510,12 +621,19 @@ class _MessageInputState extends State<MessageInput> {
} }
} }
bool _focused = false; @override
void dispose() {
_commandsOverlay?.remove();
KeyboardVisibilityNotification().removeListener(_keyboardListener);
super.dispose();
}
bool _initialized = false;
@override @override
void didChangeDependencies() { void didChangeDependencies() {
if (widget.editMessage != null && !_focused) { if (widget.editMessage != null && !_initialized) {
FocusScope.of(context).requestFocus(_focusNode); FocusScope.of(context).requestFocus(_focusNode);
_focused = true; _initialized = true;
} }
super.didChangeDependencies(); super.didChangeDependencies();
} }
+211 -123
View File
@@ -233,74 +233,124 @@ class _MessageWidgetState extends State<MessageWidget>
.copyWith(color: Color(0xffebebeb)); .copyWith(color: Color(0xffebebeb));
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 2.0), padding: const EdgeInsets.only(bottom: 2.0),
child: ClipRRect( child: Column(
borderRadius: boxDecoration.borderRadius, mainAxisSize: MainAxisSize.min,
child: Container( children: <Widget>[
decoration: boxDecoration, ClipRRect(
constraints: BoxConstraints.loose(Size.fromWidth(300)), borderRadius: boxDecoration.borderRadius,
child: Stack( child: Container(
children: <Widget>[ decoration: boxDecoration,
Column( constraints: BoxConstraints.loose(Size.fromWidth(300)),
mainAxisSize: MainAxisSize.min, child: Stack(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[ children: <Widget>[
attachmentWidget, Column(
attachment.title != null mainAxisSize: MainAxisSize.min,
? Container( crossAxisAlignment: CrossAxisAlignment.stretch,
constraints: children: <Widget>[
BoxConstraints.loose(Size(300, 500)), attachmentWidget,
child: Padding( attachment.title != null
padding: const EdgeInsets.all(8.0), ? Container(
child: Column( constraints:
mainAxisSize: MainAxisSize.min, BoxConstraints.loose(Size(300, 500)),
crossAxisAlignment: child: Padding(
CrossAxisAlignment.start, padding: const EdgeInsets.all(8.0),
children: <Widget>[ child: Column(
Text( mainAxisSize: MainAxisSize.min,
attachment.title, crossAxisAlignment:
overflow: TextOverflow.ellipsis, CrossAxisAlignment.start,
style: children: <Widget>[
messageTheme.messageText.copyWith( Text(
color: Colors.blue, attachment.title,
fontWeight: FontWeight.bold, overflow: TextOverflow.ellipsis,
), style: messageTheme.messageText
.copyWith(
color: Colors.blue,
fontWeight: FontWeight.bold,
),
),
Text(
Uri.parse(attachment.thumbUrl)
.authority
.split('.')
.reversed
.take(2)
.toList()
.reversed
.join('.'),
overflow: TextOverflow.ellipsis,
style: messageTheme.createdAt,
),
],
), ),
Text( ),
Uri.parse(attachment.thumbUrl) color: Color(0xffebebeb),
.authority )
.split('.') : SizedBox(),
.reversed ],
.take(2) ),
.toList() attachment.type == 'image' &&
.reversed attachment.titleLink != null
.join('.'), ? Positioned.fill(
overflow: TextOverflow.ellipsis, child: Material(
style: messageTheme.createdAt, color: Colors.transparent,
), child: InkWell(
], onTap: () =>
_launchURL(attachment.titleLink),
), ),
), ),
color: Color(0xffebebeb),
) )
: SizedBox(), : SizedBox(),
], ],
), ),
attachment.type == 'image' && attachment.titleLink != null margin: EdgeInsets.only(
? Positioned.fill( top: nOfAttachmentWidgets > 1 ? 5 : 0,
child: Material( ),
color: Colors.transparent, ),
child: InkWell( ),
onTap: () => _launchURL(attachment.titleLink), if (attachment.actions != null)
), Row(
mainAxisAlignment: MainAxisAlignment.end,
children: attachment.actions?.map((action) {
if (action.style == 'primary') {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: FlatButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
), ),
) child: Text('${action.text}'),
: SizedBox(), color: action.style == 'primary'
], ? StreamChatTheme.of(context).accentColor
), : null,
margin: EdgeInsets.only( textColor: Colors.white,
top: nOfAttachmentWidgets > 1 ? 5 : 0, onPressed: () {
), streamChannel.channel
), .sendAction(widget.message.id, {
action.name: action.value,
});
},
),
);
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: OutlineButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Text('${action.text}'),
color: StreamChatTheme.of(context).accentColor,
onPressed: () {
streamChannel.channel
.sendAction(widget.message.id, {
action.name: action.value,
});
},
),
);
})?.toList(),
),
],
), ),
); );
} }
@@ -313,80 +363,96 @@ class _MessageWidgetState extends State<MessageWidget>
if (widget.message.text.trim().isNotEmpty) { if (widget.message.text.trim().isNotEmpty) {
column.children.addAll( column.children.addAll(
<Widget>[ <Widget>[
IntrinsicWidth( Column(
child: Column( crossAxisAlignment:
crossAxisAlignment: isMyMessage isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
? CrossAxisAlignment.end children: <Widget>[
: CrossAxisAlignment.start, streamChannel.channel.config.reactions
children: <Widget>[ ? Align(
streamChannel.channel.config.reactions child: _buildReactions(),
? Align( alignment: isMyMessage
child: _buildReactions(), ? Alignment.centerLeft
alignment: isMyMessage : Alignment.centerRight,
? Alignment.centerLeft )
: Alignment.centerRight, : SizedBox(),
) Stack(
: SizedBox(), overflow: Overflow.visible,
Stack( children: <Widget>[
overflow: Overflow.visible, _buildReactionPaint(),
children: <Widget>[ Padding(
widget.message.reactionCounts?.isNotEmpty == true padding: EdgeInsets.only(
? Positioned( right: isMyMessage ? 0.0 : 8.0,
left: isMyMessage ? 8 : null, left: isMyMessage ? 8.0 : 0.0,
right: !isMyMessage ? 8 : null, ),
top: -6, child: Container(
child: CustomPaint( decoration: _buildBoxDecoration(
painter: ReactionBubblePainter(), isLastUser || nOfAttachmentWidgets > 0),
), padding: EdgeInsets.all(10),
) constraints: BoxConstraints.loose(Size.fromWidth(300)),
: SizedBox(), child: MarkdownBody(
Padding( data: '${widget.message.text}',
padding: EdgeInsets.only( onTapLink: (link) {
right: isMyMessage ? 0.0 : 8.0, _launchURL(link);
left: isMyMessage ? 8.0 : 0.0, },
), styleSheet: MarkdownStyleSheet.fromTheme(
child: Container( Theme.of(context).copyWith(
decoration: _buildBoxDecoration( textTheme: Theme.of(context).textTheme.apply(
isLastUser || nOfAttachmentWidgets > 0), bodyColor: messageTheme.messageText.color,
padding: EdgeInsets.all(10), decoration:
constraints: BoxConstraints.loose(Size.fromWidth(300)), messageTheme.messageText.decoration,
child: MarkdownBody( decorationColor:
data: '${widget.message.text}', messageTheme.messageText.decorationColor,
onTapLink: (link) { decorationStyle:
_launchURL(link); messageTheme.messageText.decorationStyle,
}, fontFamily:
styleSheet: MarkdownStyleSheet.fromTheme( messageTheme.messageText.fontFamily,
Theme.of(context).copyWith( ),
textTheme: Theme.of(context).textTheme.apply(
bodyColor: messageTheme.messageText.color,
decoration:
messageTheme.messageText.decoration,
decorationColor: messageTheme
.messageText.decorationColor,
decorationStyle: messageTheme
.messageText.decorationStyle,
fontFamily:
messageTheme.messageText.fontFamily,
),
),
).copyWith(
p: messageTheme.messageText,
), ),
).copyWith(
p: messageTheme.messageText,
), ),
), ),
), ),
], ),
), ],
], ),
), ],
), ),
], ],
); );
} else {
column.children.insert(
0,
streamChannel.channel.config.reactions
? Align(
child: _buildReactions(),
alignment:
isMyMessage ? Alignment.centerLeft : Alignment.centerRight,
)
: SizedBox(),
);
column.children[1] = Stack(
overflow: Overflow.visible,
children: <Widget>[
Padding(
padding: EdgeInsets.only(
right: isMyMessage ? 0.0 : 8.0,
left: isMyMessage ? 8.0 : 0.0,
),
child: column.children[1],
),
_buildReactionPaint(),
],
);
} }
return GestureDetector( return GestureDetector(
child: column, child: IntrinsicWidth(child: column),
onLongPress: () { onLongPress: () {
if (widget.message.type == 'ephemeral') {
return;
}
if (widget.onMessageActions != null) { if (widget.onMessageActions != null) {
widget.onMessageActions(context, widget.message); widget.onMessageActions(context, widget.message);
} else { } else {
@@ -395,6 +461,19 @@ class _MessageWidgetState extends State<MessageWidget>
}); });
} }
Widget _buildReactionPaint() {
return widget.message.reactionCounts?.isNotEmpty == true
? Positioned(
left: isMyMessage ? 8 : null,
right: !isMyMessage ? 8 : null,
top: -6,
child: CustomPaint(
painter: _ReactionBubblePainter(),
),
)
: SizedBox();
}
void _showMessageBottomSheet(BuildContext context) { void _showMessageBottomSheet(BuildContext context) {
if (!streamChannel.channel.config.reactions && if (!streamChannel.channel.config.reactions &&
!streamChannel.channel.config.replies) { !streamChannel.channel.config.replies) {
@@ -640,6 +719,15 @@ class _MessageWidgetState extends State<MessageWidget>
imageUrl: imageUrl:
attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl, attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl,
fit: BoxFit.cover, fit: BoxFit.cover,
placeholder: (_, __) {
return SizedBox(
height: 200,
width: 300,
child: Center(
child: CircularProgressIndicator(),
),
);
},
); );
} }
@@ -743,7 +831,7 @@ class _MessageWidgetState extends State<MessageWidget>
} }
} }
class ReactionBubblePainter extends CustomPainter { class _ReactionBubblePainter extends CustomPainter {
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
final paint = Paint()..color = Colors.black; final paint = Paint()..color = Colors.black;