add missing permissions and ui

This commit is contained in:
Salvatore Giordano
2022-01-05 11:18:46 +01:00
parent a5baffae29
commit 5d163b9a86
10 changed files with 171 additions and 125 deletions
@@ -300,6 +300,18 @@ class Channel {
return data; return data;
} }
/// List of user permissions on this channel
List<String> get ownCapabilities =>
state?._channelState.channel?.ownCapabilities ?? [];
/// List of user permissions on this channel
Stream<List<String>> get ownCapabilitiesStream {
_checkInitialized();
return state!.channelStateStream
.map((cs) => cs.channel?.ownCapabilities ?? [])
.distinct();
}
/// Channel extra data as a stream. /// Channel extra data as a stream.
Stream<Map<String, Object?>> get extraDataStream { Stream<Map<String, Object?>> get extraDataStream {
_checkInitialized(); _checkInitialized();
@@ -5,6 +5,9 @@ class PermissionType {
/// and user has CreateMessage permission. /// and user has CreateMessage permission.
static const String sendMessage = 'send-message'; static const String sendMessage = 'send-message';
/// Capability required to receive connect events in the channel
static const String connectEvents = 'connect-events';
/// Capability required to send a message /// Capability required to send a message
/// Reactions are enabled for the channel, channel is not frozen /// Reactions are enabled for the channel, channel is not frozen
/// (or user has UseFrozenChannel permission) and user has /// (or user has UseFrozenChannel permission) and user has
@@ -32,10 +35,19 @@ class PermissionType {
/// User has RemoveOwnChannelMembership or UpdateChannelMembers permission /// User has RemoveOwnChannelMembership or UpdateChannelMembers permission
static const String leaveChannel = 'leave-channel'; static const String leaveChannel = 'leave-channel';
/// Ability to receive read events
static const String readEvents = 'read-events';
/// Capability required to pin a message in a channel /// Capability required to pin a message in a channel
/// Corresponds to PinMessage permission /// Corresponds to PinMessage permission
static const String pinMessage = 'pin-message'; static const String pinMessage = 'pin-message';
/// Capability required to quote a message in a channel
static const String quoteMessage = 'quote-message';
/// Capability required to flag a message in a channel
static const String flagMessage = 'flag-message';
/// User has ability to delete any message in the channel /// User has ability to delete any message in the channel
/// User has DeleteMessage permission /// User has DeleteMessage permission
/// which applies to any message in the channel /// which applies to any message in the channel
@@ -61,7 +61,8 @@ class ChannelInfo extends StatelessWidget {
var text = context.translations.membersCountText(memberCount); var text = context.translations.membersCountText(memberCount);
final onlineCount = final onlineCount =
members?.where((m) => m.user?.online == true).length ?? 0; members?.where((m) => m.user?.online == true).length ?? 0;
if (onlineCount > 0) { if (channel.ownCapabilities.contains(PermissionType.connectEvents) &&
onlineCount > 0) {
text += ', ${context.translations.watchersCountText(onlineCount)}'; text += ', ${context.translations.watchersCountText(onlineCount)}';
} }
alternativeWidget = Text( alternativeWidget = Text(
@@ -560,14 +560,8 @@ class _ChannelListViewState extends State<ChannelListView> {
); );
}, },
), ),
if ([ if (channel.ownCapabilities
'admin', .contains(PermissionType.deleteChannel))
'owner',
].contains(channel.state!.members
.firstWhereOrNull(
(m) => m.userId == channel.client.state.currentUser?.id,
)
?.role))
IconSlideAction( IconSlideAction(
color: backgroundColor, color: backgroundColor,
iconWidget: StreamSvgIcon.delete( iconWidget: StreamSvgIcon.delete(
@@ -93,6 +93,9 @@ abstract class Translations {
/// The label for search Gif /// The label for search Gif
String get searchGifLabel; String get searchGifLabel;
/// The label for the MessageInput hint when permission denied on sendMessage
String get sendMessagePermissionError;
/// The label for add a comment or send in case of /// The label for add a comment or send in case of
/// attachments inside [MessageInput] /// attachments inside [MessageInput]
String get addACommentOrSendLabel; String get addACommentOrSendLabel;
@@ -377,6 +380,10 @@ class DefaultTranslations implements Translations {
return 'Pinned by ${pinnedBy.name}'; return 'Pinned by ${pinnedBy.name}';
} }
@override
String get sendMessagePermissionError =>
'You don\'t have permission to send messages';
@override @override
String get emptyMessagesText => 'There are no messages currently'; String get emptyMessagesText => 'There are no messages currently';
@@ -191,7 +191,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
if (widget.showReplyMessage && if (_userPermissions
.contains(PermissionType.quoteMessage) &&
widget.showReplyMessage &&
widget.message.status == MessageSendingStatus.sent) widget.message.status == MessageSendingStatus.sent)
_buildReplyButton(context), _buildReplyButton(context),
if ((widget.showThreadReplyMessage ?? if ((widget.showThreadReplyMessage ??
@@ -207,7 +209,10 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
_isMyMessage && hasEditPermission) _isMyMessage && hasEditPermission)
_buildEditMessage(context), _buildEditMessage(context),
if (widget.showCopyMessage) _buildCopyButton(context), if (widget.showCopyMessage) _buildCopyButton(context),
if (widget.showFlagButton) _buildFlagButton(context), if (_userPermissions
.contains(PermissionType.flagMessage) &&
widget.showFlagButton)
_buildFlagButton(context),
if (widget.showPinButton ?? if (widget.showPinButton ??
_userPermissions _userPermissions
.contains(PermissionType.pinMessage)) .contains(PermissionType.pinMessage))
@@ -680,9 +685,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
@override @override
void didChangeDependencies() { void didChangeDependencies() {
final newStreamChannel = StreamChannel.of(context); final newStreamChannel = StreamChannel.of(context);
_userPermissions = _userPermissions = newStreamChannel.channel.ownCapabilities;
newStreamChannel.channel.state?.channelState.channel?.ownCapabilities ??
[];
_isMyMessage = widget.message.user!.id == _isMyMessage = widget.message.user!.id ==
newStreamChannel.channel.client.state.currentUser!.id; newStreamChannel.channel.client.state.currentUser!.id;
super.didChangeDependencies(); super.didChangeDependencies();
@@ -468,112 +468,127 @@ class MessageInputState extends State<MessageInput>
void _stopSlowMode() => _slowModeTimer?.cancel(); void _stopSlowMode() => _slowModeTimer?.cancel();
@override @override
Widget build(BuildContext context) => MessageValueListenableBuilder( Widget build(BuildContext context) {
valueListenable: _effectiveController, if (!StreamChannel.of(context)
builder: (context, value, _) { .channel
Widget child = DecoratedBox( .ownCapabilities
decoration: BoxDecoration( .contains(PermissionType.sendMessage)) {
color: _messageInputTheme.inputBackgroundColor, return SizedBox(
), height: 50,
child: SafeArea( child: FittedBox(
child: GestureDetector( child: Text(
onPanUpdate: (details) { context.translations.sendMessagePermissionError,
if (details.delta.dy > 0) { style: _messageInputTheme.inputTextStyle,
_focusNode.unfocus(); ),
if (_openFilePickerSection) { ),
setState(() { );
_openFilePickerSection = false; }
}); return MessageValueListenableBuilder(
} valueListenable: _effectiveController,
builder: (context, value, _) {
Widget child = DecoratedBox(
decoration: BoxDecoration(
color: _messageInputTheme.inputBackgroundColor,
),
child: SafeArea(
child: GestureDetector(
onPanUpdate: (details) {
if (details.delta.dy > 0) {
_focusNode.unfocus();
if (_openFilePickerSection) {
setState(() {
_openFilePickerSection = false;
});
} }
}, }
child: Column( },
mainAxisSize: MainAxisSize.min, child: Column(
children: [ mainAxisSize: MainAxisSize.min,
if (_hasQuotedMessage) children: [
Padding( if (_hasQuotedMessage)
padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.all(8),
child: StreamSvgIcon.reply(
color: _streamChatTheme.colorTheme.disabled,
),
),
Text(
context.translations.replyToMessageLabel,
style:
const TextStyle(fontWeight: FontWeight.bold),
),
IconButton(
visualDensity: VisualDensity.compact,
icon: StreamSvgIcon.closeSmall(),
onPressed: () {
_effectiveController.clearQuotedMessage();
_focusNode.unfocus();
},
),
],
),
),
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
child: _buildTextField(context), child: Row(
), mainAxisAlignment: MainAxisAlignment.spaceBetween,
if (_effectiveController.value.parentId != null && children: [
!widget.hideSendAsDm) Padding(
Padding( padding: const EdgeInsets.all(8),
padding: const EdgeInsets.only( child: StreamSvgIcon.reply(
right: 12, color: _streamChatTheme.colorTheme.disabled,
left: 12, ),
bottom: 12, ),
), Text(
child: _buildDmCheckbox(), context.translations.replyToMessageLabel,
style: const TextStyle(fontWeight: FontWeight.bold),
),
IconButton(
visualDensity: VisualDensity.compact,
icon: StreamSvgIcon.closeSmall(),
onPressed: () {
_effectiveController.clearQuotedMessage();
_focusNode.unfocus();
},
),
],
), ),
_buildFilePickerSection(), ),
], Padding(
), padding: const EdgeInsets.symmetric(vertical: 8),
child: _buildTextField(context),
),
if (_effectiveController.value.parentId != null &&
!widget.hideSendAsDm)
Padding(
padding: const EdgeInsets.only(
right: 12,
left: 12,
bottom: 12,
),
child: _buildDmCheckbox(),
),
_buildFilePickerSection(),
],
), ),
), ),
); ),
if (!_isEditing) { );
child = Material( if (!_isEditing) {
elevation: 8, child = Material(
child: child, elevation: 8,
);
}
return MultiOverlay(
childAnchor: Alignment.topCenter,
overlayAnchor: Alignment.bottomCenter,
overlayOptions: [
OverlayOptions(
visible: _showCommandsOverlay,
widget: _buildCommandsOverlayEntry(),
),
OverlayOptions(
visible: _focusNode.hasFocus &&
_effectiveController.text.isNotEmpty &&
_effectiveController.baseOffset > 0 &&
_effectiveController.text
.substring(
0,
_effectiveController.baseOffset,
)
.contains(':'),
widget: _buildEmojiOverlay(),
),
OverlayOptions(
visible: _showMentionsOverlay,
widget: _buildMentionsOverlayEntry(),
),
...widget.customOverlays,
],
child: child, child: child,
); );
}, }
); return MultiOverlay(
childAnchor: Alignment.topCenter,
overlayAnchor: Alignment.bottomCenter,
overlayOptions: [
OverlayOptions(
visible: _showCommandsOverlay,
widget: _buildCommandsOverlayEntry(),
),
OverlayOptions(
visible: _focusNode.hasFocus &&
_effectiveController.text.isNotEmpty &&
_effectiveController.baseOffset > 0 &&
_effectiveController.text
.substring(
0,
_effectiveController.baseOffset,
)
.contains(':'),
widget: _buildEmojiOverlay(),
),
OverlayOptions(
visible: _showMentionsOverlay,
widget: _buildMentionsOverlayEntry(),
),
...widget.customOverlays,
],
child: child,
);
},
);
}
Flex _buildTextField(BuildContext context) => Flex( Flex _buildTextField(BuildContext context) => Flex(
direction: Axis.horizontal, direction: Axis.horizontal,
@@ -701,7 +716,9 @@ class MessageInputState extends State<MessageInput>
? const Offstage() ? const Offstage()
: Wrap( : Wrap(
children: <Widget>[ children: <Widget>[
if (!widget.disableAttachments) if (!widget.disableAttachments &&
channel.ownCapabilities
.contains(PermissionType.uploadFile))
_buildAttachmentButton(context), _buildAttachmentButton(context),
if (widget.showCommandsButton && if (widget.showCommandsButton &&
!_isEditing && !_isEditing &&
@@ -881,7 +898,8 @@ class MessageInputState extends State<MessageInput>
value = value.trim(); value = value.trim();
final channel = StreamChannel.of(context).channel; final channel = StreamChannel.of(context).channel;
if (value.isNotEmpty) { if (channel.ownCapabilities.contains(PermissionType.sendTypingEvents) &&
value.isNotEmpty) {
channel channel
.keyStroke(_effectiveController.value.parentId) .keyStroke(_effectiveController.value.parentId)
// ignore: no-empty-block // ignore: no-empty-block
@@ -1296,9 +1296,7 @@ class _MessageListViewState extends State<MessageListView> {
void didChangeDependencies() { void didChangeDependencies() {
final newStreamChannel = StreamChannel.of(context); final newStreamChannel = StreamChannel.of(context);
_streamTheme = StreamChatTheme.of(context); _streamTheme = StreamChatTheme.of(context);
_userPermissions = _userPermissions = newStreamChannel.channel.ownCapabilities;
newStreamChannel.channel.state?.channelState.channel?.ownCapabilities ??
[];
if (newStreamChannel != streamChannel) { if (newStreamChannel != streamChannel) {
streamChannel = newStreamChannel; streamChannel = newStreamChannel;
@@ -45,13 +45,7 @@ class MessageReactionsModal extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
final user = StreamChat.of(context).currentUser; final user = StreamChat.of(context).currentUser;
final _userPermissions = StreamChannel.of(context) final _userPermissions = StreamChannel.of(context).channel.ownCapabilities;
.channel
.state
?.channelState
.channel
?.ownCapabilities ??
[];
final hasReactionPermission = final hasReactionPermission =
_userPermissions.contains(PermissionType.sendReaction); _userPermissions.contains(PermissionType.sendReaction);
@@ -1250,6 +1250,13 @@ class _MessageWidgetState extends State<MessageWidget>
final channel = StreamChannel.of(context).channel; final channel = StreamChannel.of(context).channel;
if (!channel.ownCapabilities.contains(PermissionType.readEvents)) {
return SendingIndicator(
message: message,
size: style!.fontSize,
);
}
return BetterStreamBuilder<List<Read>>( return BetterStreamBuilder<List<Read>>(
stream: channel.state?.readStream, stream: channel.state?.readStream,
initialData: channel.state?.read, initialData: channel.state?.read,