From 9fb60e93c1c93d13b7b540a4f2bd3ef5dee6e9e7 Mon Sep 17 00:00:00 2001 From: groovinchip Date: Mon, 26 Jul 2021 11:42:24 -0400 Subject: [PATCH] progress on slow mode --- .../stream_chat/lib/src/client/channel.dart | 20 ++++ .../stream_chat/lib/src/client/client.dart | 22 +++++ .../lib/src/core/api/channel_api.dart | 29 ++++++ .../lib/src/core/models/channel_model.dart | 2 +- .../lib/src/core/models/channel_model.g.dart | 2 + .../lib/src/core/models/own_user.g.dart | 3 + .../stream_chat_flutter/example/lib/main.dart | 14 ++- .../lib/src/message_input.dart | 98 +++++++++++++++++-- .../lib/src/message_list_view.dart | 4 +- 9 files changed, 178 insertions(+), 16 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 4a7cbbb5..8e298fc9 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -130,6 +130,9 @@ class Channel { return state?.channelStateStream.map((cs) => cs.channel?.cooldown); } + /// + DateTime? cooldownStartedAt; + /// Channel creation date DateTime? get createdAt { _checkInitialized(); @@ -429,6 +432,9 @@ class Channel { skipPush: skipPush, ); state!.addMessage(response.message); + if (cooldown! > 0) { + cooldownStartedAt = DateTime.now(); + } return response; } catch (e) { if (e is StreamChatNetworkError && e.isRetriable) { @@ -827,6 +833,20 @@ class Channel { return _client.updateChannelPartial(id!, type, set: set, unset: unset); } + /// Enable slow mode + Future enableSlowMode({ + required int cooldownInterval, + }) async { + _checkInitialized(); + return _client.enableSlowdown(id!, type, cooldownInterval); + } + + /// Disable slow mode + Future disableSlowMode() async { + _checkInitialized(); + return _client.disableSlowdown(id!, type); + } + /// Delete this channel. Messages are permanently removed. Future delete() async { _checkInitialized(); diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 050d36f1..2ea20e60 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -1244,6 +1244,28 @@ class StreamChatClient { language, ); + /// Enables slow mode + Future enableSlowdown( + String channelId, + String channelType, + int cooldown, + ) async => + _chatApi.channel.enableSlowdown( + channelId, + channelType, + cooldown, + ); + + /// Disables slow mode + Future disableSlowdown( + String channelId, + String channelType, + ) async => + _chatApi.channel.disableSlowdown( + channelId, + channelType, + ); + /// Pins provided message /// [timeoutOrExpirationDate] can either be a [DateTime] or a value in seconds /// to be added to [DateTime.now] diff --git a/packages/stream_chat/lib/src/core/api/channel_api.dart b/packages/stream_chat/lib/src/core/api/channel_api.dart index 1ba7f333..31a64cf8 100644 --- a/packages/stream_chat/lib/src/core/api/channel_api.dart +++ b/packages/stream_chat/lib/src/core/api/channel_api.dart @@ -123,6 +123,35 @@ class ChannelApi { return PartialUpdateChannelResponse.fromJson(response.data); } + /// Enable slowdown + Future enableSlowdown( + String channelId, + String channelType, + int cooldown, + ) async { + final response = await _client.post( + _getChannelUrl(channelId, channelType), + data: { + 'cooldown': cooldown, + }, + ); + return UpdateChannelResponse.fromJson(response.data); + } + + /// Disable slowdown + Future disableSlowdown( + String channelId, + String channelType, + ) async { + final response = await _client.post( + _getChannelUrl(channelId, channelType), + data: { + 'cooldown': 0, + }, + ); + return UpdateChannelResponse.fromJson(response.data); + } + /// Accept invitation to the channel Future acceptChannelInvite( String channelId, diff --git a/packages/stream_chat/lib/src/core/models/channel_model.dart b/packages/stream_chat/lib/src/core/models/channel_model.dart index 3df04007..a4e6db79 100644 --- a/packages/stream_chat/lib/src/core/models/channel_model.dart +++ b/packages/stream_chat/lib/src/core/models/channel_model.dart @@ -83,7 +83,7 @@ class ChannelModel { final int memberCount; /// The number of seconds in a cooldown - @JsonKey(includeIfNull: false) + @JsonKey(includeIfNull: false, defaultValue: 0) final int cooldown; /// Map of custom channel extraData diff --git a/packages/stream_chat/lib/src/core/models/channel_model.g.dart b/packages/stream_chat/lib/src/core/models/channel_model.g.dart index 4bde3d4a..d9adf94b 100644 --- a/packages/stream_chat/lib/src/core/models/channel_model.g.dart +++ b/packages/stream_chat/lib/src/core/models/channel_model.g.dart @@ -33,6 +33,7 @@ ChannelModel _$ChannelModelFromJson(Map json) { memberCount: json['member_count'] as int? ?? 0, extraData: json['extra_data'] as Map? ?? {}, team: json['team'] as String?, + cooldown: json['cooldown'] as int? ?? 0, ); } @@ -57,6 +58,7 @@ Map _$ChannelModelToJson(ChannelModel instance) { writeNotNull('updated_at', readonly(instance.updatedAt)); writeNotNull('deleted_at', readonly(instance.deletedAt)); writeNotNull('member_count', readonly(instance.memberCount)); + val['cooldown'] = instance.cooldown; val['extra_data'] = instance.extraData; writeNotNull('team', readonly(instance.team)); return val; diff --git a/packages/stream_chat/lib/src/core/models/own_user.g.dart b/packages/stream_chat/lib/src/core/models/own_user.g.dart index 26e4786e..465faff2 100644 --- a/packages/stream_chat/lib/src/core/models/own_user.g.dart +++ b/packages/stream_chat/lib/src/core/models/own_user.g.dart @@ -36,5 +36,8 @@ OwnUser _$OwnUserFromJson(Map json) { online: json['online'] as bool? ?? false, extraData: json['extra_data'] as Map? ?? {}, banned: json['banned'] as bool? ?? false, + teams: + (json['teams'] as List?)?.map((e) => e as String).toList() ?? + [], ); } diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index 89362d81..6f038616 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -13,7 +13,7 @@ void main() async { /// Create a new instance of [StreamChatClient] passing the apikey obtained /// from your project dashboard. final client = StreamChatClient( - 's2dxdhpxd94g', + 'kv7mcsxr24p8', logLevel: Level.INFO, )..chatPersistenceClient = chatPersistentClient; @@ -24,15 +24,19 @@ void main() async { /// Please see the following for more information: /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/ await client.connectUser( - User(id: 'super-band-9'), - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.' - '0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + User(id: 'salvatore'), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FsdmF0b3JlIn0.pgiJz7sIc7iP29BHKFwe3nLm5-OaR_1l2P-SlgiC9a8', ); - final channel = client.channel('messaging', id: 'godevs'); + final channel = client.channel('messaging', id: 'godevs2', extraData: { + 'members': ['salvatore'], + }); await channel.watch(); + await channel.enableSlowMode(cooldownInterval: 20); + print('Channel cooldown set to ${channel.cooldown}'); + runApp( MyApp( client: client, diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 5a4f6822..c3c2e30a 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -291,9 +291,13 @@ class MessageInputState extends State { bool get _hasQuotedMessage => widget.quotedMessage != null; + late DateTime? _cooldownStartedAt; + int? _timeOut; + @override void initState() { super.initState(); + _startSlowMode(); _focusNode = widget.focusNode ?? FocusNode(); _emojiNames = Emoji.all().where((it) => it.name != null).map((e) => e.name!); @@ -324,6 +328,25 @@ class MessageInputState extends State { }); } + void _startSlowMode() { + if (StreamChannel.of(context).channel.cooldownStartedAt != null) { + _cooldownStartedAt = StreamChannel.of(context).channel.cooldownStartedAt; + if (DateTime.now().difference(_cooldownStartedAt!).inSeconds < + StreamChannel.of(context).channel.cooldown!) { + _timeOut = StreamChannel.of(context).channel.cooldown! - + DateTime.now().difference(_cooldownStartedAt!).inSeconds; + Timer.periodic(const Duration(seconds: 1), (timer) { + if (_timeOut == 0) { + timer.cancel(); + } else { + print('Time left until cooldown is over: $_timeOut'); + setState(() => _timeOut = _timeOut! - 1); + } + }); + } + } + } + @override Widget build(BuildContext context) { Widget child = DecoratedBox( @@ -472,13 +495,44 @@ class MessageInputState extends State { ); Widget _animateSendButton(BuildContext context) { - final sendButton = widget.activeSendButton != null - ? InkWell( - onTap: sendMessage, - child: widget.activeSendButton, - ) - : _buildSendButton(context); - return AnimatedCrossFade( + 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); + } + + /*if (_timeOut == null || _timeOut == 0) { + sendButton = widget.activeSendButton != null + ? InkWell( + onTap: sendMessage, + child: widget.activeSendButton, + ) + : _buildSendButton(context); + } else { + sendButton = _CountdownButton( + count: _timeOut!, + ); + } + + if (!_messageIsPresent && _attachments.isEmpty) { + sendButton = widget.idleSendButton ?? _buildIdleSendButton(context); + }*/ + + return AnimatedSwitcher( + duration: _streamChatTheme.messageInputTheme.sendAnimationDuration!, + child: sendButton, + ); + /*return AnimatedCrossFade( crossFadeState: (_messageIsPresent || _attachments.isNotEmpty) ? CrossFadeState.showFirst : CrossFadeState.showSecond, @@ -486,7 +540,7 @@ class MessageInputState extends State { secondChild: widget.idleSendButton ?? _buildIdleSendButton(context), duration: _streamChatTheme.messageInputTheme.sendAnimationDuration!, alignment: Alignment.center, - ); + );*/ } Widget _buildExpandActionsButton() { @@ -2082,6 +2136,7 @@ class MessageInputState extends State { if (resp.message?.type == 'error') { _parseExistingMessage(message); } + _startSlowMode(); widget.onMessageSent?.call(resp.message); } catch (e, stk) { if (widget.onError != null) { @@ -2347,3 +2402,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'), + ), + ), + ), + ); +} diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 3d0a3acd..8a236cbd 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -942,7 +942,7 @@ class _MessageListViewState extends State { final currentUser = StreamChat.of(context).user; final members = StreamChannel.of(context).channel.state?.members ?? []; final currentUserMember = - members.firstWhere((e) => e.user!.id == currentUser!.id); + members.firstWhereOrNull((e) => e.user!.id == currentUser!.id); Widget messageWidget = MessageWidget( key: ValueKey('MESSAGE-${message.id}'), @@ -1049,7 +1049,7 @@ class _MessageListViewState extends State { } FocusScope.of(context).unfocus(); }, - showPinButton: widget.pinPermissions.contains(currentUserMember.role), + showPinButton: widget.pinPermissions.contains(currentUserMember?.role), ); if (widget.messageBuilder != null) {