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/material.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_flutter/src/message_list_view.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
@@ -82,6 +83,10 @@ class _MessageInputState extends State<MessageInput> {
final _focusNode = FocusNode();
bool _modalOn = false;
OverlayEntry _commandsOverlay;
@override
Widget build(BuildContext context) {
return SafeArea(
@@ -140,6 +145,19 @@ class _MessageInputState extends State<MessageInput> {
setState(() {
_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: () {
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) {
if (_typingStarted) {
if (widget.editMessage == null) {
@@ -438,6 +529,12 @@ class _MessageInputState extends State<MessageInput> {
_messageIsPresent = false;
_typingStarted = false;
});
_modalOn = false;
_commandsOverlay?.remove();
_commandsOverlay = null;
FocusScope.of(context).unfocus();
if (widget.editMessage != null) {
@@ -486,10 +583,24 @@ class _MessageInputState extends State<MessageInput> {
});
}
int _keyboardListener;
@override
void 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) {
_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
void didChangeDependencies() {
if (widget.editMessage != null && !_focused) {
if (widget.editMessage != null && !_initialized) {
FocusScope.of(context).requestFocus(_focusNode);
_focused = true;
_initialized = true;
}
super.didChangeDependencies();
}
+211 -123
View File
@@ -233,74 +233,124 @@ class _MessageWidgetState extends State<MessageWidget>
.copyWith(color: Color(0xffebebeb));
return Padding(
padding: const EdgeInsets.only(bottom: 2.0),
child: ClipRRect(
borderRadius: boxDecoration.borderRadius,
child: Container(
decoration: boxDecoration,
constraints: BoxConstraints.loose(Size.fromWidth(300)),
child: Stack(
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ClipRRect(
borderRadius: boxDecoration.borderRadius,
child: Container(
decoration: boxDecoration,
constraints: BoxConstraints.loose(Size.fromWidth(300)),
child: Stack(
children: <Widget>[
attachmentWidget,
attachment.title != null
? Container(
constraints:
BoxConstraints.loose(Size(300, 500)),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text(
attachment.title,
overflow: TextOverflow.ellipsis,
style:
messageTheme.messageText.copyWith(
color: Colors.blue,
fontWeight: FontWeight.bold,
),
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
attachmentWidget,
attachment.title != null
? Container(
constraints:
BoxConstraints.loose(Size(300, 500)),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text(
attachment.title,
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)
.authority
.split('.')
.reversed
.take(2)
.toList()
.reversed
.join('.'),
overflow: TextOverflow.ellipsis,
style: messageTheme.createdAt,
),
],
),
color: Color(0xffebebeb),
)
: SizedBox(),
],
),
attachment.type == 'image' &&
attachment.titleLink != null
? Positioned.fill(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () =>
_launchURL(attachment.titleLink),
),
),
color: Color(0xffebebeb),
)
: SizedBox(),
],
),
attachment.type == 'image' && attachment.titleLink != null
? Positioned.fill(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () => _launchURL(attachment.titleLink),
),
margin: EdgeInsets.only(
top: nOfAttachmentWidgets > 1 ? 5 : 0,
),
),
),
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),
),
)
: SizedBox(),
],
),
margin: EdgeInsets.only(
top: nOfAttachmentWidgets > 1 ? 5 : 0,
),
),
child: Text('${action.text}'),
color: action.style == 'primary'
? StreamChatTheme.of(context).accentColor
: null,
textColor: Colors.white,
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) {
column.children.addAll(
<Widget>[
IntrinsicWidth(
child: Column(
crossAxisAlignment: isMyMessage
? CrossAxisAlignment.end
: CrossAxisAlignment.start,
children: <Widget>[
streamChannel.channel.config.reactions
? Align(
child: _buildReactions(),
alignment: isMyMessage
? Alignment.centerLeft
: Alignment.centerRight,
)
: SizedBox(),
Stack(
overflow: Overflow.visible,
children: <Widget>[
widget.message.reactionCounts?.isNotEmpty == true
? Positioned(
left: isMyMessage ? 8 : null,
right: !isMyMessage ? 8 : null,
top: -6,
child: CustomPaint(
painter: ReactionBubblePainter(),
),
)
: SizedBox(),
Padding(
padding: EdgeInsets.only(
right: isMyMessage ? 0.0 : 8.0,
left: isMyMessage ? 8.0 : 0.0,
),
child: Container(
decoration: _buildBoxDecoration(
isLastUser || nOfAttachmentWidgets > 0),
padding: EdgeInsets.all(10),
constraints: BoxConstraints.loose(Size.fromWidth(300)),
child: MarkdownBody(
data: '${widget.message.text}',
onTapLink: (link) {
_launchURL(link);
},
styleSheet: MarkdownStyleSheet.fromTheme(
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,
Column(
crossAxisAlignment:
isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: <Widget>[
streamChannel.channel.config.reactions
? Align(
child: _buildReactions(),
alignment: isMyMessage
? Alignment.centerLeft
: Alignment.centerRight,
)
: SizedBox(),
Stack(
overflow: Overflow.visible,
children: <Widget>[
_buildReactionPaint(),
Padding(
padding: EdgeInsets.only(
right: isMyMessage ? 0.0 : 8.0,
left: isMyMessage ? 8.0 : 0.0,
),
child: Container(
decoration: _buildBoxDecoration(
isLastUser || nOfAttachmentWidgets > 0),
padding: EdgeInsets.all(10),
constraints: BoxConstraints.loose(Size.fromWidth(300)),
child: MarkdownBody(
data: '${widget.message.text}',
onTapLink: (link) {
_launchURL(link);
},
styleSheet: MarkdownStyleSheet.fromTheme(
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,
),
),
),
],
),
],
),
),
],
),
],
),
],
);
} 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(
child: column,
child: IntrinsicWidth(child: column),
onLongPress: () {
if (widget.message.type == 'ephemeral') {
return;
}
if (widget.onMessageActions != null) {
widget.onMessageActions(context, widget.message);
} 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) {
if (!streamChannel.channel.config.reactions &&
!streamChannel.channel.config.replies) {
@@ -640,6 +719,15 @@ class _MessageWidgetState extends State<MessageWidget>
imageUrl:
attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl,
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
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = Colors.black;