Merge remote-tracking branch 'origin/develop' into feat/localization

# Conflicts:
#	packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart
#	packages/stream_chat_flutter/lib/src/channel_name.dart
#	packages/stream_chat_flutter/lib/src/message_search_item.dart
#	packages/stream_chat_flutter/lib/src/message_widget.dart
This commit is contained in:
xsahil03x
2021-07-28 15:18:09 +05:30
67 changed files with 723 additions and 252 deletions
@@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart';
import 'package:stream_chat_flutter/src/visible_footnote.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
@@ -217,36 +218,11 @@ class GiphyAttachment extends AttachmentWidget {
),
),
const SizedBox(height: 4),
Align(
const Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
StreamSvgIcon.eye(
color: StreamChatTheme.of(context)
.colorTheme
.textHighEmphasis
.withOpacity(0.5),
size: 16,
),
const SizedBox(
width: 8,
),
Text(
context.translations.onlyVisibleToYouText,
style: StreamChatTheme.of(context)
.textTheme
.footnote
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.textHighEmphasis
.withOpacity(0.5)),
),
],
),
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: VisibleFootnote(),
),
),
],
@@ -139,7 +139,8 @@ class AttachmentActionsModal extends StatelessWidget {
);
},
),
if (StreamChat.of(context).user?.id == message.user?.id)
if (StreamChat.of(context).currentUser?.id ==
message.user?.id)
_buildButton(
context,
context.translations.deleteLabel.capitalize(),
@@ -141,7 +141,7 @@ class ChannelAvatar extends StatelessWidget {
return child;
}
final currentUser = streamChat.user!;
final currentUser = streamChat.currentUser!;
final otherMembers = channel.state!.members
.where((it) => it.userId != currentUser.id)
.toList(growable: false);
@@ -149,7 +149,7 @@ class ChannelAvatar extends StatelessWidget {
// our own space, no other members
if (otherMembers.isEmpty) {
return BetterStreamBuilder<User>(
stream: streamChat.client.state.userStream.map((it) => it!),
stream: streamChat.client.state.currentUserStream.map((it) => it!),
initialData: currentUser,
builder: (context, user) => UserAvatar(
borderRadius: borderRadius ?? previewTheme?.borderRadius,
@@ -28,8 +28,8 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
final members = channel.state?.members ?? [];
final userAsMember =
members.firstWhere((e) => e.user?.id == _streamChatState.user?.id);
final userAsMember = members
.firstWhere((e) => e.user?.id == _streamChatState.currentUser?.id);
final isOwner = userAsMember.role == 'owner';
return Material(
@@ -248,7 +248,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
);
if (res == true) {
final channel = _streamChannelState.channel;
final user = _streamChatState.user;
final user = _streamChatState.currentUser;
if (user != null) {
await channel.removeMembers([user.id]);
}
@@ -71,7 +71,7 @@ class ChannelInfo extends StatelessWidget {
.subtitle,
);
} else {
final userId = StreamChat.of(context).user?.id;
final userId = StreamChat.of(context).currentUser?.id;
final otherMember = members?.firstWhereOrNull(
(element) => element.userId != userId,
);
@@ -96,7 +96,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
@override
Widget build(BuildContext context) {
final _client = client ?? StreamChat.of(context).client;
final user = _client.state.user;
final user = _client.state.currentUser;
return ConnectionStatusBuilder(
statusBuilder: (context, status) {
var statusString = '';
@@ -553,7 +553,7 @@ class _ChannelListViewState extends State<ChannelListView> {
'owner',
].contains(channel.state!.members
.firstWhereOrNull(
(m) => m.userId == channel.client.state.user?.id)
(m) => m.userId == channel.client.state.currentUser?.id)
?.role))
IconSlideAction(
color: backgroundColor,
@@ -49,8 +49,8 @@ class ChannelName extends StatelessWidget {
if (extraData['name'] != null) {
title = extraData['name'];
} else {
final otherMembers =
members?.where((member) => member.userId != client.user!.id);
final otherMembers = members
?.where((member) => member.userId != client.currentUser!.id);
if (otherMembers?.length == 1) {
if (otherMembers!.first.user != null) {
title = otherMembers.first.user!.name;
@@ -102,7 +102,7 @@ class ChannelPreview extends StatelessWidget {
if (members?.isEmpty == true ||
members?.any((Member e) =>
e.user!.id ==
channel.client.state.user?.id) !=
channel.client.state.currentUser?.id) !=
true) {
return const SizedBox();
}
@@ -125,7 +125,7 @@ class ChannelPreview extends StatelessWidget {
(m) => !m.isDeleted && m.shadowed != true,
);
if (lastMessage?.user?.id ==
streamChatState.user?.id) {
streamChatState.currentUser?.id) {
return Padding(
padding: const EdgeInsets.only(right: 4),
child: SendingIndicator(
@@ -134,7 +134,8 @@ class ChannelPreview extends StatelessWidget {
isMessageRead: channel.state!.read
?.where((element) =>
element.user.id !=
channel.client.state.user!.id)
channel
.client.state.currentUser!.id)
.where((element) => element.lastRead
.isAfter(lastMessage.createdAt))
.isNotEmpty ==
@@ -0,0 +1,258 @@
import 'dart:math';
import 'dart:ui';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
/// Fallback user avatar with a polygon gradient overlayed with text
class GradientAvatar extends StatefulWidget {
/// Constructor for [GradientAvatar]
const GradientAvatar({
Key? key,
required this.name,
required this.userId,
}) : super(key: key);
/// Name of user to shorten and display
final String name;
/// ID of user to be used for key
final String userId;
@override
_GradientAvatarState createState() => _GradientAvatarState();
}
class _GradientAvatarState extends State<GradientAvatar> {
@override
Widget build(BuildContext context) => Center(
child: RepaintBoundary(
child: CustomPaint(
painter: DemoPainter(
widget.userId,
getShortenedName(widget.name),
DefaultTextStyle.of(context).style.fontFamily ?? 'Roboto',
),
child: const SizedBox.expand(),
),
),
);
String getShortenedName(String name) {
var parts = name.split(' ')..removeWhere((e) => e == '');
if (parts.length > 2) {
parts = parts.take(2).toList();
}
var result = '';
for (var i = 0; i < parts.length; i++) {
result = result + parts[i][0].toUpperCase();
}
return result;
}
}
/// Painter for bg polygon gradient
class DemoPainter extends CustomPainter {
/// Constructor for [DemoPainter]
DemoPainter(
this.userId,
this.username,
this.fontFamily,
);
/// Init grid row count
static const int rowCount = 5;
/// Init grid column count
static const int columnCount = 5;
/// User ID used for key
String userId;
/// User name to display
String username;
/// Font family to use
String fontFamily;
@override
void paint(Canvas canvas, Size size) {
final rowUnit = size.width / columnCount;
final columnUnit = size.height / rowCount;
final rand = Random(userId.length);
final squares = <Offset4>[];
final points = <Offset>{};
final gradient = colorGradients[rand.nextInt(colorGradients.length)];
for (var i = 0; i < rowCount; i++) {
for (var j = 0; j < columnCount; j++) {
final off1 = Offset(rowUnit * j, columnUnit * i);
final off2 = Offset(rowUnit * (j + 1), columnUnit * i);
final off3 = Offset(rowUnit * (j + 1), columnUnit * (i + 1));
final off4 = Offset(rowUnit * j, columnUnit * (i + 1));
points.addAll([off1, off2, off3, off4]);
final pointsList = points.toList();
final p1 = pointsList.indexOf(off1);
final p2 = pointsList.indexOf(off2);
final p3 = pointsList.indexOf(off3);
final p4 = pointsList.indexOf(off4);
squares.add(
Offset4(p1, p2, p3, p4, i, j, rowCount, columnCount, gradient));
}
}
final list = transformPoints(points, size);
squares.forEach((e) => e.draw(canvas, list));
final smallerSide = size.width > size.height ? size.width : size.height;
final textSize = smallerSide / 3;
final dxShift = (username.length == 2 ? 1.45 : 0.9) * textSize / 2;
final dyShift = (username.length == 2 ? 1.0 : 1.65) * textSize / 2;
final fontSize = username.length == 2 ? textSize : textSize * 1.5;
TextPainter(
text: TextSpan(
text: username,
style: TextStyle(
fontFamily: fontFamily,
fontSize: fontSize,
fontWeight: FontWeight.w500,
color: Colors.white.withOpacity(0.7),
),
),
textAlign: TextAlign.center,
textDirection: TextDirection.ltr)
..layout(maxWidth: size.width)
..paint(
canvas,
Offset(
(size.width / 2) - dxShift,
(size.height / 2) - dyShift,
),
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
/// Transforms initial grid into a polygon grid
List<Offset> transformPoints(Set<Offset> points, Size size) {
final transformedList = <Offset>[];
final orgList = points.toList();
final rand = Random(userId.length);
for (var i = 0; i < points.length; i++) {
final orgDx = orgList[i].dx;
final orgDy = orgList[i].dy;
if (orgDx == 0 ||
orgDy == 0 ||
orgDx == size.width ||
orgDy == size.height) {
transformedList.add(Offset(orgDx, orgDy));
continue;
}
final sign1 = rand.nextInt(2) == 1 ? 1 : -1;
final sign2 = rand.nextInt(2) == 1 ? 1 : -1;
final dx = 0.6 * sign1 * rand.nextInt(size.width ~/ columnCount);
final dy = 0.6 * sign2 * rand.nextInt(size.height ~/ rowCount);
transformedList.add(Offset(orgDx + dx, orgDy + dy));
}
return transformedList;
}
}
/// Class for storing and drawing four points of a polygon
class Offset4 {
/// Constructor for [Offset4]
Offset4(
this.p1,
this.p2,
this.p3,
this.p4,
this.row,
this.column,
this.rowSize,
this.colSize,
this.gradient,
);
/// Point 1
int p1;
/// Point 2
int p2;
/// Point 3
int p3;
/// Point 4
int p4;
/// Position of polygon on grid
int row;
/// Position of polygon on grid
int column;
/// Max row size
int rowSize;
/// Max col size
int colSize;
/// Gradient to be applied to polygon
List<Color> gradient;
/// Draw the polygon on canvas
void draw(Canvas canvas, List<Offset> points) {
final paint = Paint()
..color = Color.fromARGB(255, Random().nextInt(255),
Random().nextInt(255), Random().nextInt(255))
..shader = ui.Gradient.linear(
points[p1],
points[p3],
gradient,
);
final backgroundPath = Path()
..moveTo(points[p1].dx, points[p1].dy)
..lineTo(points[p2].dx, points[p2].dy)
..lineTo(points[p3].dx, points[p3].dy)
..lineTo(points[p4].dx, points[p4].dy)
..lineTo(points[p1].dx, points[p1].dy)
..close();
canvas.drawPath(backgroundPath, paint);
}
}
/// Gradient list for polygons
const colorGradients = [
[Color(0xffffafbd), Color(0xffffc3a0)],
[Color(0xff2193b0), Color(0xff6dd5ed)],
[Color(0xffcc2b5e), Color(0xff753a88)],
[Color(0xffee9ca7), Color(0xffffdde1)],
[Color(0xff42275a), Color(0xff734b6d)],
[Color(0xffde6262), Color(0xffffb88c)],
[Color(0xff56ab2f), Color(0xffa8e063)],
[Color(0xff614385), Color(0xff516395)],
[Color(0xffeacda3), Color(0xffd6ae7b)],
[Color(0xff02aab0), Color(0xff00cdac)],
];
@@ -101,7 +101,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
Widget _showMessageOptionsModal() {
final mediaQueryData = MediaQuery.of(context);
final size = mediaQueryData.size;
final user = StreamChat.of(context).user;
final user = StreamChat.of(context).currentUser;
final roughMaxSize = 2 * size.width / 3;
var messageTextLength = widget.message.text!.length;
@@ -704,7 +704,8 @@ class _MessageListViewState extends State<MessageListView> {
final unreadCount = snapshot.data!.item2;
final showUnreadCount = unreadCount > 0 &&
streamChannel!.channel.state!.members.any((e) =>
e.userId == streamChannel!.channel.client.state.user!.id);
e.userId ==
streamChannel!.channel.client.state.currentUser!.id);
return Positioned(
bottom: 8,
right: 8,
@@ -810,9 +811,10 @@ class _MessageListViewState extends State<MessageListView> {
Widget buildParentMessage(
Message message,
) {
final isMyMessage = message.user!.id == StreamChat.of(context).user!.id;
final isMyMessage =
message.user!.id == StreamChat.of(context).currentUser!.id;
final isOnlyEmoji = message.text!.isOnlyEmoji;
final currentUser = StreamChat.of(context).user;
final currentUser = StreamChat.of(context).currentUser;
final members = StreamChannel.of(context).channel.state?.members ?? [];
final currentUserMember =
members.firstWhereOrNull((e) => e.user!.id == currentUser!.id);
@@ -897,7 +899,7 @@ class _MessageListViewState extends State<MessageListView> {
);
}
final userId = StreamChat.of(context).user!.id;
final userId = StreamChat.of(context).currentUser!.id;
final isMyMessage = message.user!.id == userId;
final nextMessage = index - 1 >= 0 ? messages[index - 1] : null;
final isNextUserSame =
@@ -960,7 +962,7 @@ class _MessageListViewState extends State<MessageListView> {
? BorderSide.none
: null;
final currentUser = StreamChat.of(context).user;
final currentUser = StreamChat.of(context).currentUser;
final members = StreamChannel.of(context).channel.state?.members ?? [];
final currentUserMember =
members.firstWhere((e) => e.user!.id == currentUser!.id);
@@ -1166,7 +1168,7 @@ class _MessageListViewState extends State<MessageListView> {
_topPaginationActive = false;
}
if (event.message!.user!.id ==
streamChannel!.channel.client.state.user!.id) {
streamChannel!.channel.client.state.currentUser!.id) {
WidgetsBinding.instance!.addPostFrameCallback((_) {
_scrollController?.jumpTo(
index: 0,
@@ -43,7 +43,7 @@ class MessageReactionsModal extends StatelessWidget {
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final user = StreamChat.of(context).user;
final user = StreamChat.of(context).currentUser;
final roughMaxSize = 2 * size.width / 3;
var messageTextLength = message.text!.length;
@@ -50,7 +50,7 @@ class MessageSearchItem extends StatelessWidget {
title: Row(
children: [
Text(
user.id == StreamChat.of(context).user?.id
user.id == StreamChat.of(context).currentUser?.id
? context.translations.youText
: user.name,
style: chatThemeData.channelPreviewTheme.title,
@@ -92,6 +92,8 @@ class MessageWidget extends StatefulWidget {
this.userAvatarBuilder,
this.editMessageInputBuilder,
this.textBuilder,
this.bottomRowBuilder,
this.deletedBottomRowBuilder,
this.onReturnAction,
Map<String, AttachmentBuilder>? customAttachmentBuilders,
this.readList,
@@ -275,6 +277,12 @@ class MessageWidget extends StatefulWidget {
/// Function called on long press
final void Function(BuildContext, Message)? onMessageActions;
/// Widget builder for building a bottom row below the message
final Widget Function(BuildContext, Message)? bottomRowBuilder;
/// Widget builder for building a bottom row below a deleted message
final Widget Function(BuildContext, Message)? deletedBottomRowBuilder;
/// Widget builder for building user avatar
final Widget Function(BuildContext, User)? userAvatarBuilder;
@@ -410,6 +418,8 @@ class MessageWidget extends StatefulWidget {
Widget Function(BuildContext, Message)? editMessageInputBuilder,
Widget Function(BuildContext, Message)? textBuilder,
Widget Function(BuildContext, Message)? usernameBuilder,
Widget Function(BuildContext, Message)? bottomRowBuilder,
Widget Function(BuildContext, Message)? deletedBottomRowBuilder,
void Function(BuildContext, Message)? onMessageActions,
Message? message,
MessageTheme? messageTheme,
@@ -463,6 +473,9 @@ class MessageWidget extends StatefulWidget {
editMessageInputBuilder ?? this.editMessageInputBuilder,
textBuilder: textBuilder ?? this.textBuilder,
usernameBuilder: usernameBuilder ?? this.usernameBuilder,
bottomRowBuilder: bottomRowBuilder ?? this.bottomRowBuilder,
deletedBottomRowBuilder:
deletedBottomRowBuilder ?? this.deletedBottomRowBuilder,
onMessageActions: onMessageActions ?? this.onMessageActions,
message: message ?? this.message,
messageTheme: messageTheme ?? this.messageTheme,
@@ -782,7 +795,11 @@ class _MessageWidgetState extends State<MessageWidget>
bottom:
isPinned && widget.showPinHighlight ? 6.0 : 0.0,
),
child: _bottomRow,
child: widget.bottomRowBuilder?.call(
context,
widget.message,
) ??
_bottomRow,
),
if (isFailedState)
Positioned(
@@ -810,7 +827,7 @@ class _MessageWidgetState extends State<MessageWidget>
}
Widget _buildQuotedMessage() {
final isMyMessage = widget.message.user?.id == _streamChat.user?.id;
final isMyMessage = widget.message.user?.id == _streamChat.currentUser?.id;
final onTap = widget.message.quotedMessage?.isDeleted != true &&
widget.onQuotedMessageTap != null
? () => widget.onQuotedMessageTap!(widget.message.quotedMessageId)
@@ -830,22 +847,11 @@ class _MessageWidgetState extends State<MessageWidget>
Widget get _bottomRow {
if (isDeleted) {
final chatThemeData = _streamChatTheme;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
StreamSvgIcon.eye(
color: chatThemeData.colorTheme.textLowEmphasis,
size: 16,
),
const SizedBox(width: 8),
Text(
context.translations.onlyVisibleToYouText,
style: chatThemeData.textTheme.footnote
.copyWith(color: chatThemeData.colorTheme.textLowEmphasis),
),
],
);
return widget.deletedBottomRowBuilder?.call(
context,
widget.message,
) ??
const Offstage();
}
final children = <Widget>[];
@@ -993,7 +999,7 @@ class _MessageWidgetState extends State<MessageWidget>
Widget _buildReactionIndicator(
BuildContext context,
) {
final ownId = _streamChat.user!.id;
final ownId = _streamChat.currentUser!.id;
final reactionsMap = <String, Reaction>{};
widget.message.latestReactions?.forEach((element) {
if (!reactionsMap.containsKey(element.type) ||
@@ -1054,10 +1060,10 @@ class _MessageWidgetState extends State<MessageWidget>
showReactionPickerIndicator: widget.showReactions &&
(widget.message.status == MessageSendingStatus.sent),
showPinHighlight: false,
showUserAvatar:
widget.message.user!.id == channel.client.state.user!.id
? DisplayWidget.gone
: DisplayWidget.show,
showUserAvatar: widget.message.user!.id ==
channel.client.state.currentUser!.id
? DisplayWidget.gone
: DisplayWidget.show,
),
onCopyTap: (message) =>
Clipboard.setData(ClipboardData(text: message.text)),
@@ -1118,7 +1124,7 @@ class _MessageWidgetState extends State<MessageWidget>
(widget.message.status == MessageSendingStatus.sent),
showPinHighlight: false,
showUserAvatar:
widget.message.user!.id == channel.client.state.user!.id
widget.message.user!.id == channel.client.state.currentUser!.id
? DisplayWidget.gone
: DisplayWidget.show,
),
@@ -1279,7 +1285,7 @@ class _MessageWidgetState extends State<MessageWidget>
Widget _buildPinnedMessage(Message message) {
final pinnedBy = message.pinnedBy!;
final currentUser = _streamChat.user!;
final currentUser = _streamChat.currentUser!;
return Padding(
padding: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 8),
@@ -123,7 +123,7 @@ class ReactionBubble extends StatelessWidget {
);
final chatThemeData = StreamChatTheme.of(context);
final userId = StreamChat.of(context).user?.id;
final userId = StreamChat.of(context).currentUser?.id;
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 4,
@@ -126,11 +126,25 @@ class StreamChatState extends State<StreamChat> {
return defaultTheme.merge(themeData);
}
// coverage:ignore-start
/// The current user
User? get user => widget.client.state.user;
@Deprecated('Use `.currentUser` instead, Will be removed in future releases')
User? get user => widget.client.state.currentUser;
/// The current user as a stream
Stream<User?> get userStream => widget.client.state.userStream;
@Deprecated(
'Use `.currentUserStream` instead, Will be removed in future releases',
)
Stream<User?> get userStream => widget.client.state.currentUserStream;
// coverage:ignore-end
/// The current user
User? get currentUser => widget.client.state.currentUser;
/// The current user as a stream
Stream<User?> get currentUserStream => widget.client.state.currentUserStream;
@override
void didChangeDependencies() {
@@ -1,12 +1,11 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/channel_header.dart';
import 'package:stream_chat_flutter/src/channel_preview.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/gradient_avatar.dart';
import 'package:stream_chat_flutter/src/message_input.dart';
import 'package:stream_chat_flutter/src/reaction_icon.dart';
import 'package:stream_chat_flutter/src/utils.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
@@ -310,10 +309,9 @@ class StreamChatThemeData {
colorTheme: colorTheme,
primaryIconTheme: iconTheme,
defaultUserImage: (context, user) => Center(
child: CachedNetworkImage(
filterQuality: FilterQuality.high,
imageUrl: getRandomPicUrl(user),
fit: BoxFit.cover,
child: GradientAvatar(
name: user.name,
userId: user.id,
),
),
channelPreviewTheme: channelPreviewTheme,
@@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Widget for displaying a footnote
class VisibleFootnote extends StatelessWidget {
/// Constructor for creating a [VisibleFootnote]
const VisibleFootnote({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return Row(
mainAxisSize: MainAxisSize.min,
children: [
StreamSvgIcon.eye(
color: chatThemeData.colorTheme.textLowEmphasis,
size: 16,
),
const SizedBox(width: 8),
Text(
'Only visible to you',
style: chatThemeData.textTheme.footnote
.copyWith(color: chatThemeData.colorTheme.textLowEmphasis),
),
],
);
}
}