add missing permissions and ui
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user