Merge remote-tracking branch 'origin/develop' into v7.0.0

# Conflicts:
#	packages/stream_chat/CHANGELOG.md
#	packages/stream_chat/lib/version.dart
#	packages/stream_chat/pubspec.yaml
#	packages/stream_chat_flutter/CHANGELOG.md
#	packages/stream_chat_flutter/lib/src/channel/channel_preview.dart
#	packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart
#	packages/stream_chat_flutter/lib/src/user/user_item.dart
#	packages/stream_chat_flutter/pubspec.yaml
#	packages/stream_chat_flutter_core/CHANGELOG.md
#	packages/stream_chat_flutter_core/pubspec.yaml
#	packages/stream_chat_localizations/CHANGELOG.md
#	packages/stream_chat_localizations/pubspec.yaml
#	packages/stream_chat_persistence/CHANGELOG.md
#	packages/stream_chat_persistence/pubspec.yaml
This commit is contained in:
Sahil Kumar
2023-08-18 13:15:08 +05:30
31 changed files with 751 additions and 437 deletions
@@ -66,7 +66,8 @@ class StreamGroupAvatar extends StatelessWidget {
Widget avatar = GestureDetector(
onTap: onTap,
child: ClipRRect(
borderRadius: borderRadius ?? previewTheme?.borderRadius,
borderRadius:
borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero,
child: Container(
constraints: constraints ?? previewTheme?.constraints,
decoration: BoxDecoration(color: colorTheme.accentPrimary),
@@ -86,7 +86,8 @@ class StreamUserAvatar extends StatelessWidget {
final backupGradientAvatar = ClipRRect(
borderRadius: borderRadius ??
streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius,
streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius ??
BorderRadius.zero,
child: streamChatConfig.defaultUserImage(context, user),
);
@@ -111,7 +111,8 @@ class StreamChannelAvatar extends StatelessWidget {
initialData: channel.image,
builder: (context, channelImage) {
Widget child = ClipRRect(
borderRadius: borderRadius ?? previewTheme?.borderRadius,
borderRadius:
borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero,
child: Container(
constraints: constraints ?? previewTheme?.constraints,
decoration: BoxDecoration(color: colorTheme.accentPrimary),
@@ -3,13 +3,11 @@ import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:chewie/chewie.dart';
import 'package:contextmenu/contextmenu.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:shimmer/shimmer.dart';
import 'package:stream_chat_flutter/platform_widget_builder/platform_widget_builder.dart';
import 'package:stream_chat_flutter/src/context_menu_items/download_menu_item.dart';
import 'package:stream_chat_flutter/src/fullscreen_media/full_screen_media_widget.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:video_player/video_player.dart';
@@ -281,84 +279,83 @@ class _FullScreenMediaState extends State<StreamFullScreenMedia> {
final currentAttachmentPackage =
widget.mediaAttachmentPackages[index];
final attachment = currentAttachmentPackage.attachment;
if (attachment.type == 'image' || attachment.type == 'giphy') {
final imageUrl = attachment.imageUrl ??
attachment.assetUrl ??
attachment.thumbUrl;
return ValueListenableBuilder<bool>(
valueListenable: _isDisplayingDetail,
builder: (context, isDisplayingDetail, _) =>
AnimatedContainer(
return ValueListenableBuilder(
valueListenable: _isDisplayingDetail,
builder: (context, isDisplayingDetail, child) {
return AnimatedContainer(
duration: kThemeChangeDuration,
color: isDisplayingDetail
? StreamChannelHeaderTheme.of(context).color
: Colors.black,
duration: kThemeAnimationDuration,
child: ContextMenuArea(
verticalPadding: 0,
builder: (_) => [
DownloadMenuItem(
attachment: attachment,
),
],
child: PhotoView(
imageProvider: (imageUrl == null &&
attachment.localUri != null &&
attachment.file?.bytes != null)
? Image.memory(attachment.file!.bytes!).image
: CachedNetworkImageProvider(imageUrl!),
errorBuilder: (_, __, ___) => const AttachmentError(),
loadingBuilder: (context, _) {
final image = Image.asset(
'images/placeholder.png',
fit: BoxFit.cover,
package: 'stream_chat_flutter',
child: Builder(
builder: (context) {
if (attachment.type == 'image' ||
attachment.type == 'giphy') {
final imageUrl = attachment.imageUrl ??
attachment.assetUrl ??
attachment.thumbUrl;
return PhotoView(
imageProvider: (imageUrl == null &&
attachment.localUri != null &&
attachment.file?.bytes != null)
? Image.memory(attachment.file!.bytes!).image
: CachedNetworkImageProvider(imageUrl!),
errorBuilder: (_, __, ___) =>
const AttachmentError(),
loadingBuilder: (context, _) {
final image = Image.asset(
'images/placeholder.png',
fit: BoxFit.cover,
package: 'stream_chat_flutter',
);
final colorTheme =
StreamChatTheme.of(context).colorTheme;
return Shimmer.fromColors(
baseColor: colorTheme.disabled,
highlightColor: colorTheme.inputBg,
child: image,
);
},
maxScale: PhotoViewComputedScale.covered,
minScale: PhotoViewComputedScale.contained,
heroAttributes: PhotoViewHeroAttributes(
tag: widget.mediaAttachmentPackages,
),
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
),
);
final colorTheme =
StreamChatTheme.of(context).colorTheme;
return Shimmer.fromColors(
baseColor: colorTheme.disabled,
highlightColor: colorTheme.inputBg,
child: image,
} else if (attachment.type == 'video') {
final controller = videoPackages[attachment.id]!;
if (!controller.initialized) {
return const Center(
child: CircularProgressIndicator.adaptive(),
);
}
final mediaQuery = MediaQuery.of(context);
final bottomPadding = mediaQuery.padding.bottom;
return AnimatedPadding(
duration: kThemeChangeDuration,
padding: EdgeInsets.symmetric(
vertical: isDisplayingDetail
? kToolbarHeight + bottomPadding
: 0,
),
child: Chewie(
controller: controller.chewieController!,
),
);
},
maxScale: PhotoViewComputedScale.covered,
minScale: PhotoViewComputedScale.contained,
heroAttributes: PhotoViewHeroAttributes(
tag: widget.mediaAttachmentPackages,
),
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
),
),
}
return const SizedBox();
},
),
),
);
} else if (attachment.type == 'video') {
final controller = videoPackages[attachment.id]!;
if (!controller.initialized) {
return const Center(
child: CircularProgressIndicator.adaptive(),
);
}
return InkWell(
onTap: switchDisplayingDetail,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 50),
child: ContextMenuArea(
verticalPadding: 0,
builder: (_) => [
DownloadMenuItem(
attachment: attachment,
),
],
child: Chewie(
controller: controller.chewieController!,
),
),
),
);
}
return const SizedBox();
},
);
},
),
),
@@ -475,6 +472,7 @@ class VideoPackage {
videoPlayerController: _videoPlayerController,
autoInitialize: _autoInitialize,
showControls: _showControls,
showOptions: false,
aspectRatio: _videoPlayerController.value.aspectRatio,
);
});
@@ -307,88 +307,95 @@ class _FullScreenMediaDesktopState extends State<FullScreenMediaDesktop> {
final currentAttachmentPackage =
widget.mediaAttachmentPackages[index];
final attachment = currentAttachmentPackage.attachment;
if (attachment.type == 'image' || attachment.type == 'giphy') {
final imageUrl = attachment.imageUrl ??
attachment.assetUrl ??
attachment.thumbUrl;
return ValueListenableBuilder<bool>(
valueListenable: _isDisplayingDetail,
builder: (context, isDisplayingDetail, _) =>
AnimatedContainer(
return ValueListenableBuilder(
valueListenable: _isDisplayingDetail,
builder: (context, isDisplayingDetail, child) {
return AnimatedContainer(
duration: kThemeChangeDuration,
color: isDisplayingDetail
? StreamChannelHeaderTheme.of(context).color
: Colors.black,
duration: kThemeAnimationDuration,
child: ContextMenuArea(
verticalPadding: 0,
builder: (_) => [
DownloadMenuItem(
attachment: attachment,
),
],
child: PhotoView(
imageProvider: (imageUrl == null &&
attachment.localUri != null &&
attachment.file?.bytes != null)
? Image.memory(attachment.file!.bytes!).image
: CachedNetworkImageProvider(imageUrl!),
errorBuilder: (_, __, ___) => const AttachmentError(),
loadingBuilder: (context, _) {
final image = Image.asset(
'images/placeholder.png',
fit: BoxFit.cover,
package: 'stream_chat_flutter',
);
final colorTheme =
StreamChatTheme.of(context).colorTheme;
return Shimmer.fromColors(
baseColor: colorTheme.disabled,
highlightColor: colorTheme.inputBg,
child: image,
);
},
maxScale: PhotoViewComputedScale.covered,
minScale: PhotoViewComputedScale.contained,
heroAttributes: PhotoViewHeroAttributes(
tag: widget.mediaAttachmentPackages,
),
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
),
),
),
),
);
} else if (attachment.type == 'video') {
final package = videoPackages[attachment.id]!;
package.player.open(
Playlist(
medias: [
Media.network(package.attachment.assetUrl),
],
),
autoStart: widget.autoplayVideos,
);
child: Builder(
builder: (context) {
if (attachment.type == 'image' ||
attachment.type == 'giphy') {
final imageUrl = attachment.imageUrl ??
attachment.assetUrl ??
attachment.thumbUrl;
return InkWell(
onTap: switchDisplayingDetail,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 50),
child: ContextMenuArea(
verticalPadding: 0,
builder: (_) => [
DownloadMenuItem(
attachment: attachment,
),
],
child: Video(
player: package.player,
),
return PhotoView(
imageProvider: (imageUrl == null &&
attachment.localUri != null &&
attachment.file?.bytes != null)
? Image.memory(attachment.file!.bytes!).image
: CachedNetworkImageProvider(imageUrl!),
errorBuilder: (_, __, ___) =>
const AttachmentError(),
loadingBuilder: (context, _) {
final image = Image.asset(
'images/placeholder.png',
fit: BoxFit.cover,
package: 'stream_chat_flutter',
);
final colorTheme =
StreamChatTheme.of(context).colorTheme;
return Shimmer.fromColors(
baseColor: colorTheme.disabled,
highlightColor: colorTheme.inputBg,
child: image,
);
},
maxScale: PhotoViewComputedScale.covered,
minScale: PhotoViewComputedScale.contained,
heroAttributes: PhotoViewHeroAttributes(
tag: widget.mediaAttachmentPackages,
),
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
),
);
} else if (attachment.type == 'video') {
final package = videoPackages[attachment.id]!;
package.player.open(
Playlist(
medias: [
Media.network(package.attachment.assetUrl),
],
),
autoStart: widget.autoplayVideos,
);
final mediaQuery = MediaQuery.of(context);
final bottomPadding = mediaQuery.padding.bottom;
return AnimatedPadding(
duration: kThemeChangeDuration,
padding: EdgeInsets.symmetric(
vertical: isDisplayingDetail
? kToolbarHeight + bottomPadding
: 0,
),
child: ContextMenuArea(
verticalPadding: 0,
builder: (_) => [
DownloadMenuItem(
attachment: attachment,
),
],
child: Video(
player: package.player,
),
),
);
}
return const SizedBox();
},
),
),
);
}
return const SizedBox();
);
},
);
},
),
),
@@ -698,6 +698,7 @@ Widget mobileAttachmentPickerBuilder({
ThumbnailFormat attachmentThumbnailFormat = ThumbnailFormat.jpeg,
int attachmentThumbnailQuality = 100,
double attachmentThumbnailScale = 1,
ErrorListener? onError,
}) {
return StreamMobileAttachmentPickerBottomSheet(
controller: controller,
@@ -721,10 +722,15 @@ Widget mobileAttachmentPickerBuilder({
mediaThumbnailQuality: attachmentThumbnailQuality,
mediaThumbnailScale: attachmentThumbnailScale,
onMediaItemSelected: (media) async {
if (selectedIds.contains(media.id)) {
return controller.removeAssetAttachment(media);
try {
if (selectedIds.contains(media.id)) {
return await controller.removeAssetAttachment(media);
}
return await controller.addAssetAttachment(media);
} catch (e, stk) {
if (onError != null) return onError.call(e, stk);
rethrow;
}
return controller.addAssetAttachment(media);
},
);
},
@@ -736,8 +742,15 @@ Widget mobileAttachmentPickerBuilder({
optionViewBuilder: (context, controller) {
return StreamFilePicker(
onFilePicked: (file) async {
if (file != null) await controller.addAttachment(file);
return Navigator.pop(context, controller.value);
try {
if (file != null) await controller.addAttachment(file);
return Navigator.pop(context, controller.value);
} catch (e, stk) {
Navigator.pop(context, controller.value);
if (onError != null) return onError.call(e, stk);
rethrow;
}
},
);
},
@@ -749,10 +762,17 @@ Widget mobileAttachmentPickerBuilder({
optionViewBuilder: (context, controller) {
return StreamImagePicker(
onImagePicked: (image) async {
if (image != null) {
await controller.addAttachment(image);
try {
if (image != null) {
await controller.addAttachment(image);
}
return Navigator.pop(context, controller.value);
} catch (e, stk) {
Navigator.pop(context, controller.value);
if (onError != null) return onError.call(e, stk);
rethrow;
}
return Navigator.pop(context, controller.value);
},
);
},
@@ -764,10 +784,17 @@ Widget mobileAttachmentPickerBuilder({
optionViewBuilder: (context, controller) {
return StreamVideoPicker(
onVideoPicked: (video) async {
if (video != null) {
await controller.addAttachment(video);
try {
if (video != null) {
await controller.addAttachment(video);
}
return Navigator.pop(context, controller.value);
} catch (e, stk) {
Navigator.pop(context, controller.value);
if (onError != null) return onError.call(e, stk);
rethrow;
}
return Navigator.pop(context, controller.value);
},
);
},
@@ -787,6 +814,7 @@ Widget webOrDesktopAttachmentPickerBuilder({
ThumbnailFormat attachmentThumbnailFormat = ThumbnailFormat.jpeg,
int attachmentThumbnailQuality = 100,
double attachmentThumbnailScale = 1,
ErrorListener? onError,
}) {
return StreamWebOrDesktopAttachmentPickerBottomSheet(
controller: controller,
@@ -814,13 +842,20 @@ Widget webOrDesktopAttachmentPickerBuilder({
}.where((option) => option.supportedTypes.every(allowedTypes.contains)),
},
onOptionTap: (context, controller, option) async {
final attachment = await StreamAttachmentHandler.instance.pickFile(
type: option.type.fileType,
);
if (attachment != null) {
await controller.addAttachment(attachment);
try {
final attachment = await StreamAttachmentHandler.instance.pickFile(
type: option.type.fileType,
);
if (attachment != null) {
await controller.addAttachment(attachment);
}
return Navigator.pop(context, controller.value);
} catch (e, stk) {
Navigator.pop(context, controller.value);
if (onError != null) return onError.call(e, stk);
rethrow;
}
return Navigator.pop(context, controller.value);
},
);
}
@@ -69,6 +69,7 @@ Future<T?> showStreamAttachmentPickerModalBottomSheet<T>({
List<AttachmentPickerType> allowedTypes = AttachmentPickerType.values,
List<Attachment>? initialAttachments,
StreamAttachmentPickerController? controller,
ErrorListener? onError,
Color? backgroundColor,
double? elevation,
BoxConstraints? constraints,
@@ -117,6 +118,7 @@ Future<T?> showStreamAttachmentPickerModalBottomSheet<T>({
if (isWebOrDesktop) {
return webOrDesktopAttachmentPickerBuilder.call(
context: context,
onError: onError,
controller: controller,
allowedTypes: allowedTypes,
customOptions: customOptions?.map(
@@ -131,6 +133,7 @@ Future<T?> showStreamAttachmentPickerModalBottomSheet<T>({
return mobileAttachmentPickerBuilder.call(
context: context,
onError: onError,
controller: controller,
allowedTypes: allowedTypes,
customOptions: customOptions,
@@ -811,6 +811,7 @@ class StreamMessageInputState extends State<StreamMessageInput>
Future<void> _onAttachmentButtonPressed() async {
final attachments = await showStreamAttachmentPickerModalBottomSheet(
context: context,
onError: widget.onError,
allowedTypes: widget.allowedAttachmentPickerTypes,
initialAttachments: _effectiveController.attachments,
);
@@ -55,6 +55,7 @@ class StreamMessageWidget extends StatefulWidget {
this.attachmentBorderRadiusGeometry,
this.onMentionTap,
this.onMessageTap,
this.onReactionsTap,
this.showReactionPicker = true,
@internal this.showReactionPickerTail = false,
this.showUserAvatar = DisplayWidget.show,
@@ -524,6 +525,9 @@ class StreamMessageWidget extends StatefulWidget {
/// {@macro onMessageTap}
final void Function(Message)? onMessageTap;
/// {@macro onReactionsTap}
final OnReactionsTap? onReactionsTap;
/// {@template customActions}
/// List of custom actions shown on message long tap
/// {@endtemplate}
@@ -604,6 +608,7 @@ class StreamMessageWidget extends StatefulWidget {
bool? translateUserAvatar,
OnQuotedMessageTap? onQuotedMessageTap,
void Function(Message)? onMessageTap,
OnReactionsTap? onReactionsTap,
List<StreamMessageAction>? customActions,
void Function(Message message, Attachment attachment)? onAttachmentTap,
Widget Function(BuildContext, User)? userAvatarBuilder,
@@ -669,6 +674,7 @@ class StreamMessageWidget extends StatefulWidget {
translateUserAvatar: translateUserAvatar ?? this.translateUserAvatar,
onQuotedMessageTap: onQuotedMessageTap ?? this.onQuotedMessageTap,
onMessageTap: onMessageTap ?? this.onMessageTap,
onReactionsTap: onReactionsTap ?? this.onReactionsTap,
customActions: customActions ?? this.customActions,
onAttachmentTap: onAttachmentTap ?? this.onAttachmentTap,
userAvatarBuilder: userAvatarBuilder ?? this.userAvatarBuilder,
@@ -892,7 +898,11 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
showPinHighlight: widget.showPinHighlight,
showReactionPickerTail: widget.showReactionPickerTail,
showReactions: showReactions,
onReactionsTap: () => _showMessageReactionsModal(context),
onReactionsTap: () {
widget.onReactionsTap != null
? widget.onReactionsTap!(widget.message)
: _showMessageReactionsModal(context);
},
showUserAvatar: widget.showUserAvatar,
streamChat: _streamChat,
translateUserAvatar: widget.translateUserAvatar,
@@ -352,12 +352,12 @@ extension MessageX on Message {
final userName = user.name;
if (linkify) {
messageTextToRender = messageTextToRender?.replaceAll(
'@$userId',
'[@$userName](@${userName.replaceAll(' ', '')})',
RegExp('@($userId|$userName)'),
'[@$userName]($userId)',
);
} else {
messageTextToRender = messageTextToRender?.replaceAll(
'@$userId',
RegExp('@($userId|$userName)'),
'@$userName',
);
}
@@ -221,6 +221,11 @@ typedef OnQuotedMessageTap = void Function(String?);
/// {@endtemplate}
typedef OnMessageTap = void Function(Message);
/// {@template onReactionsTap}
/// The action to perform when a message's reactions are tapped.
/// {@endtemplate}
typedef OnReactionsTap = void Function(Message);
/// {@template messageSearchItemTapCallback}
/// The action to perform when tapping or clicking on a user in a
/// [MessageSearchListView].