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

# Conflicts:
#	packages/stream_chat_flutter/example/lib/main.dart
#	packages/stream_chat_flutter/example/pubspec.yaml
#	packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart
#	packages/stream_chat_flutter/lib/src/channel_list_view.dart
#	packages/stream_chat_flutter/lib/src/gallery_footer.dart
#	packages/stream_chat_flutter/lib/src/message_input.dart
#	packages/stream_chat_flutter/lib/src/message_list_view.dart
#	packages/stream_chat_flutter/lib/src/user_item.dart
This commit is contained in:
xsahil03x
2021-07-19 12:49:07 +05:30
188 changed files with 6663 additions and 3145 deletions
@@ -26,6 +26,18 @@ import 'package:substring_highlight/substring_highlight.dart';
import 'package:video_compress/video_compress.dart';
import 'package:stream_chat_flutter/src/extension.dart';
export 'package:video_compress/video_compress.dart' show VideoQuality;
/// A callback that can be passed to [MessageInput.onError].
///
/// This callback should not throw.
///
/// It exists merely for error reporting, and should not be used otherwise.
typedef ErrorListener = void Function(
Object error,
StackTrace? stackTrace,
);
/// Builder for attachment thumbnails
typedef AttachmentThumbnailBuilder = Widget Function(
BuildContext,
@@ -150,11 +162,20 @@ class MessageInput extends StatefulWidget {
this.showCommandsButton = true,
this.mentionsTileBuilder,
this.maxAttachmentSize = _kDefaultMaxAttachmentSize,
this.compressedVideoQuality = VideoQuality.DefaultQuality,
this.compressedVideoFrameRate = 30,
this.onError,
}) : super(key: key);
/// Message to edit
final Message? editMessage;
/// Video quality to use when compressing the videos
final VideoQuality compressedVideoQuality;
/// Frame rate to use when compressing the videos
final int compressedVideoFrameRate;
/// Max attachment size in bytes
/// Defaults to 20 MB
/// do not set it if you're using our default CDN
@@ -224,6 +245,9 @@ class MessageInput extends StatefulWidget {
/// Customize the tile for the mentions overlay
final MentionTileBuilder? mentionsTileBuilder;
/// A callback for error reporting
final ErrorListener? onError;
@override
MessageInputState createState() => MessageInputState();
@@ -331,7 +355,7 @@ class MessageInputState extends State<MessageInput> {
Padding(
padding: const EdgeInsets.all(8),
child: StreamSvgIcon.reply(
color: _streamChatTheme.colorTheme.greyGainsboro,
color: _streamChatTheme.colorTheme.disabled,
),
),
const Text(
@@ -398,7 +422,8 @@ class MessageInputState extends State<MessageInput> {
border: _sendAsDm
? null
: Border.all(
color: _streamChatTheme.colorTheme.black.withOpacity(.5),
color: _streamChatTheme.colorTheme.textHighEmphasis
.withOpacity(.5),
width: 2,
),
borderRadius: BorderRadius.circular(3),
@@ -407,8 +432,8 @@ class MessageInputState extends State<MessageInput> {
child: Material(
borderRadius: BorderRadius.circular(3),
color: _sendAsDm
? _streamChatTheme.colorTheme.accentBlue
: _streamChatTheme.colorTheme.white,
? _streamChatTheme.colorTheme.accentPrimary
: _streamChatTheme.colorTheme.barsBg,
child: InkWell(
onTap: () {
setState(() {
@@ -423,7 +448,7 @@ class MessageInputState extends State<MessageInput> {
: CrossFadeState.showSecond,
firstChild: StreamSvgIcon.check(
size: 16,
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
),
secondChild: const SizedBox(
height: 16,
@@ -439,7 +464,8 @@ class MessageInputState extends State<MessageInput> {
child: Text(
context.translations.alsoSendAsDirectMessageLabel,
style: _streamChatTheme.textTheme.footnote.copyWith(
color: _streamChatTheme.colorTheme.black.withOpacity(0.5),
color: _streamChatTheme.colorTheme.textHighEmphasis
.withOpacity(0.5),
),
),
),
@@ -579,7 +605,7 @@ class MessageInputState extends State<MessageInput> {
isDense: true,
hintText: _getHint(context),
hintStyle: _streamChatTheme.messageInputTheme.inputTextStyle!.copyWith(
color: _streamChatTheme.colorTheme.grey,
color: _streamChatTheme.colorTheme.textLowEmphasis,
),
border: const OutlineInputBorder(
borderSide: BorderSide(
@@ -617,7 +643,7 @@ class MessageInputState extends State<MessageInput> {
constraints: BoxConstraints.tight(const Size(64, 24)),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: _streamChatTheme.colorTheme.accentBlue,
color: _streamChatTheme.colorTheme.accentPrimary,
),
alignment: Alignment.center,
child: Row(
@@ -831,12 +857,12 @@ class MessageInputState extends State<MessageInput> {
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
clipBehavior: Clip.hardEdge,
child: Container(
constraints: BoxConstraints.loose(const Size.fromHeight(400)),
decoration: BoxDecoration(
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
borderRadius: BorderRadius.circular(8)),
child: ListView(
padding: const EdgeInsets.all(0),
@@ -852,14 +878,14 @@ class MessageInputState extends State<MessageInput> {
horizontal: 8,
),
child: StreamSvgIcon.lightning(
color: _streamChatTheme.colorTheme.accentBlue,
color: _streamChatTheme.colorTheme.accentPrimary,
),
),
Text(
context.translations.instantCommandsLabel,
style: TextStyle(
color:
_streamChatTheme.colorTheme.black.withOpacity(.5),
color: _streamChatTheme.colorTheme.textHighEmphasis
.withOpacity(.5),
),
)
],
@@ -899,7 +925,7 @@ class MessageInputState extends State<MessageInput> {
color: _streamChatTheme
// ignore: lines_longer_than_80_chars
.colorTheme
.grey,
.textLowEmphasis,
),
),
],
@@ -943,24 +969,29 @@ class MessageInputState extends State<MessageInput> {
switch (index) {
case 0:
return _attachments.isEmpty
? streamChatThemeData.colorTheme.accentBlue
? streamChatThemeData.colorTheme.accentPrimary
: (!_attachmentContainsFile
? streamChatThemeData.colorTheme.accentBlue
: streamChatThemeData.colorTheme.black.withOpacity(0.2));
? streamChatThemeData.colorTheme.accentPrimary
: streamChatThemeData.colorTheme.textHighEmphasis
.withOpacity(0.2));
case 1:
return _attachmentContainsFile
? streamChatThemeData.colorTheme.accentBlue
? streamChatThemeData.colorTheme.accentPrimary
: (_attachments.isEmpty
? streamChatThemeData.colorTheme.black.withOpacity(0.5)
: streamChatThemeData.colorTheme.black.withOpacity(0.2));
? streamChatThemeData.colorTheme.textHighEmphasis
.withOpacity(0.5)
: streamChatThemeData.colorTheme.textHighEmphasis
.withOpacity(0.2));
case 2:
return _attachmentContainsFile && _attachments.isNotEmpty
? streamChatThemeData.colorTheme.black.withOpacity(0.2)
: streamChatThemeData.colorTheme.black.withOpacity(0.5);
? streamChatThemeData.colorTheme.textHighEmphasis.withOpacity(0.2)
: streamChatThemeData.colorTheme.textHighEmphasis
.withOpacity(0.5);
case 3:
return _attachmentContainsFile && _attachments.isNotEmpty
? streamChatThemeData.colorTheme.black.withOpacity(0.2)
: streamChatThemeData.colorTheme.black.withOpacity(0.5);
? streamChatThemeData.colorTheme.textHighEmphasis.withOpacity(0.2)
: streamChatThemeData.colorTheme.textHighEmphasis
.withOpacity(0.5);
default:
return Colors.black;
}
@@ -970,7 +1001,7 @@ class MessageInputState extends State<MessageInput> {
duration: const Duration(milliseconds: 300),
height: _openFilePickerSection ? _filePickerSize : 0,
child: Material(
color: _streamChatTheme.colorTheme.whiteSmoke,
color: _streamChatTheme.colorTheme.inputBg,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
@@ -1033,7 +1064,7 @@ class MessageInputState extends State<MessageInput> {
},
child: DecoratedBox(
decoration: BoxDecoration(
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
@@ -1049,7 +1080,7 @@ class MessageInputState extends State<MessageInput> {
height: 4,
child: DecoratedBox(
decoration: BoxDecoration(
color: _streamChatTheme.colorTheme.whiteSmoke,
color: _streamChatTheme.colorTheme.inputBg,
borderRadius: BorderRadius.circular(4),
),
),
@@ -1063,7 +1094,7 @@ class MessageInputState extends State<MessageInput> {
Expanded(
child: DecoratedBox(
decoration: BoxDecoration(
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
borderRadius: BorderRadius.circular(8),
),
child: _PickerWidget(
@@ -1106,8 +1137,11 @@ class MessageInputState extends State<MessageInput> {
if (file.size! > widget.maxAttachmentSize) {
if (medium.type == AssetType.video && file.path != null) {
final mediaInfo = await (VideoService.compressVideo(file.path!)
as FutureOr<MediaInfo>);
final mediaInfo = await (VideoService.compressVideo(
file.path!,
frameRate: widget.compressedVideoFrameRate,
quality: widget.compressedVideoQuality,
) as FutureOr<MediaInfo>);
if (mediaInfo.filesize! > widget.maxAttachmentSize) {
_showErrorAlert(
@@ -1147,7 +1181,7 @@ class MessageInputState extends State<MessageInput> {
);
case 'ban':
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentBlue,
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: StreamSvgIcon.iconUserDelete(
size: 16,
@@ -1156,7 +1190,7 @@ class MessageInputState extends State<MessageInput> {
);
case 'flag':
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentBlue,
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: StreamSvgIcon.flag(
size: 14,
@@ -1165,7 +1199,7 @@ class MessageInputState extends State<MessageInput> {
);
case 'imgur':
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentBlue,
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: ClipOval(
child: StreamSvgIcon.imgur(
@@ -1175,7 +1209,7 @@ class MessageInputState extends State<MessageInput> {
);
case 'mute':
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentBlue,
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: StreamSvgIcon.mute(
size: 16,
@@ -1184,7 +1218,7 @@ class MessageInputState extends State<MessageInput> {
);
case 'unban':
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentBlue,
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: StreamSvgIcon.userAdd(
size: 16,
@@ -1193,7 +1227,7 @@ class MessageInputState extends State<MessageInput> {
);
case 'unmute':
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentBlue,
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: StreamSvgIcon.volumeUp(
size: 16,
@@ -1202,7 +1236,7 @@ class MessageInputState extends State<MessageInput> {
);
default:
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentBlue,
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: StreamSvgIcon.lightning(
size: 16,
@@ -1242,7 +1276,7 @@ class MessageInputState extends State<MessageInput> {
final child = Card(
margin: const EdgeInsets.all(8),
elevation: 2,
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
@@ -1250,7 +1284,7 @@ class MessageInputState extends State<MessageInput> {
child: Container(
constraints: BoxConstraints.loose(const Size.fromHeight(240)),
decoration: BoxDecoration(
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
),
child: FutureBuilder<List<Member>>(
future: queryMembers ?? Future.value(members),
@@ -1266,7 +1300,7 @@ class MessageInputState extends State<MessageInput> {
.where((it) => it.user != null)
.map(
(m) => Material(
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
child: InkWell(
onTap: () {
if (m.user != null) {
@@ -1353,7 +1387,7 @@ class MessageInputState extends State<MessageInput> {
child: Card(
margin: const EdgeInsets.all(8),
elevation: 2,
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
@@ -1368,7 +1402,7 @@ class MessageInputState extends State<MessageInput> {
offset: Offset(0, -4),
),
],
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
),
child: ListView.builder(
padding: const EdgeInsets.all(0),
@@ -1384,8 +1418,8 @@ class MessageInputState extends State<MessageInput> {
padding:
const EdgeInsets.symmetric(horizontal: 8),
child: StreamSvgIcon.smile(
color:
_streamChatTheme.colorTheme.accentBlue,
color: _streamChatTheme
.colorTheme.accentPrimary,
),
),
Flexible(
@@ -1394,7 +1428,8 @@ class MessageInputState extends State<MessageInput> {
query,
),
style: TextStyle(
color: _streamChatTheme.colorTheme.black
color: _streamChatTheme
.colorTheme.textHighEmphasis
.withOpacity(.5),
),
),
@@ -1566,11 +1601,12 @@ class MessageInputState extends State<MessageInput> {
onPressed: () {
setState(() => _attachments.remove(attachment.id));
},
fillColor: _streamChatTheme.colorTheme.black.withOpacity(.5),
fillColor:
_streamChatTheme.colorTheme.textHighEmphasis.withOpacity(.5),
child: Center(
child: StreamSvgIcon.close(
size: 24,
color: _streamChatTheme.colorTheme.white,
color: _streamChatTheme.colorTheme.barsBg,
),
),
),
@@ -1605,8 +1641,8 @@ class MessageInputState extends State<MessageInput> {
errorWidget: (_, obj, trace) =>
getFileTypeImage(attachment.extraData['other'] as String?),
placeholder: (context, _) => Shimmer.fromColors(
baseColor: _streamChatTheme.colorTheme.greyGainsboro,
highlightColor: _streamChatTheme.colorTheme.whiteSmoke,
baseColor: _streamChatTheme.colorTheme.disabled,
highlightColor: _streamChatTheme.colorTheme.inputBg,
child: Image.asset(
'images/placeholder.png',
fit: BoxFit.cover,
@@ -1647,7 +1683,7 @@ class MessageInputState extends State<MessageInput> {
return IconButton(
icon: StreamSvgIcon.lightning(
color: s.isNotEmpty
? _streamChatTheme.colorTheme.greyGainsboro
? _streamChatTheme.colorTheme.disabled
: (_commandsOverlay != null
? _streamChatTheme.messageInputTheme.actionButtonColor
: _streamChatTheme.messageInputTheme.actionButtonIdleColor),
@@ -1823,11 +1859,11 @@ class MessageInputState extends State<MessageInput> {
}
if (camera) {
PickedFile? pickedFile;
XFile? pickedFile;
if (fileType == DefaultAttachmentTypes.image) {
pickedFile = await _imagePicker.getImage(source: ImageSource.camera);
pickedFile = await _imagePicker.pickImage(source: ImageSource.camera);
} else if (fileType == DefaultAttachmentTypes.video) {
pickedFile = await _imagePicker.getVideo(source: ImageSource.camera);
pickedFile = await _imagePicker.pickVideo(source: ImageSource.camera);
}
if (pickedFile == null) {
return;
@@ -1879,8 +1915,11 @@ class MessageInputState extends State<MessageInput> {
if (file.size! > widget.maxAttachmentSize) {
if (attachmentType == 'video' && file.path != null) {
final mediaInfo = await (VideoService.compressVideo(file.path!)
as FutureOr<MediaInfo>);
final mediaInfo = await (VideoService.compressVideo(
file.path!,
frameRate: widget.compressedVideoFrameRate,
quality: widget.compressedVideoQuality,
) as FutureOr<MediaInfo>);
if (mediaInfo.filesize! > widget.maxAttachmentSize) {
_showErrorAlert(
@@ -1957,7 +1996,7 @@ class MessageInputState extends State<MessageInput> {
}
/// Sends the current message
void sendMessage() async {
Future<void> sendMessage() async {
var text = textEditingController.text.trim();
if (text.isEmpty && _attachments.isEmpty) {
return;
@@ -1973,9 +2012,7 @@ class MessageInputState extends State<MessageInput> {
textEditingController.clear();
_attachments.clear();
if (widget.onQuotedMessageCleared != null) {
widget.onQuotedMessageCleared!();
}
widget.onQuotedMessageCleared?.call();
setState(() {
_messageIsPresent = false;
@@ -1987,7 +2024,6 @@ class MessageInputState extends State<MessageInput> {
_mentionsOverlay?.remove();
_mentionsOverlay = null;
Future sendingFuture;
Message message;
if (widget.editMessage != null) {
message = widget.editMessage!.copyWith(
@@ -2025,33 +2061,39 @@ class MessageInputState extends State<MessageInput> {
_mentionedUsers.clear();
if (widget.editMessage == null ||
widget.editMessage!.status == MessageSendingStatus.failed ||
widget.editMessage!.status == MessageSendingStatus.sending) {
sendingFuture = channel.sendMessage(message);
} else {
sendingFuture = channel.updateMessage(message);
}
try {
Future sendingFuture;
if (widget.editMessage == null ||
widget.editMessage!.status == MessageSendingStatus.failed ||
widget.editMessage!.status == MessageSendingStatus.sending) {
sendingFuture = channel.sendMessage(message);
} else {
sendingFuture = channel.updateMessage(message);
}
if (!shouldUnfocus) {
FocusScope.of(context).requestFocus(_focusNode);
}
if (!shouldUnfocus) {
FocusScope.of(context).requestFocus(_focusNode);
}
return sendingFuture.then((resp) {
final resp = await sendingFuture;
if (resp.message?.type == 'error') {
_parseExistingMessage(message);
}
if (widget.onMessageSent != null) {
widget.onMessageSent!(resp.message);
widget.onMessageSent?.call(resp.message);
} catch (e, stk) {
if (widget.onError != null) {
widget.onError?.call(e, stk);
} else {
rethrow;
}
});
}
}
StreamSubscription? _keyboardListener;
void _showErrorAlert(String description) {
showModalBottomSheet(
backgroundColor: _streamChatTheme.colorTheme.white,
backgroundColor: _streamChatTheme.colorTheme.barsBg,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
@@ -2065,7 +2107,7 @@ class MessageInputState extends State<MessageInput> {
height: 26,
),
StreamSvgIcon.error(
color: _streamChatTheme.colorTheme.accentRed,
color: _streamChatTheme.colorTheme.accentError,
size: 24,
),
const SizedBox(
@@ -2089,7 +2131,8 @@ class MessageInputState extends State<MessageInput> {
height: 36,
),
Container(
color: _streamChatTheme.colorTheme.black.withOpacity(.08),
color:
_streamChatTheme.colorTheme.textHighEmphasis.withOpacity(.08),
height: 1,
),
Row(
@@ -2101,8 +2144,8 @@ class MessageInputState extends State<MessageInput> {
},
child: Text(
context.translations.okLabel,
style: _streamChatTheme.textTheme.bodyBold
.copyWith(color: _streamChatTheme.colorTheme.accentBlue),
style: _streamChatTheme.textTheme.bodyBold.copyWith(
color: _streamChatTheme.colorTheme.accentPrimary),
),
),
],
@@ -2243,12 +2286,12 @@ class __PickerWidgetState extends State<_PickerWidget> {
},
child: Container(
constraints: const BoxConstraints.expand(),
color: widget.streamChatTheme.colorTheme.whiteSmoke,
color: widget.streamChatTheme.colorTheme.inputBg,
alignment: Alignment.center,
child: Text(
context.translations.addMoreFilesLabel,
style: TextStyle(
color: widget.streamChatTheme.colorTheme.accentBlue,
color: widget.streamChatTheme.colorTheme.accentPrimary,
fontWeight: FontWeight.bold,
),
),
@@ -2266,7 +2309,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
PhotoManager.openSetting();
},
child: Container(
color: widget.streamChatTheme.colorTheme.whiteSmoke,
color: widget.streamChatTheme.colorTheme.inputBg,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -2275,12 +2318,13 @@ class __PickerWidgetState extends State<_PickerWidget> {
'svgs/icon_picture_empty_state.svg',
package: 'stream_chat_flutter',
height: 140,
color: widget.streamChatTheme.colorTheme.greyGainsboro,
color: widget.streamChatTheme.colorTheme.disabled,
),
Text(
context.translations.enablePhotoAndVideoAccessMessage,
style: widget.streamChatTheme.textTheme.body.copyWith(
color: widget.streamChatTheme.colorTheme.grey),
color:
widget.streamChatTheme.colorTheme.textLowEmphasis),
textAlign: TextAlign.center,
),
const SizedBox(height: 6),
@@ -2288,7 +2332,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
child: Text(
context.translations.allowGalleryAccessMessage,
style: widget.streamChatTheme.textTheme.bodyBold.copyWith(
color: widget.streamChatTheme.colorTheme.accentBlue,
color: widget.streamChatTheme.colorTheme.accentPrimary,
),
),
),