Merge branch 'feature/new-ui' into feature/mono-repo

This commit is contained in:
Salvatore Giordano
2021-01-15 14:36:31 +01:00
11 changed files with 352 additions and 338 deletions
@@ -612,7 +612,10 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
body: UsersBloc(
child: Column(
children: [
_buildTextInputSection(modalSetState),
Padding(
padding: const EdgeInsets.all(16),
child: _buildTextInputSection(modalSetState),
),
Expanded(
child: UserListView(
selectedUsers: {},
@@ -690,66 +693,57 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
}
Widget _buildTextInputSection(modalSetState) {
final theme = StreamChatTheme.of(context);
return Column(
children: [
SizedBox(
height: 16.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: 16.0,
),
Expanded(
child: TextField(
controller: _searchController,
cursorColor: StreamChatTheme.of(context).colorTheme.black,
autofocus: true,
decoration: InputDecoration(
isDense: true,
prefixIconConstraints: BoxConstraints.tight(Size(36.0, 44.0)),
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(
vertical: 2.0, horizontal: 6.0),
child: StreamSvgIcon.search(
color: StreamChatTheme.of(context).colorTheme.black,
child: Container(
height: 36,
child: TextField(
controller: _searchController,
cursorColor: theme.colorTheme.black,
autofocus: true,
decoration: InputDecoration(
hintText: 'Search',
hintStyle: theme.textTheme.body.copyWith(
color: theme.colorTheme.grey,
),
prefixIconConstraints: BoxConstraints.tight(Size(40, 24)),
prefixIcon: StreamSvgIcon.search(
color: theme.colorTheme.black,
size: 24,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(24.0),
borderSide: BorderSide(
color: theme.colorTheme.greyWhisper,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(24.0),
borderSide: BorderSide(
color: theme.colorTheme.greyWhisper,
)),
contentPadding: const EdgeInsets.all(0),
),
hintText: 'Search',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(32.0),
borderSide: BorderSide(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(0.08)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(32.0),
borderSide: BorderSide(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(0.08)),
),
contentPadding: EdgeInsets.zero,
),
),
),
SizedBox(
width: 8.0,
),
SizedBox(width: 16.0),
IconButton(
icon: StreamSvgIcon.close_small(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(0.5),
color: theme.colorTheme.grey,
),
onPressed: () {
Navigator.pop(context);
},
constraints: BoxConstraints.tightFor(
height: 24,
width: 24,
),
padding: EdgeInsets.zero,
splashRadius: 24,
onPressed: () => Navigator.pop(context),
)
],
),
+123 -124
View File
@@ -457,133 +457,132 @@ class _ChannelListPageState extends State<ChannelListPage> {
},
child: ChannelsBloc(
child: MessageSearchBloc(
child: Column(
children: [
SearchTextField(
controller: _controller,
showCloseButton: _isSearchActive,
),
Expanded(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 350),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onPanDown: (_) => FocusScope.of(context).unfocus(),
child: _isSearchActive
? MessageSearchListView(
messageQuery: _channelQuery,
filters: {
'members': {
r'$in': [user.id]
}
},
sortOptions: [
SortOption(
'created_at',
direction: SortOption.ASC,
),
],
pullToRefresh: false,
paginationParams: PaginationParams(limit: 20),
emptyBuilder: (_, query) {
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight:
viewportConstraints.maxHeight,
),
child: Center(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(24),
child: StreamSvgIcon.search(
size: 96,
color: Colors.grey,
),
),
Text(
'No results for \"$query\"...',
),
],
),
),
),
);
},
);
},
onItemTap: (messageResponse) async {
final client = StreamChat.of(context).client;
final message = messageResponse.message;
final channel = client.channel(
messageResponse.channel.type,
id: messageResponse.channel.id,
);
if (channel.state == null) {
await channel.watch();
}
Navigator.pushNamed(
context,
Routes.CHANNEL_PAGE,
arguments: ChannelPageArgs(
channel: channel,
initialMessage: message,
),
);
},
)
: ChannelListView(
onStartChatPressed: () {
Navigator.pushNamed(context, Routes.NEW_CHAT);
},
swipeToAction: true,
filter: {
'members': {
r'$in': [user.id],
},
},
options: {
'presence': true,
},
pagination: PaginationParams(
limit: 20,
),
channelWidget: ChannelPage(),
onViewInfoTap: (channel) {
if (channel.memberCount == 2 &&
channel.isDistinct) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: channel,
child: ChatInfoScreen(
user: channel.state.members.first.user,
),
),
),
);
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: channel,
child: GroupInfoScreen(),
),
),
);
}
},
),
),
child: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (_, __) => [
SliverToBoxAdapter(
child: SearchTextField(
controller: _controller,
showCloseButton: _isSearchActive,
),
),
],
body: AnimatedSwitcher(
duration: const Duration(milliseconds: 350),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onPanDown: (_) => FocusScope.of(context).unfocus(),
child: _isSearchActive
? MessageSearchListView(
messageQuery: _channelQuery,
filters: {
'members': {
r'$in': [user.id]
}
},
sortOptions: [
SortOption(
'created_at',
direction: SortOption.ASC,
),
],
pullToRefresh: false,
paginationParams: PaginationParams(limit: 20),
emptyBuilder: (_, query) {
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(24),
child: StreamSvgIcon.search(
size: 96,
color: Colors.grey,
),
),
Text(
'No results for \"$query\"...',
),
],
),
),
),
);
},
);
},
onItemTap: (messageResponse) async {
final client = StreamChat.of(context).client;
final message = messageResponse.message;
final channel = client.channel(
messageResponse.channel.type,
id: messageResponse.channel.id,
);
if (channel.state == null) {
await channel.watch();
}
Navigator.pushNamed(
context,
Routes.CHANNEL_PAGE,
arguments: ChannelPageArgs(
channel: channel,
initialMessage: message,
),
);
},
)
: ChannelListView(
onStartChatPressed: () {
Navigator.pushNamed(context, Routes.NEW_CHAT);
},
swipeToAction: true,
filter: {
'members': {
r'$in': [user.id],
},
},
options: {
'presence': true,
},
pagination: PaginationParams(
limit: 20,
),
channelWidget: ChannelPage(),
onViewInfoTap: (channel) {
if (channel.memberCount == 2 && channel.isDistinct) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: channel,
child: ChatInfoScreen(
user: channel.state.members.first.user,
),
),
),
);
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: channel,
child: GroupInfoScreen(),
),
),
);
}
},
),
),
),
),
),
),
@@ -1,6 +1,6 @@
name: example
description: A new Flutter project.
version: 1.1.1+3
version: 1.1.2
environment:
sdk: ">=2.2.2 <3.0.0"
@@ -729,17 +729,11 @@ class _ChannelListViewState extends State<ChannelListView>
}
Widget _separatorBuilder(context, i) {
var effect = StreamChatTheme.of(context).colorTheme.borderTop;
var effect = StreamChatTheme.of(context).colorTheme.borderBottom;
return BackdropFilter(
filter: ui.ImageFilter.blur(
sigmaX: effect.sigmaX,
sigmaY: effect.sigmaY,
),
child: Container(
height: 1,
color: effect.color.withOpacity(0.08),
),
return Container(
height: 1,
color: effect.color.withOpacity(effect.alpha ?? 1.0),
);
}
@@ -15,13 +15,7 @@ extension StringExtension on String {
final characters = this.trim().characters;
if (characters.isEmpty) return false;
if (characters.length > 3) return false;
return characters.every((c) {
return _emojis.firstWhere(
(Emoji emoji) => emoji.char.contains(c),
orElse: () => null,
) !=
null;
});
return characters.every((c) => _emojis.map((e) => e.char).contains(c));
}
}
+122 -101
View File
@@ -174,117 +174,138 @@ class _ImageFooterState extends State<ImageFooter> {
showModalBottomSheet(
context: context,
barrierColor: StreamChatTheme.of(context).colorTheme.overlay,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: const BorderRadius.only(
topLeft: const Radius.circular(16.0),
topRight: const Radius.circular(16.0),
),
),
builder: (context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.white,
borderRadius: BorderRadius.only(
topRight: Radius.circular(16.0),
topLeft: Radius.circular(16.0),
)),
child: Stack(
final crossAxisCount = 3;
final noOfRowToShowInitially =
widget.mediaAttachments.length > crossAxisCount ? 2 : 1;
final size = MediaQuery.of(context).size;
final initialChildSize =
48 + (size.width * noOfRowToShowInitially) / crossAxisCount;
return DraggableScrollableSheet(
expand: false,
initialChildSize: initialChildSize / size.height,
builder: (context, scrollController) {
return SingleChildScrollView(
controller: scrollController,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Photos',
style:
StreamChatTheme.of(context).textTheme.headlineBold,
Stack(
children: [
Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Photos',
style: StreamChatTheme.of(context)
.textTheme
.headlineBold,
),
),
),
),
Align(
alignment: Alignment.centerRight,
child: IconButton(
icon: StreamSvgIcon.close(
color: StreamChatTheme.of(context).colorTheme.black,
),
onPressed: () => Navigator.maybePop(context),
),
),
],
),
Align(
alignment: Alignment.centerRight,
child: IconButton(
icon: StreamSvgIcon.close(
color: StreamChatTheme.of(context).colorTheme.black,
Flexible(
child: GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: widget.mediaAttachments.length,
padding: const EdgeInsets.all(1),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
mainAxisSpacing: 2.0,
crossAxisSpacing: 2.0,
),
onPressed: () => Navigator.maybePop(context),
itemBuilder: (context, index) {
Widget media;
final attachment = widget.mediaAttachments[index];
if (attachment.type == 'video') {
var controllerPackage = widget.videoPackages[
videoAttachments.indexOf(attachment)];
media = InkWell(
onTap: () => widget.mediaSelectedCallBack(index),
child: FittedBox(
fit: BoxFit.cover,
child: Chewie(
controller: controllerPackage.chewieController,
),
),
);
} else {
media = InkWell(
onTap: () => widget.mediaSelectedCallBack(index),
child: AspectRatio(
child: CachedNetworkImage(
imageUrl: attachment.imageUrl ??
attachment.assetUrl ??
attachment.thumbUrl,
fit: BoxFit.cover,
),
aspectRatio: 1.0,
),
);
}
return Stack(
children: [
media,
Padding(
padding: EdgeInsets.all(8.0),
child: Container(
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: StreamChatTheme.of(context)
.colorTheme
.white
.withOpacity(0.6),
boxShadow: [
BoxShadow(
blurRadius: 8.0,
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(0.3),
),
],
),
padding: const EdgeInsets.all(1),
child: UserAvatar(
user: widget.message.user,
constraints:
BoxConstraints.tight(Size(24, 24)),
showOnlineStatus: false,
),
),
),
],
);
},
),
),
],
),
),
Container(
color: StreamChatTheme.of(context).colorTheme.white,
child: GridView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, position) {
Widget media;
if (widget.mediaAttachments[position].type == 'video') {
var controllerPackage = widget.videoPackages[
videoAttachments
.indexOf(widget.mediaAttachments[position])];
media = InkWell(
onTap: () {
widget.mediaSelectedCallBack(position);
},
child: FittedBox(
fit: BoxFit.cover,
child: Chewie(
controller: controllerPackage.chewieController,
),
),
);
} else {
media = InkWell(
onTap: () {
widget.mediaSelectedCallBack(position);
},
child: Padding(
padding: const EdgeInsets.all(1.0),
child: AspectRatio(
child: CachedNetworkImage(
imageUrl: widget
.mediaAttachments[position].imageUrl ??
widget.mediaAttachments[position].assetUrl ??
widget.mediaAttachments[position].thumbUrl,
fit: BoxFit.cover,
),
aspectRatio: 1.0,
),
),
);
}
return Stack(
children: [
media,
Padding(
padding: EdgeInsets.all(8.0),
child: Align(
alignment: Alignment.topLeft,
child: UserAvatar(
user: widget.message.user,
constraints: BoxConstraints.tight(
Size(
24,
24,
),
),
showOnlineStatus: false,
),
),
),
],
);
},
itemCount: widget.mediaAttachments.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3),
),
),
],
);
},
);
},
);
@@ -32,6 +32,7 @@ class MessageActionsModal extends StatefulWidget {
final bool showFlagButton;
final bool reverse;
final ShapeBorder messageShape;
final ShapeBorder attachmentShape;
final DisplayWidget showUserAvatar;
const MessageActionsModal({
@@ -51,6 +52,7 @@ class MessageActionsModal extends StatefulWidget {
this.showUserAvatar = DisplayWidget.show,
this.editMessageInputBuilder,
this.messageShape,
this.attachmentShape,
this.reverse = false,
}) : super(key: key);
@@ -173,6 +175,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
showInChannelIndicator: false,
showSendingIndicator: false,
shape: widget.messageShape,
attachmentShape: widget.attachmentShape,
),
),
SizedBox(height: 8),
@@ -461,7 +464,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
),
const SizedBox(width: 16),
Text(
'Flag',
'Flag Message',
style: StreamChatTheme.of(context).textTheme.body,
),
],
@@ -20,6 +20,7 @@ class MessageReactionsModal extends StatelessWidget {
final bool showReactions;
final DisplayWidget showUserAvatar;
final ShapeBorder messageShape;
final ShapeBorder attachmentShape;
final void Function(User) onUserAvatarTap;
const MessageReactionsModal({
@@ -30,6 +31,7 @@ class MessageReactionsModal extends StatelessWidget {
this.onThreadTap,
this.editMessageInputBuilder,
this.messageShape,
this.attachmentShape,
this.reverse = false,
this.showUserAvatar = DisplayWidget.show,
this.onUserAvatarTap,
@@ -127,6 +129,7 @@ class MessageReactionsModal extends StatelessWidget {
translateUserAvatar: false,
showSendingIndicator: false,
shape: messageShape,
attachmentShape: attachmentShape,
padding: const EdgeInsets.all(0),
attachmentPadding: EdgeInsets.all(
hasFileAttachment ? 4 : 2,
@@ -295,10 +295,6 @@ class _MessageWidgetState extends State<MessageWidget> {
var leftPadding =
widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5;
final hasFiles =
widget.message.attachments?.any((element) => element.type == 'file') ==
true;
return Material(
type: MaterialType.transparency,
child: Portal(
@@ -406,29 +402,19 @@ class _MessageWidgetState extends State<MessageWidget> {
BorderRadius.zero,
),
color: _getBackgroundColor(),
child: Padding(
padding: EdgeInsets.all(
hasFiles ? 2.0 : 0.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment
.end,
mainAxisSize:
MainAxisSize.min,
children: <Widget>[
if (hasQuotedMessage)
_buildQuotedMessage(),
if (hasNonUrlAttachments)
_parseAttachments(
context),
if (widget.message.text
.trim()
.isNotEmpty &&
!isGiphy)
_buildTextBubble(
context),
],
),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.end,
mainAxisSize:
MainAxisSize.min,
children: <Widget>[
if (hasQuotedMessage)
_buildQuotedMessage(),
if (hasNonUrlAttachments)
_parseAttachments(),
if (!isGiphy)
_buildTextBubble(),
],
),
),
),
@@ -536,7 +522,7 @@ class _MessageWidgetState extends State<MessageWidget> {
var children = <Widget>[];
final threadParticipants = widget.message.threadParticipants;
final threadParticipants = widget.message?.threadParticipants?.take(2);
final showThreadParticipants = threadParticipants?.isNotEmpty == true;
final replyCount = widget.message.replyCount;
@@ -565,7 +551,7 @@ class _MessageWidgetState extends State<MessageWidget> {
if (showThreadParticipants)
SizedBox.fromSize(
size: Size((threadParticipants.length * 8.0) + 8, 16),
child: _buildThreadParticipantsIndicator(),
child: _buildThreadParticipantsIndicator(threadParticipants),
),
InkWell(
onTap: widget.onThreadTap != null ? onThreadTap : null,
@@ -642,10 +628,10 @@ class _MessageWidgetState extends State<MessageWidget> {
);
}
Widget _buildThreadParticipantsIndicator() {
Widget _buildThreadParticipantsIndicator(Iterable<User> threadParticipants) {
var padding = 0.0;
return Stack(
children: widget.message.threadParticipants.map((user) {
children: threadParticipants.map((user) {
padding += 8.0;
return Positioned(
right: padding - 8,
@@ -718,6 +704,8 @@ class _MessageWidgetState extends State<MessageWidget> {
: DisplayWidget.show,
messageTheme: widget.messageTheme,
messageShape: widget.shape ?? _getDefaultShape(context),
attachmentShape:
widget.attachmentShape ?? _getDefaultAttachmentShape(context),
reverse: widget.reverse,
showDeleteMessage: widget.showDeleteMessage || isDeleteFailed,
message: widget.message,
@@ -763,6 +751,8 @@ class _MessageWidgetState extends State<MessageWidget> {
onUserAvatarTap: widget.onUserAvatarTap,
messageTheme: widget.messageTheme,
messageShape: widget.shape ?? _getDefaultShape(context),
attachmentShape:
widget.attachmentShape ?? _getDefaultAttachmentShape(context),
reverse: widget.reverse,
message: widget.message,
editMessageInputBuilder: widget.editMessageInputBuilder,
@@ -773,20 +763,31 @@ class _MessageWidgetState extends State<MessageWidget> {
});
}
ShapeBorder _getDefaultShape(BuildContext context) {
ShapeBorder _getDefaultAttachmentShape(BuildContext context) {
final hasFiles =
widget.message.attachments?.any((it) => it.type == 'file') == true;
return RoundedRectangleBorder(
side: widget.attachmentBorderSide ??
widget.borderSide ??
BorderSide(
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
),
borderRadius: widget.attachmentBorderRadiusGeometry ??
widget.borderRadiusGeometry ??
BorderRadius.zero,
side: hasFiles
? widget.attachmentBorderSide ??
BorderSide(
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
)
: BorderSide.none,
borderRadius: widget.attachmentBorderRadiusGeometry ?? BorderRadius.zero,
);
}
Widget _parseAttachments(BuildContext context) {
ShapeBorder _getDefaultShape(BuildContext context) {
return RoundedRectangleBorder(
side: widget.borderSide ??
BorderSide(
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
),
borderRadius: widget.borderRadiusGeometry ?? BorderRadius.zero,
);
}
Widget _parseAttachments() {
final images = widget.message.attachments
?.where((element) =>
element.type == 'image' && element.ogScrapeUrl == null)
@@ -849,7 +850,7 @@ class _MessageWidgetState extends State<MessageWidget> {
Attachment attachment,
}) {
final attachmentShape =
widget.attachmentShape ?? widget.shape ?? _getDefaultShape(context);
widget.attachmentShape ?? _getDefaultAttachmentShape(context);
return Material(
clipBehavior: Clip.antiAlias,
shape: attachmentShape,
@@ -920,7 +921,8 @@ class _MessageWidgetState extends State<MessageWidget> {
),
);
Widget _buildTextBubble(BuildContext context) {
Widget _buildTextBubble() {
if (widget.message.text.trim().isEmpty) return Offstage();
return Transform(
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
alignment: Alignment.center,
@@ -492,8 +492,8 @@ class ColorTheme {
),
this.borderTop = const Effect(
sigmaX: 0, sigmaY: -1, color: Color(0xff000000), blur: 0.0),
this.borderBottom =
const Effect(sigmaX: 0, sigmaY: 1, color: Color(0xff000000), blur: 0.0),
this.borderBottom = const Effect(
sigmaX: 0, sigmaY: 1, color: Color(0xff000000), blur: 0.0, alpha: 0.08),
this.shadowIconButton = const Effect(
sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4.0),
this.modalShadow = const Effect(
@@ -514,8 +514,8 @@ class ColorTheme {
this.accentGreen = const Color(0xff20E070),
this.borderTop = const Effect(
sigmaX: 0, sigmaY: -1, color: Color(0xff141924), blur: 0.0),
this.borderBottom =
const Effect(sigmaX: 0, sigmaY: 1, color: Color(0xff141924), blur: 0.0),
this.borderBottom = const Effect(
sigmaX: 0, sigmaY: 1, color: Color(0xff141924), blur: 0.0, alpha: 1.0),
this.shadowIconButton = const Effect(
sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4.0),
this.modalShadow = const Effect(
@@ -416,6 +416,10 @@ class _UserListViewState extends State<UserListView>
height: 64,
width: 64,
),
onlineIndicatorConstraints: BoxConstraints.tightFor(
height: 12,
width: 12,
),
onTap: (user) =>
widget.onUserTap(user, widget.userWidget),
onLongPress: widget.onUserLongPress,