Merge branch 'feature/new-ui' into hotfix/commands
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -9,7 +10,6 @@ import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_flutter/src/channels_bloc.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||
import 'package:stream_chat_flutter/src/utils.dart';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import '../stream_chat_flutter.dart';
|
||||
import 'channel_bottom_sheet.dart';
|
||||
|
||||
@@ -201,7 +201,8 @@ class ChannelPreview extends StatelessWidget {
|
||||
stream: channel.state.messagesStream,
|
||||
initialData: channel.state.messages,
|
||||
builder: (context, snapshot) {
|
||||
final lastMessage = channel.state.lastMessage;
|
||||
final lastMessage = snapshot.data
|
||||
?.lastWhere((m) => m.shadowed != true, orElse: () => null);
|
||||
if (lastMessage == null) {
|
||||
return SizedBox();
|
||||
}
|
||||
|
||||
+24
-13
@@ -14,11 +14,19 @@ class ChannelsBloc extends StatefulWidget {
|
||||
/// Set this to true to prevent channels to be brought to the top of the list when a new message arrives
|
||||
final bool lockChannelsOrder;
|
||||
|
||||
/// Comparator used to sort the channels when a message.new event is received
|
||||
final Comparator<Channel> channelsComparator;
|
||||
|
||||
/// Function used to evaluate if a channel should be added to the list when a message.new event is received
|
||||
final bool Function(Event) shouldAddChannel;
|
||||
|
||||
/// Instantiate a new ChannelsBloc
|
||||
const ChannelsBloc({
|
||||
Key key,
|
||||
this.child,
|
||||
this.lockChannelsOrder = false,
|
||||
this.channelsComparator,
|
||||
this.shouldAddChannel,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -85,23 +93,21 @@ class ChannelsBlocState extends State<ChannelsBloc>
|
||||
paginationParams.offset == null ||
|
||||
paginationParams.offset == 0;
|
||||
final oldChannels = List<Channel>.from(channels ?? []);
|
||||
await client
|
||||
.queryChannels(
|
||||
final _channels = await client.queryChannels(
|
||||
filter: filter,
|
||||
sort: sortOptions,
|
||||
options: options,
|
||||
paginationParams: paginationParams,
|
||||
onlyOffline: onlyOffline,
|
||||
)
|
||||
.then((channels) {
|
||||
if (clear) {
|
||||
_channelsController.add(channels);
|
||||
} else {
|
||||
final l = oldChannels + channels;
|
||||
_channelsController.add(l);
|
||||
}
|
||||
_queryChannelsLoadingController.sink.add(false);
|
||||
});
|
||||
);
|
||||
|
||||
if (clear) {
|
||||
_channelsController.add(_channels);
|
||||
} else {
|
||||
final l = oldChannels + _channels;
|
||||
_channelsController.add(l);
|
||||
}
|
||||
_queryChannelsLoadingController.sink.add(false);
|
||||
} catch (err, stackTrace) {
|
||||
print(err);
|
||||
print(stackTrace);
|
||||
@@ -126,7 +132,8 @@ class ChannelsBlocState extends State<ChannelsBloc>
|
||||
final channel = newChannels.removeAt(index);
|
||||
newChannels.insert(0, channel);
|
||||
}
|
||||
} else {
|
||||
} else if (widget.shouldAddChannel != null &&
|
||||
widget.shouldAddChannel(e)) {
|
||||
final hiddenIndex = _hiddenChannels.indexWhere((c) => c.cid == e.cid);
|
||||
if (hiddenIndex > -1) {
|
||||
newChannels.insert(0, _hiddenChannels[hiddenIndex]);
|
||||
@@ -138,6 +145,10 @@ class ChannelsBlocState extends State<ChannelsBloc>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (widget.channelsComparator != null) {
|
||||
newChannels.sort(widget.channelsComparator);
|
||||
}
|
||||
_channelsController.add(newChannels);
|
||||
}));
|
||||
}
|
||||
|
||||
+133
-61
@@ -1208,12 +1208,13 @@ class MessageInputState extends State<MessageInput> {
|
||||
RenderBox renderBox = context.findRenderObject();
|
||||
final size = renderBox.size;
|
||||
|
||||
return OverlayEntry(builder: (context) {
|
||||
return Positioned(
|
||||
bottom: size.height + MediaQuery.of(context).viewInsets.bottom,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: TweenAnimationBuilder<double>(
|
||||
return OverlayEntry(
|
||||
builder: (context) {
|
||||
return Positioned(
|
||||
bottom: size.height + MediaQuery.of(context).viewInsets.bottom,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 0.0, end: 1.0),
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOutExpo,
|
||||
@@ -1229,70 +1230,141 @@ class MessageInputState extends State<MessageInput> {
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Container(
|
||||
constraints: BoxConstraints.loose(Size.fromHeight(400)),
|
||||
constraints: BoxConstraints.loose(Size.fromHeight(240)),
|
||||
decoration: BoxDecoration(
|
||||
color: StreamChatTheme.of(context).colorTheme.white,
|
||||
),
|
||||
child: FutureBuilder<List<Member>>(
|
||||
future: queryMembers ?? Future.value(members),
|
||||
initialData: members,
|
||||
builder: (context, snapshot) {
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(0),
|
||||
shrinkWrap: true,
|
||||
children: snapshot.data
|
||||
.map((m) => ListTile(
|
||||
leading: UserAvatar(
|
||||
constraints: BoxConstraints.tight(
|
||||
Size(
|
||||
40,
|
||||
40,
|
||||
),
|
||||
future: queryMembers ?? Future.value(members),
|
||||
initialData: members,
|
||||
builder: (context, snapshot) {
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(0),
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
...snapshot.data.map(
|
||||
(m) {
|
||||
return Material(
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.white,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
_mentionedUsers.add(m.user);
|
||||
|
||||
splits[splits.length - 1] = m.user.name;
|
||||
final rejoin = splits.join('@');
|
||||
|
||||
textEditingController.value =
|
||||
TextEditingValue(
|
||||
text: rejoin +
|
||||
textEditingController.text
|
||||
.substring(textEditingController
|
||||
.selection.start),
|
||||
selection: TextSelection.collapsed(
|
||||
offset: rejoin.length,
|
||||
),
|
||||
user: m.user,
|
||||
),
|
||||
title: Text(
|
||||
'${m.user.name}',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
subtitle: Text('@${m.userId}'),
|
||||
trailing: StreamSvgIcon.mentions(
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.accentBlue,
|
||||
),
|
||||
onTap: () {
|
||||
_mentionedUsers.add(m.user);
|
||||
|
||||
splits[splits.length - 1] = m.user.name;
|
||||
final rejoin = splits.join('@');
|
||||
|
||||
textEditingController.value =
|
||||
TextEditingValue(
|
||||
text: rejoin +
|
||||
textEditingController.text
|
||||
.substring(
|
||||
textEditingController
|
||||
.selection.start),
|
||||
selection: TextSelection.collapsed(
|
||||
offset: rejoin.length,
|
||||
);
|
||||
_debounce.cancel();
|
||||
_mentionsOverlay?.remove();
|
||||
_mentionsOverlay = null;
|
||||
},
|
||||
child: Container(
|
||||
height: 56.0,
|
||||
child: Row(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 16.0,
|
||||
),
|
||||
);
|
||||
_debounce.cancel();
|
||||
_mentionsOverlay?.remove();
|
||||
_mentionsOverlay = null;
|
||||
},
|
||||
))
|
||||
.toList(),
|
||||
);
|
||||
}),
|
||||
UserAvatar(
|
||||
constraints: BoxConstraints.tight(
|
||||
Size(
|
||||
40,
|
||||
40,
|
||||
),
|
||||
),
|
||||
user: m.user,
|
||||
),
|
||||
SizedBox(
|
||||
width: 8.0,
|
||||
),
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${m.user.name}',
|
||||
maxLines: 1,
|
||||
overflow:
|
||||
TextOverflow.ellipsis,
|
||||
style: StreamChatTheme.of(
|
||||
context)
|
||||
.textTheme
|
||||
.bodyBold,
|
||||
),
|
||||
SizedBox(
|
||||
height: 2.0,
|
||||
),
|
||||
Text(
|
||||
'@${m.userId}',
|
||||
maxLines: 1,
|
||||
overflow:
|
||||
TextOverflow.ellipsis,
|
||||
style: StreamChatTheme.of(
|
||||
context)
|
||||
.textTheme
|
||||
.footnoteBold
|
||||
.copyWith(
|
||||
color: StreamChatTheme
|
||||
.of(context)
|
||||
.colorTheme
|
||||
.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 18.0, left: 8.0),
|
||||
child: StreamSvgIcon.mentions(
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.accentBlue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
OverlayEntry _buildEmojiOverlay() {
|
||||
|
||||
@@ -243,7 +243,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
return StreamBuilder<List<Message>>(
|
||||
stream: messagesStream?.map((messages) => messages
|
||||
?.where((e) =>
|
||||
!e.isDeleted ||
|
||||
(!e.isDeleted && e.shadowed != true) ||
|
||||
(e.isDeleted &&
|
||||
e.user.id == streamChannel.channel.client.state.user.id))
|
||||
?.toList()),
|
||||
|
||||
@@ -572,13 +572,16 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(left: leftPadding),
|
||||
child: Row(
|
||||
child: Flex(
|
||||
direction: Axis.horizontal,
|
||||
clipBehavior: Clip.none,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
if (!deleted && (showThreadReplyIndicator || showInChannel))
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: widget.messageTheme.replies.fontSize / 2),
|
||||
bottom: widget.messageTheme.replies.fontSize / 2,
|
||||
),
|
||||
child: CustomPaint(
|
||||
size: const Size(16, 32),
|
||||
painter: _ThreadReplyPainter(
|
||||
@@ -591,7 +594,12 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
(child) => Transform(
|
||||
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
||||
alignment: Alignment.center,
|
||||
child: Container(height: 16, child: Center(child: child)),
|
||||
child: Container(
|
||||
height: 16,
|
||||
child: Center(
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
].insertBetween(const SizedBox(width: 8.0)),
|
||||
|
||||
@@ -125,6 +125,7 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
|
||||
.on(EventType.messageNew)
|
||||
.where((e) => e.user?.id != user.id)
|
||||
.where((e) => e.message.silent != true)
|
||||
.where((e) => e.message.shadowed != true)
|
||||
.listen((event) async {
|
||||
final channel = client.channel(
|
||||
event.channelType,
|
||||
|
||||
+60
-44
@@ -4,11 +4,16 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// It shows a date divider depending on the date difference
|
||||
class SystemMessage extends StatelessWidget {
|
||||
/// This message
|
||||
final Message message;
|
||||
|
||||
/// The function called when tapping on the message when the message is not failed
|
||||
final void Function(Message) onMessageTap;
|
||||
|
||||
const SystemMessage({
|
||||
Key key,
|
||||
@required this.message,
|
||||
this.onMessageTap,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -44,57 +49,68 @@ class SystemMessage extends StatelessWidget {
|
||||
dayInfo = createdAt.format('dd/MM/yyyy').toUpperCase();
|
||||
}
|
||||
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
divider,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
message.text,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.headline6
|
||||
.color
|
||||
.withOpacity(.5),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (onMessageTap != null) {
|
||||
onMessageTap(message);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
divider,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
message.text,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.headline6
|
||||
.color
|
||||
.withOpacity(.5),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
text: dayInfo,
|
||||
children: [
|
||||
TextSpan(
|
||||
text: dayInfo,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
TextSpan(text: ' AT'),
|
||||
TextSpan(text: ' $hourInfo'),
|
||||
],
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
TextSpan(text: ' AT'),
|
||||
TextSpan(text: ' $hourInfo'),
|
||||
],
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.headline6
|
||||
.color
|
||||
.withOpacity(.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.headline6
|
||||
.color
|
||||
.withOpacity(.5),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
divider,
|
||||
],
|
||||
),
|
||||
divider,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user