Merge pull request #626 from GetStream/cds-189

feat(ui, llc): slow mode
This commit is contained in:
Salvatore Giordano
2021-08-23 09:58:28 +02:00
committed by GitHub
28 changed files with 398 additions and 16 deletions
@@ -25,6 +25,8 @@ typedef ActionButtonBuilder = Widget Function(
> **_NOTE:_** The last parameter is the default `ActionButton`
You can call `.copyWith` to customize just a subset of properties.
- Added slow mode which allows a cooldown period after a user sends a message.
🔄 Changed
Theming has been upgraded! Most theme classes now have `InheritedTheme` classes associated with
@@ -100,6 +100,9 @@ abstract class Translations {
/// The label for write a message in [MessageInput]
String get writeAMessageLabel;
/// The label for slow mode enabled in [MessageInput]
String get slowModeOnLabel;
/// The label for instant commands in [MessageInput]
String get instantCommandsLabel;
@@ -667,4 +670,7 @@ class DefaultTranslations implements Translations {
@override
String get replyToMessageLabel => 'Reply to Message';
@override
String get slowModeOnLabel => 'Slow mode ON';
}
@@ -315,9 +315,15 @@ class MessageInputState extends State<MessageInput> {
bool get _hasQuotedMessage => widget.quotedMessage != null;
late DateTime? _cooldownStartedAt;
int? _timeOut;
Timer? _slowModeTimer;
@override
void initState() {
super.initState();
_startSlowMode();
_focusNode = widget.focusNode ?? FocusNode();
_emojiNames =
Emoji.all().where((it) => it.name != null).map((e) => e.name!);
@@ -348,6 +354,25 @@ class MessageInputState extends State<MessageInput> {
});
}
void _startSlowMode() {
final channel = StreamChannel.of(context).channel;
if (channel.cooldownStartedAt != null) {
_cooldownStartedAt = channel.cooldownStartedAt;
if (DateTime.now().difference(_cooldownStartedAt!).inSeconds <
channel.cooldown!) {
_timeOut = channel.cooldown! -
DateTime.now().difference(_cooldownStartedAt!).inSeconds;
_slowModeTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (_timeOut == 0) {
timer.cancel();
} else {
setState(() => _timeOut = _timeOut! - 1);
}
});
}
}
}
@override
Widget build(BuildContext context) {
Widget child = DecoratedBox(
@@ -496,20 +521,25 @@ class MessageInputState extends State<MessageInput> {
);
Widget _animateSendButton(BuildContext context) {
final sendButton = widget.activeSendButton != null
? InkWell(
onTap: sendMessage,
child: widget.activeSendButton,
)
: _buildSendButton(context);
return AnimatedCrossFade(
crossFadeState: (_messageIsPresent || _attachments.isNotEmpty)
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
firstChild: sendButton,
secondChild: widget.idleSendButton ?? _buildIdleSendButton(context),
duration: _messageInputTheme.sendAnimationDuration!,
alignment: Alignment.center,
late Widget sendButton;
if (_timeOut != null && _timeOut! > 0) {
sendButton = _CountdownButton(
count: _timeOut!,
);
} else if (!_messageIsPresent && _attachments.isEmpty) {
sendButton = widget.idleSendButton ?? _buildIdleSendButton(context);
} else {
sendButton = widget.activeSendButton != null
? InkWell(
onTap: sendMessage,
child: widget.activeSendButton,
)
: _buildSendButton(context);
}
return AnimatedSwitcher(
duration: _streamChatTheme.messageInputTheme.sendAnimationDuration!,
child: sendButton,
);
}
@@ -781,6 +811,10 @@ class MessageInputState extends State<MessageInput> {
if (_attachments.isNotEmpty) {
return context.translations.addACommentOrSendLabel;
}
if (_timeOut != 0 && _timeOut != null) {
return context.translations.slowModeOnLabel;
}
return context.translations.writeAMessageLabel;
}
@@ -2115,6 +2149,7 @@ class MessageInputState extends State<MessageInput> {
if (resp.message?.type == 'error') {
_parseExistingMessage(message);
}
_startSlowMode();
widget.onMessageSent?.call(resp.message);
} catch (e, stk) {
if (widget.onError != null) {
@@ -2207,6 +2242,7 @@ class MessageInputState extends State<MessageInput> {
_emojiOverlay?.remove();
_mentionsOverlay?.remove();
_keyboardListener?.cancel();
_slowModeTimer?.cancel();
super.dispose();
}
@@ -2380,3 +2416,30 @@ class _PickerWidgetState extends State<_PickerWidget> {
});
}
}
class _CountdownButton extends StatelessWidget {
const _CountdownButton({
Key? key,
required this.count,
}) : super(key: key);
final int count;
@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.all(8),
child: DecoratedBox(
decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.disabled,
shape: BoxShape.circle,
),
child: SizedBox(
height: 24,
width: 24,
child: Center(
child: Text('$count'),
),
),
),
);
}
@@ -69,4 +69,69 @@ void main() {
expect(find.byKey(const Key('messageInputText')), findsOneWidget);
},
);
testWidgets(
'checks message input slow mode',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(() => client.state).thenReturn(clientState);
when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(() => channel.state).thenReturn(channelState);
when(() => channel.cooldown).thenReturn(10);
when(() => channel.cooldownStartedAt).thenReturn(DateTime.now());
when(() => channel.client).thenReturn(client);
when(() => channel.isMuted).thenReturn(false);
when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(() => channel.extraData).thenReturn({
'name': 'test',
});
when(() => channelState.membersStream).thenAnswer((i) => Stream.value([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
)
]));
when(() => channelState.members).thenReturn([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
),
]);
when(() => channelState.messages).thenReturn([
Message(
text: 'hello',
user: User(id: 'other-user'),
)
]);
when(() => channelState.messagesStream).thenAnswer((i) => Stream.value([
Message(
text: 'hello',
user: User(id: 'other-user'),
)
]));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: const Scaffold(
body: MessageInput(),
),
),
),
));
expect(find.text('Slow mode ON'), findsOneWidget);
},
);
}