Merge branch 'develop' into feat/pinned

This commit is contained in:
Salvatore Giordano
2021-06-08 09:43:07 +02:00
12 changed files with 167 additions and 110 deletions
+11 -2
View File
@@ -351,9 +351,13 @@ class Channel {
}
/// Send a [message] to this channel.
/// If [skipPush] is true the message will not send a push notification
/// Waits for a [_messageAttachmentsUploadCompleter] to complete
/// before actually sending the message.
Future<SendMessageResponse> sendMessage(Message message) async {
Future<SendMessageResponse> sendMessage(
Message message, {
bool skipPush = false,
}) async {
_checkInitialized();
// Cancelling previous completer in case it's called again in the process
// Eg. Updating the message while the previous call is in progress.
@@ -396,7 +400,12 @@ class Channel {
message = await attachmentsUploadCompleter.future;
}
final response = await _client.sendMessage(message, id!, type);
final response = await _client.sendMessage(
message,
id!,
type,
skipPush: skipPush,
);
state!.addMessage(response.message);
return response;
} catch (error) {
+7 -3
View File
@@ -1329,11 +1329,15 @@ class StreamChatClient {
Future<SendMessageResponse> sendMessage(
Message message,
String channelId,
String channelType,
) async {
String channelType, {
bool skipPush = false,
}) async {
final response = await post(
'/channels/$channelType/$channelId/message',
data: {'message': message.toJson()},
data: {
'message': message.toJson(),
'skip_push': skipPush,
},
);
return decode(response.data, SendMessageResponse.fromJson);
}
@@ -73,7 +73,6 @@ class Message extends Equatable {
this.extraData = const {},
this.deletedAt,
this.status = MessageSendingStatus.sent,
this.skipPush = false,
}) : id = id ?? const Uuid().v4(),
pinExpires = pinExpires?.toUtc(),
createdAt = createdAt ?? DateTime.now(),
@@ -158,10 +157,6 @@ class Message extends Equatable {
@JsonKey(defaultValue: false)
final bool silent;
/// If true the message will not send a push notification
@JsonKey(defaultValue: false)
final bool skipPush;
/// If true the message is shadowed
@JsonKey(
includeIfNull: false,
@@ -253,7 +248,6 @@ class Message extends Equatable {
'pinned_at',
'pin_expires',
'pinned_by',
'skip_push',
];
/// Serialize to json
@@ -291,7 +285,6 @@ class Message extends Equatable {
User? pinnedBy,
Map<String, Object?>? extraData,
MessageSendingStatus? status,
bool? skipPush,
}) {
assert(() {
if (pinExpires is! DateTime &&
@@ -331,7 +324,6 @@ class Message extends Equatable {
pinnedBy: pinnedBy ?? this.pinnedBy,
pinExpires:
pinExpires == _pinExpires ? this.pinExpires : pinExpires as DateTime?,
skipPush: skipPush ?? this.skipPush,
);
}
@@ -398,7 +390,6 @@ class Message extends Equatable {
pinnedBy,
extraData,
status,
skipPush,
];
}
@@ -67,7 +67,6 @@ Message _$MessageFromJson(Map<String, dynamic> json) {
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
skipPush: json['skip_push'] as bool? ?? false,
);
}
@@ -97,7 +96,6 @@ Map<String, dynamic> _$MessageToJson(Message instance) {
writeNotNull('thread_participants', readonly(instance.threadParticipants));
val['show_in_channel'] = instance.showInChannel;
val['silent'] = instance.silent;
val['skip_push'] = instance.skipPush;
writeNotNull('shadowed', readonly(instance.shadowed));
writeNotNull('command', readonly(instance.command));
writeNotNull('created_at', readonly(instance.createdAt));
@@ -52,7 +52,10 @@ void main() {
when(
() => mockDio.post<String>(
'/channels/messaging/testid/message',
data: {'message': message.toJson()},
data: {
'message': message.toJson(),
'skip_push': false,
},
),
).thenAnswer(
(_) async => Response(
@@ -67,6 +70,7 @@ void main() {
verify(() =>
mockDio.post<String>('/channels/messaging/testid/message', data: {
'message': message.toJson(),
'skip_push': false,
})).called(1);
});
@@ -902,7 +902,6 @@ void main() {
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"skip_push": false,
"silent": false,
"pinned": false,
"pinned_at": null,
@@ -919,7 +918,6 @@ void main() {
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"skip_push": false,
"silent": false,
"pinned": false,
"pinned_at": null,
@@ -929,7 +927,6 @@ void main() {
{
"id": "dry-meadow-0-53e6299f-9b97-4a9c-a27e-7e2dde49b7e0",
"text": "test message",
"skip_push": false,
"attachments": [],
"parent_id": null,
"quoted_message": null,
@@ -952,7 +949,6 @@ void main() {
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"skip_push": false,
"status": "SENT",
"silent": false,
"pinned": false,
@@ -964,7 +960,6 @@ void main() {
"id": "dry-meadow-0-64d7970f-ede8-4b31-9738-1bc1756d2bfe",
"text": "test",
"attachments": [],
"skip_push": false,
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
@@ -982,7 +977,6 @@ void main() {
"text": "hi",
"attachments": [],
"parent_id": null,
"skip_push": false,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
@@ -1000,7 +994,6 @@ void main() {
"attachments": [],
"parent_id": null,
"quoted_message": null,
"skip_push": false,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
@@ -1023,7 +1016,6 @@ void main() {
"status": "SENT",
"silent": false,
"pinned": false,
"skip_push": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
@@ -1041,7 +1033,6 @@ void main() {
"silent": false,
"pinned": false,
"pinned_at": null,
"skip_push": false,
"pin_expires": null,
"pinned_by": null
},
@@ -1053,7 +1044,6 @@ void main() {
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"skip_push": false,
"mentioned_users": [],
"status": "SENT",
"silent": false,
@@ -1071,7 +1061,6 @@ void main() {
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"skip_push": false,
"status": "SENT",
"silent": false,
"pinned": false,
@@ -1090,7 +1079,6 @@ void main() {
"mentioned_users": [],
"status": "SENT",
"silent": false,
"skip_push": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
@@ -1100,7 +1088,6 @@ void main() {
"id": "icy-recipe-7-935c396e-ddf8-4a9a-951c-0a12fa5bf055",
"text": "what are you doing?",
"attachments": [],
"skip_push": false,
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
@@ -1118,7 +1105,6 @@ void main() {
"text": "👍",
"attachments": [],
"parent_id": null,
"skip_push": false,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
@@ -1134,7 +1120,6 @@ void main() {
"id": "snowy-credit-3-3e0c1a0d-d22f-42ee-b2a1-f9f49477bf21",
"text": "sdasas",
"attachments": [],
"skip_push": false,
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
@@ -1155,7 +1140,6 @@ void main() {
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"skip_push": false,
"mentioned_users": [],
"status": "SENT",
"silent": false,
@@ -1168,7 +1152,6 @@ void main() {
"id": "snowy-credit-3-cfaf0b46-1daa-49c5-947c-b16d6697487d",
"text": "nhisagdhsadz",
"attachments": [],
"skip_push": false,
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
@@ -1187,7 +1170,6 @@ void main() {
"attachments": [],
"parent_id": null,
"quoted_message": null,
"skip_push": false,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
@@ -1204,7 +1186,6 @@ void main() {
"attachments": [],
"parent_id": null,
"quoted_message": null,
"skip_push": false,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
@@ -1212,7 +1193,6 @@ void main() {
"silent": false,
"pinned": false,
"pinned_at": null,
"skip_push": false,
"pin_expires": null,
"pinned_by": null
},
@@ -1229,7 +1209,6 @@ void main() {
"silent": false,
"pinned": false,
"pinned_at": null,
"skip_push": false,
"pin_expires": null,
"pinned_by": null
},
@@ -1246,7 +1225,6 @@ void main() {
"silent": false,
"pinned": false,
"pinned_at": null,
"skip_push": false,
"pin_expires": null,
"pinned_by": null
},
@@ -1263,7 +1241,6 @@ void main() {
"silent": false,
"pinned": false,
"pinned_at": null,
"skip_push": false,
"pin_expires": null,
"pinned_by": null
},
@@ -1280,7 +1257,6 @@ void main() {
"silent": false,
"pinned": false,
"pinned_at": null,
"skip_push": false,
"pin_expires": null,
"pinned_by": null
},
@@ -1297,7 +1273,6 @@ void main() {
"silent": false,
"pinned": false,
"pinned_at": null,
"skip_push": false,
"pin_expires": null,
"pinned_by": null
},
@@ -1314,7 +1289,6 @@ void main() {
"silent": false,
"pinned": false,
"pinned_at": null,
"skip_push": false,
"pin_expires": null,
"pinned_by": null
}
@@ -133,7 +133,6 @@ void main() {
"id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f",
"text": "https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA",
"silent": false,
"skip_push": false,
"attachments": [
{
"type": "video",
@@ -12,6 +12,9 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Callback called when tapping on a channel
typedef ChannelTapCallback = void Function(Channel, Widget?);
/// Callback called when tapping on a channel
typedef ChannelInfoCallback = void Function(Channel);
/// Builder used to create a custom [ChannelPreview] from a [Channel]
typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel);
@@ -78,6 +81,9 @@ class ChannelListView extends StatefulWidget {
this.emptyBuilder,
this.loadingBuilder,
this.listBuilder,
this.onMoreDetailsPressed,
this.onDeletePressed,
this.swipeActions,
}) : super(key: key);
/// If true a default swipe to action behaviour will be added to this widget
@@ -158,6 +164,15 @@ class ChannelListView extends StatefulWidget {
/// The builder used when the channel list is empty.
final WidgetBuilder? emptyBuilder;
/// Callback used when the more details slidable option is pressed
final ChannelInfoCallback? onMoreDetailsPressed;
/// Callback used when the delete slidable option is pressed
final ChannelInfoCallback? onDeletePressed;
/// List of actions for slidable
final List<SwipeAction>? swipeActions;
@override
_ChannelListViewState createState() => _ChannelListViewState();
}
@@ -465,61 +480,79 @@ class _ChannelListViewState extends State<ChannelListView> {
enabled: widget.swipeToAction,
actionPane: const SlidableBehindActionPane(),
actionExtentRatio: 0.12,
secondaryActions: <Widget>[
IconSlideAction(
color: backgroundColor,
icon: Icons.more_horiz,
onTap: () {
showModalBottomSheet(
clipBehavior: Clip.hardEdge,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(32),
topRight: Radius.circular(32),
),
),
context: context,
builder: (context) => StreamChannel(
channel: channel,
child: ChannelBottomSheet(
onViewInfoTap: () {
widget.onViewInfoTap?.call(channel);
},
),
),
);
},
),
if ([
'admin',
'owner',
].contains(channel.state!.members
.firstWhereOrNull(
(m) => m.userId == channel.client.state.user?.id)
?.role))
IconSlideAction(
color: backgroundColor,
iconWidget: StreamSvgIcon.delete(
color: chatThemeData.colorTheme.accentRed,
secondaryActions: widget.swipeActions
?.map((e) => IconSlideAction(
color: e.color,
iconWidget: e.iconWidget,
onTap: () {
e.onTap?.call(channel);
},
))
.toList() ??
<Widget>[
IconSlideAction(
color: backgroundColor,
icon: Icons.more_horiz,
onTap: widget.onMoreDetailsPressed != null
? () {
widget.onMoreDetailsPressed!(channel);
}
: () {
showModalBottomSheet(
clipBehavior: Clip.hardEdge,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(32),
topRight: Radius.circular(32),
),
),
context: context,
builder: (context) => StreamChannel(
channel: channel,
child: ChannelBottomSheet(
onViewInfoTap: () {
widget.onViewInfoTap?.call(channel);
},
),
),
);
},
),
onTap: () async {
final res = await showConfirmationDialog(
context,
title: 'Delete Conversation',
okText: 'DELETE',
question:
'Are you sure you want to delete this conversation?',
cancelText: 'CANCEL',
icon: StreamSvgIcon.delete(
if ([
'admin',
'owner',
].contains(channel.state!.members
.firstWhereOrNull(
(m) => m.userId == channel.client.state.user?.id)
?.role))
IconSlideAction(
color: backgroundColor,
iconWidget: StreamSvgIcon.delete(
color: chatThemeData.colorTheme.accentRed,
),
);
if (res == true) {
await channel.delete();
}
},
),
],
onTap: widget.onDeletePressed != null
? () {
widget.onDeletePressed!(channel);
}
: () async {
final res = await showConfirmationDialog(
context,
title: 'Delete Conversation',
okText: 'DELETE',
question:
// ignore: lines_longer_than_80_chars
'Are you sure you want to delete this conversation?',
cancelText: 'CANCEL',
icon: StreamSvgIcon.delete(
color: chatThemeData.colorTheme.accentRed,
),
);
if (res == true) {
await channel.delete();
}
},
),
],
child: Container(
color: chatThemeData.colorTheme.whiteSnow,
child: widget.channelPreviewBuilder?.call(context, channel) ??
@@ -640,3 +673,22 @@ class _ChannelListViewState extends State<ChannelListView> {
);
}
}
/// Class for slidable action
class SwipeAction {
/// Constructor for creating [SwipeAction]
SwipeAction({
this.color,
required this.iconWidget,
this.onTap,
});
/// Background color of action
Color? color;
/// Widget to display as icon
Widget iconWidget;
/// Callback when icon is tapped
ChannelInfoCallback? onTap;
}
@@ -37,6 +37,7 @@ class MessageActionsModal extends StatefulWidget {
this.customActions = const [],
this.attachmentBorderRadiusGeometry,
this.onCopyTap,
this.textBuilder,
}) : super(key: key);
/// Builder for edit message
@@ -105,6 +106,9 @@ class MessageActionsModal extends StatefulWidget {
/// List of custom actions
final List<MessageAction> customActions;
/// Customize the MessageWidget textBuilder
final Widget Function(BuildContext context, Message message)? textBuilder;
@override
_MessageActionsModalState createState() => _MessageActionsModalState();
}
@@ -231,6 +235,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
shape: widget.messageShape,
attachmentShape: widget.attachmentShape,
showPinHighlight: false,
textBuilder: widget.textBuilder,
),
),
const SizedBox(height: 8),
@@ -156,9 +156,10 @@ class MessageListView extends StatefulWidget {
this.onMessageTap,
this.onSystemMessageTap,
this.onAttachmentTap,
this.textBuilder,
this.onLinkTap,
this.pinPermissions = const [],
this.textBuilder,
this.usernameBuilder,
}) : super(key: key);
/// Function used to build a custom message widget
@@ -260,7 +261,10 @@ class MessageListView extends StatefulWidget {
final void Function(Message message, Attachment attachment)? onAttachmentTap;
/// Customize the MessageWidget textBuilder
final void Function(BuildContext context, Message message)? textBuilder;
final Widget Function(BuildContext context, Message message)? textBuilder;
/// Customize the MessageWidget usernameBuilder
final Widget Function(BuildContext context, Message message)? usernameBuilder;
/// Callback for when link is tapped
final void Function(String link)? onLinkTap;
@@ -865,8 +869,8 @@ class _MessageListViewState extends State<MessageListView> {
}
FocusScope.of(context).unfocus();
},
textBuilder:
widget.textBuilder as Widget Function(BuildContext, Message)?,
textBuilder: widget.textBuilder,
usernameBuilder: widget.usernameBuilder,
onLinkTap: widget.onLinkTap,
showPinButton: widget.pinPermissions.contains(currentUserMember.role),
);
@@ -1070,8 +1074,8 @@ class _MessageListViewState extends State<MessageListView> {
FocusScope.of(context).unfocus();
},
onAttachmentTap: widget.onAttachmentTap,
textBuilder:
widget.textBuilder as Widget Function(BuildContext, Message)?,
textBuilder: widget.textBuilder,
usernameBuilder: widget.usernameBuilder,
onLinkTap: widget.onLinkTap,
showPinButton: widget.pinPermissions.contains(currentUserMember.role),
);
@@ -23,6 +23,7 @@ class MessageReactionsModal extends StatelessWidget {
this.showUserAvatar = DisplayWidget.show,
this.onUserAvatarTap,
this.attachmentBorderRadiusGeometry,
this.textBuilder,
}) : super(key: key);
/// Message to display reactions of
@@ -52,6 +53,9 @@ class MessageReactionsModal extends StatelessWidget {
/// [BorderRadius] to apply to attachments
final BorderRadius? attachmentBorderRadiusGeometry;
/// Customize the MessageWidget textBuilder
final Widget Function(BuildContext context, Message message)? textBuilder;
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
@@ -157,6 +161,7 @@ class MessageReactionsModal extends StatelessWidget {
),
showReactionPickerIndicator: showReactions &&
(message.status == MessageSendingStatus.sent),
textBuilder: textBuilder,
showPinHighlight: false,
),
),
@@ -104,6 +104,7 @@ class MessageWidget extends StatefulWidget {
this.onQuotedMessageTap,
this.customActions = const [],
this.onAttachmentTap,
this.usernameBuilder,
}) : attachmentBuilders = {
'image': (context, message, attachments) {
final border = RoundedRectangleBorder(
@@ -267,6 +268,9 @@ class MessageWidget extends StatefulWidget {
/// Widget builder for building text
final Widget Function(BuildContext, Message)? textBuilder;
/// Widget builder for building username
final Widget Function(BuildContext, Message)? usernameBuilder;
/// Function called on long press
final void Function(BuildContext, Message)? onMessageActions;
@@ -751,14 +755,7 @@ class _MessageWidgetState extends State<MessageWidget>
child: Text(msg, style: widget.messageTheme.replies),
),
],
if (showUsername)
Text(
widget.message.user!.name,
maxLines: 1,
key: usernameKey,
style: widget.messageTheme.messageAuthor,
overflow: TextOverflow.ellipsis,
),
if (showUsername) _buildUsername(usernameKey),
if (showTimeStamp)
Text(
Jiffy(widget.message.createdAt.toLocal()).jm,
@@ -821,6 +818,19 @@ class _MessageWidgetState extends State<MessageWidget>
);
}
Widget _buildUsername(Key usernameKey) {
if (widget.usernameBuilder != null) {
return widget.usernameBuilder!(context, widget.message);
}
return Text(
widget.message.user!.name,
maxLines: 1,
key: usernameKey,
style: widget.messageTheme.messageAuthor,
overflow: TextOverflow.ellipsis,
);
}
Widget _buildUrlAttachment() {
final urlAttachment = widget.message.attachments
.firstWhere((element) => element.ogScrapeUrl != null);
@@ -912,6 +922,7 @@ class _MessageWidgetState extends State<MessageWidget>
builder: (context) => StreamChannel(
channel: channel,
child: MessageActionsModal(
textBuilder: widget.textBuilder,
onCopyTap: (message) =>
Clipboard.setData(ClipboardData(text: message.text)),
attachmentBorderRadiusGeometry:
@@ -962,6 +973,7 @@ class _MessageWidgetState extends State<MessageWidget>
builder: (context) => StreamChannel(
channel: channel,
child: MessageReactionsModal(
textBuilder: widget.textBuilder,
attachmentBorderRadiusGeometry:
widget.attachmentBorderRadiusGeometry as BorderRadius?,
showUserAvatar: