progress on slow mode

This commit is contained in:
groovinchip
2021-07-26 11:42:24 -04:00
parent 848aa550f7
commit 9fb60e93c1
9 changed files with 178 additions and 16 deletions
@@ -130,6 +130,9 @@ class Channel {
return state?.channelStateStream.map((cs) => cs.channel?.cooldown); return state?.channelStateStream.map((cs) => cs.channel?.cooldown);
} }
///
DateTime? cooldownStartedAt;
/// Channel creation date /// Channel creation date
DateTime? get createdAt { DateTime? get createdAt {
_checkInitialized(); _checkInitialized();
@@ -429,6 +432,9 @@ class Channel {
skipPush: skipPush, skipPush: skipPush,
); );
state!.addMessage(response.message); state!.addMessage(response.message);
if (cooldown! > 0) {
cooldownStartedAt = DateTime.now();
}
return response; return response;
} catch (e) { } catch (e) {
if (e is StreamChatNetworkError && e.isRetriable) { if (e is StreamChatNetworkError && e.isRetriable) {
@@ -827,6 +833,20 @@ class Channel {
return _client.updateChannelPartial(id!, type, set: set, unset: unset); return _client.updateChannelPartial(id!, type, set: set, unset: unset);
} }
/// Enable slow mode
Future<UpdateChannelResponse> enableSlowMode({
required int cooldownInterval,
}) async {
_checkInitialized();
return _client.enableSlowdown(id!, type, cooldownInterval);
}
/// Disable slow mode
Future<UpdateChannelResponse> disableSlowMode() async {
_checkInitialized();
return _client.disableSlowdown(id!, type);
}
/// Delete this channel. Messages are permanently removed. /// Delete this channel. Messages are permanently removed.
Future<EmptyResponse> delete() async { Future<EmptyResponse> delete() async {
_checkInitialized(); _checkInitialized();
@@ -1244,6 +1244,28 @@ class StreamChatClient {
language, language,
); );
/// Enables slow mode
Future<UpdateChannelResponse> enableSlowdown(
String channelId,
String channelType,
int cooldown,
) async =>
_chatApi.channel.enableSlowdown(
channelId,
channelType,
cooldown,
);
/// Disables slow mode
Future<UpdateChannelResponse> disableSlowdown(
String channelId,
String channelType,
) async =>
_chatApi.channel.disableSlowdown(
channelId,
channelType,
);
/// Pins provided message /// Pins provided message
/// [timeoutOrExpirationDate] can either be a [DateTime] or a value in seconds /// [timeoutOrExpirationDate] can either be a [DateTime] or a value in seconds
/// to be added to [DateTime.now] /// to be added to [DateTime.now]
@@ -123,6 +123,35 @@ class ChannelApi {
return PartialUpdateChannelResponse.fromJson(response.data); return PartialUpdateChannelResponse.fromJson(response.data);
} }
/// Enable slowdown
Future<UpdateChannelResponse> 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<UpdateChannelResponse> 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 /// Accept invitation to the channel
Future<AcceptInviteResponse> acceptChannelInvite( Future<AcceptInviteResponse> acceptChannelInvite(
String channelId, String channelId,
@@ -83,7 +83,7 @@ class ChannelModel {
final int memberCount; final int memberCount;
/// The number of seconds in a cooldown /// The number of seconds in a cooldown
@JsonKey(includeIfNull: false) @JsonKey(includeIfNull: false, defaultValue: 0)
final int cooldown; final int cooldown;
/// Map of custom channel extraData /// Map of custom channel extraData
@@ -33,6 +33,7 @@ ChannelModel _$ChannelModelFromJson(Map<String, dynamic> json) {
memberCount: json['member_count'] as int? ?? 0, memberCount: json['member_count'] as int? ?? 0,
extraData: json['extra_data'] as Map<String, dynamic>? ?? {}, extraData: json['extra_data'] as Map<String, dynamic>? ?? {},
team: json['team'] as String?, team: json['team'] as String?,
cooldown: json['cooldown'] as int? ?? 0,
); );
} }
@@ -57,6 +58,7 @@ Map<String, dynamic> _$ChannelModelToJson(ChannelModel instance) {
writeNotNull('updated_at', readonly(instance.updatedAt)); writeNotNull('updated_at', readonly(instance.updatedAt));
writeNotNull('deleted_at', readonly(instance.deletedAt)); writeNotNull('deleted_at', readonly(instance.deletedAt));
writeNotNull('member_count', readonly(instance.memberCount)); writeNotNull('member_count', readonly(instance.memberCount));
val['cooldown'] = instance.cooldown;
val['extra_data'] = instance.extraData; val['extra_data'] = instance.extraData;
writeNotNull('team', readonly(instance.team)); writeNotNull('team', readonly(instance.team));
return val; return val;
@@ -36,5 +36,8 @@ OwnUser _$OwnUserFromJson(Map<String, dynamic> json) {
online: json['online'] as bool? ?? false, online: json['online'] as bool? ?? false,
extraData: json['extra_data'] as Map<String, dynamic>? ?? {}, extraData: json['extra_data'] as Map<String, dynamic>? ?? {},
banned: json['banned'] as bool? ?? false, banned: json['banned'] as bool? ?? false,
teams:
(json['teams'] as List<dynamic>?)?.map((e) => e as String).toList() ??
[],
); );
} }
@@ -13,7 +13,7 @@ void main() async {
/// Create a new instance of [StreamChatClient] passing the apikey obtained /// Create a new instance of [StreamChatClient] passing the apikey obtained
/// from your project dashboard. /// from your project dashboard.
final client = StreamChatClient( final client = StreamChatClient(
's2dxdhpxd94g', 'kv7mcsxr24p8',
logLevel: Level.INFO, logLevel: Level.INFO,
)..chatPersistenceClient = chatPersistentClient; )..chatPersistenceClient = chatPersistentClient;
@@ -24,15 +24,19 @@ void main() async {
/// Please see the following for more information: /// Please see the following for more information:
/// https://getstream.io/chat/docs/ios_user_setup_and_tokens/ /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/
await client.connectUser( await client.connectUser(
User(id: 'super-band-9'), User(id: 'salvatore'),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.' 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FsdmF0b3JlIn0.pgiJz7sIc7iP29BHKFwe3nLm5-OaR_1l2P-SlgiC9a8',
'0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
); );
final channel = client.channel('messaging', id: 'godevs'); final channel = client.channel('messaging', id: 'godevs2', extraData: {
'members': ['salvatore'],
});
await channel.watch(); await channel.watch();
await channel.enableSlowMode(cooldownInterval: 20);
print('Channel cooldown set to ${channel.cooldown}');
runApp( runApp(
MyApp( MyApp(
client: client, client: client,
@@ -291,9 +291,13 @@ class MessageInputState extends State<MessageInput> {
bool get _hasQuotedMessage => widget.quotedMessage != null; bool get _hasQuotedMessage => widget.quotedMessage != null;
late DateTime? _cooldownStartedAt;
int? _timeOut;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_startSlowMode();
_focusNode = widget.focusNode ?? FocusNode(); _focusNode = widget.focusNode ?? FocusNode();
_emojiNames = _emojiNames =
Emoji.all().where((it) => it.name != null).map((e) => e.name!); Emoji.all().where((it) => it.name != null).map((e) => e.name!);
@@ -324,6 +328,25 @@ class MessageInputState extends State<MessageInput> {
}); });
} }
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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget child = DecoratedBox( Widget child = DecoratedBox(
@@ -472,13 +495,44 @@ class MessageInputState extends State<MessageInput> {
); );
Widget _animateSendButton(BuildContext context) { Widget _animateSendButton(BuildContext context) {
final sendButton = widget.activeSendButton != null late Widget sendButton;
? InkWell( if (_timeOut != null && _timeOut! > 0) {
onTap: sendMessage, sendButton = _CountdownButton(
child: widget.activeSendButton, count: _timeOut!,
) );
: _buildSendButton(context); } else if (!_messageIsPresent && _attachments.isEmpty) {
return AnimatedCrossFade( 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: (_messageIsPresent || _attachments.isNotEmpty)
? CrossFadeState.showFirst ? CrossFadeState.showFirst
: CrossFadeState.showSecond, : CrossFadeState.showSecond,
@@ -486,7 +540,7 @@ class MessageInputState extends State<MessageInput> {
secondChild: widget.idleSendButton ?? _buildIdleSendButton(context), secondChild: widget.idleSendButton ?? _buildIdleSendButton(context),
duration: _streamChatTheme.messageInputTheme.sendAnimationDuration!, duration: _streamChatTheme.messageInputTheme.sendAnimationDuration!,
alignment: Alignment.center, alignment: Alignment.center,
); );*/
} }
Widget _buildExpandActionsButton() { Widget _buildExpandActionsButton() {
@@ -2082,6 +2136,7 @@ class MessageInputState extends State<MessageInput> {
if (resp.message?.type == 'error') { if (resp.message?.type == 'error') {
_parseExistingMessage(message); _parseExistingMessage(message);
} }
_startSlowMode();
widget.onMessageSent?.call(resp.message); widget.onMessageSent?.call(resp.message);
} catch (e, stk) { } catch (e, stk) {
if (widget.onError != null) { 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'),
),
),
),
);
}
@@ -942,7 +942,7 @@ class _MessageListViewState extends State<MessageListView> {
final currentUser = StreamChat.of(context).user; final currentUser = StreamChat.of(context).user;
final members = StreamChannel.of(context).channel.state?.members ?? []; final members = StreamChannel.of(context).channel.state?.members ?? [];
final currentUserMember = final currentUserMember =
members.firstWhere((e) => e.user!.id == currentUser!.id); members.firstWhereOrNull((e) => e.user!.id == currentUser!.id);
Widget messageWidget = MessageWidget( Widget messageWidget = MessageWidget(
key: ValueKey<String>('MESSAGE-${message.id}'), key: ValueKey<String>('MESSAGE-${message.id}'),
@@ -1049,7 +1049,7 @@ class _MessageListViewState extends State<MessageListView> {
} }
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
}, },
showPinButton: widget.pinPermissions.contains(currentUserMember.role), showPinButton: widget.pinPermissions.contains(currentUserMember?.role),
); );
if (widget.messageBuilder != null) { if (widget.messageBuilder != null) {