Merge branch 'feature/new-ui' into hotfix/commands

This commit is contained in:
Salvatore Giordano
2021-01-07 17:06:16 +01:00
committed by GitHub
13 changed files with 326 additions and 131 deletions
+1 -1
View File
@@ -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';
+2 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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() {
+1 -1
View File
@@ -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()),
+11 -3
View File
@@ -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)),
+1
View File
@@ -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
View File
@@ -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,
],
),
);
}
}