add missing permissions and ui
This commit is contained in:
@@ -300,6 +300,18 @@ class Channel {
|
||||
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.
|
||||
Stream<Map<String, Object?>> get extraDataStream {
|
||||
_checkInitialized();
|
||||
|
||||
@@ -5,6 +5,9 @@ class PermissionType {
|
||||
/// and user has CreateMessage permission.
|
||||
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
|
||||
/// Reactions are enabled for the channel, channel is not frozen
|
||||
/// (or user has UseFrozenChannel permission) and user has
|
||||
@@ -32,10 +35,19 @@ class PermissionType {
|
||||
/// User has RemoveOwnChannelMembership or UpdateChannelMembers permission
|
||||
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
|
||||
/// Corresponds to PinMessage permission
|
||||
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 DeleteMessage permission
|
||||
/// which applies to any message in the channel
|
||||
|
||||
@@ -61,7 +61,8 @@ class ChannelInfo extends StatelessWidget {
|
||||
var text = context.translations.membersCountText(memberCount);
|
||||
final onlineCount =
|
||||
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)}';
|
||||
}
|
||||
alternativeWidget = Text(
|
||||
|
||||
@@ -560,14 +560,8 @@ class _ChannelListViewState extends State<ChannelListView> {
|
||||
);
|
||||
},
|
||||
),
|
||||
if ([
|
||||
'admin',
|
||||
'owner',
|
||||
].contains(channel.state!.members
|
||||
.firstWhereOrNull(
|
||||
(m) => m.userId == channel.client.state.currentUser?.id,
|
||||
)
|
||||
?.role))
|
||||
if (channel.ownCapabilities
|
||||
.contains(PermissionType.deleteChannel))
|
||||
IconSlideAction(
|
||||
color: backgroundColor,
|
||||
iconWidget: StreamSvgIcon.delete(
|
||||
|
||||
@@ -93,6 +93,9 @@ abstract class Translations {
|
||||
/// The label for search Gif
|
||||
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
|
||||
/// attachments inside [MessageInput]
|
||||
String get addACommentOrSendLabel;
|
||||
@@ -377,6 +380,10 @@ class DefaultTranslations implements Translations {
|
||||
return 'Pinned by ${pinnedBy.name}';
|
||||
}
|
||||
|
||||
@override
|
||||
String get sendMessagePermissionError =>
|
||||
'You don\'t have permission to send messages';
|
||||
|
||||
@override
|
||||
String get emptyMessagesText => 'There are no messages currently';
|
||||
|
||||
|
||||
@@ -191,7 +191,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (widget.showReplyMessage &&
|
||||
if (_userPermissions
|
||||
.contains(PermissionType.quoteMessage) &&
|
||||
widget.showReplyMessage &&
|
||||
widget.message.status == MessageSendingStatus.sent)
|
||||
_buildReplyButton(context),
|
||||
if ((widget.showThreadReplyMessage ??
|
||||
@@ -207,7 +209,10 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
_isMyMessage && hasEditPermission)
|
||||
_buildEditMessage(context),
|
||||
if (widget.showCopyMessage) _buildCopyButton(context),
|
||||
if (widget.showFlagButton) _buildFlagButton(context),
|
||||
if (_userPermissions
|
||||
.contains(PermissionType.flagMessage) &&
|
||||
widget.showFlagButton)
|
||||
_buildFlagButton(context),
|
||||
if (widget.showPinButton ??
|
||||
_userPermissions
|
||||
.contains(PermissionType.pinMessage))
|
||||
@@ -680,9 +685,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
final newStreamChannel = StreamChannel.of(context);
|
||||
_userPermissions =
|
||||
newStreamChannel.channel.state?.channelState.channel?.ownCapabilities ??
|
||||
[];
|
||||
_userPermissions = newStreamChannel.channel.ownCapabilities;
|
||||
_isMyMessage = widget.message.user!.id ==
|
||||
newStreamChannel.channel.client.state.currentUser!.id;
|
||||
super.didChangeDependencies();
|
||||
|
||||
@@ -468,112 +468,127 @@ class MessageInputState extends State<MessageInput>
|
||||
void _stopSlowMode() => _slowModeTimer?.cancel();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => 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;
|
||||
});
|
||||
}
|
||||
Widget build(BuildContext context) {
|
||||
if (!StreamChannel.of(context)
|
||||
.channel
|
||||
.ownCapabilities
|
||||
.contains(PermissionType.sendMessage)) {
|
||||
return SizedBox(
|
||||
height: 50,
|
||||
child: FittedBox(
|
||||
child: Text(
|
||||
context.translations.sendMessagePermissionError,
|
||||
style: _messageInputTheme.inputTextStyle,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
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,
|
||||
children: [
|
||||
if (_hasQuotedMessage)
|
||||
Padding(
|
||||
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();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (_hasQuotedMessage)
|
||||
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(),
|
||||
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();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
_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(
|
||||
elevation: 8,
|
||||
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,
|
||||
],
|
||||
),
|
||||
);
|
||||
if (!_isEditing) {
|
||||
child = Material(
|
||||
elevation: 8,
|
||||
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(
|
||||
direction: Axis.horizontal,
|
||||
@@ -701,7 +716,9 @@ class MessageInputState extends State<MessageInput>
|
||||
? const Offstage()
|
||||
: Wrap(
|
||||
children: <Widget>[
|
||||
if (!widget.disableAttachments)
|
||||
if (!widget.disableAttachments &&
|
||||
channel.ownCapabilities
|
||||
.contains(PermissionType.uploadFile))
|
||||
_buildAttachmentButton(context),
|
||||
if (widget.showCommandsButton &&
|
||||
!_isEditing &&
|
||||
@@ -881,7 +898,8 @@ class MessageInputState extends State<MessageInput>
|
||||
value = value.trim();
|
||||
|
||||
final channel = StreamChannel.of(context).channel;
|
||||
if (value.isNotEmpty) {
|
||||
if (channel.ownCapabilities.contains(PermissionType.sendTypingEvents) &&
|
||||
value.isNotEmpty) {
|
||||
channel
|
||||
.keyStroke(_effectiveController.value.parentId)
|
||||
// ignore: no-empty-block
|
||||
|
||||
@@ -1296,9 +1296,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
void didChangeDependencies() {
|
||||
final newStreamChannel = StreamChannel.of(context);
|
||||
_streamTheme = StreamChatTheme.of(context);
|
||||
_userPermissions =
|
||||
newStreamChannel.channel.state?.channelState.channel?.ownCapabilities ??
|
||||
[];
|
||||
_userPermissions = newStreamChannel.channel.ownCapabilities;
|
||||
|
||||
if (newStreamChannel != streamChannel) {
|
||||
streamChannel = newStreamChannel;
|
||||
|
||||
@@ -45,13 +45,7 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
final user = StreamChat.of(context).currentUser;
|
||||
final _userPermissions = StreamChannel.of(context)
|
||||
.channel
|
||||
.state
|
||||
?.channelState
|
||||
.channel
|
||||
?.ownCapabilities ??
|
||||
[];
|
||||
final _userPermissions = StreamChannel.of(context).channel.ownCapabilities;
|
||||
|
||||
final hasReactionPermission =
|
||||
_userPermissions.contains(PermissionType.sendReaction);
|
||||
|
||||
@@ -1250,6 +1250,13 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
|
||||
final channel = StreamChannel.of(context).channel;
|
||||
|
||||
if (!channel.ownCapabilities.contains(PermissionType.readEvents)) {
|
||||
return SendingIndicator(
|
||||
message: message,
|
||||
size: style!.fontSize,
|
||||
);
|
||||
}
|
||||
|
||||
return BetterStreamBuilder<List<Read>>(
|
||||
stream: channel.state?.readStream,
|
||||
initialData: channel.state?.read,
|
||||
|
||||
Reference in New Issue
Block a user