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;
}
/// 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,