Merge branch 'develop' into v4
This commit is contained in:
@@ -7,6 +7,22 @@
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- Mentions overlay now doesn't overflow when not enough height available
|
||||
|
||||
## 3.5.0
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- [[#888]](https://github.com/GetStream/stream-chat-flutter/issues/888) Fix `unban` command not working in `MessageInput`.
|
||||
- [[#805]](https://github.com/GetStream/stream-chat-flutter/issues/805) Updated chewie dependency version to 1.3.0
|
||||
- Fix `showScrollToBottom` in `MessageListView` not respecting false value.
|
||||
- Fix default `Channel` route not opening from `ChannelListView` when `ChannelAvatar` is tapped
|
||||
|
||||
## 3.4.0
|
||||
- Updated `stream_chat_flutter_core` dependency to [`3.4.0`](https://pub.dev/packages/stream_chat_flutter_core/changelog).
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- SVG rendering fixes.
|
||||
- Use file extension instead of mimeType for downloading files.
|
||||
- [[#860]](https://github.com/GetStream/stream-chat-flutter/issues/860) CastError while compressing Videos.
|
||||
|
||||
@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
compileSdkVersion 31
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
@@ -41,7 +41,7 @@ android {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "com.example.example"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
targetSdkVersion 31
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:exported="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.5.20'
|
||||
ext.kotlin_version = '1.6.0'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
|
||||
@@ -592,7 +592,9 @@ class _ChannelListViewState extends State<ChannelListView> {
|
||||
child: ChannelPreview(
|
||||
onLongPress: widget.onChannelLongPress,
|
||||
channel: channel,
|
||||
onImageTap: () => widget.onImageTap?.call(channel),
|
||||
onImageTap: widget.onImageTap != null
|
||||
? () => widget.onImageTap!(channel)
|
||||
: null,
|
||||
onTap: (channel) => onTap(channel, widget.channelWidget),
|
||||
),
|
||||
),
|
||||
@@ -605,7 +607,7 @@ class _ChannelListViewState extends State<ChannelListView> {
|
||||
if (widget.onChannelTap != null) {
|
||||
onTap = widget.onChannelTap!;
|
||||
} else {
|
||||
onTap = (client, _) {
|
||||
onTap = (channel, _) {
|
||||
if (widget.channelWidget == null) {
|
||||
return;
|
||||
}
|
||||
@@ -613,7 +615,7 @@ class _ChannelListViewState extends State<ChannelListView> {
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
channel: client,
|
||||
channel: channel,
|
||||
child: widget.channelWidget!,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -65,24 +65,35 @@ class FullScreenMedia extends StatefulWidget {
|
||||
|
||||
class _FullScreenMediaState extends State<FullScreenMedia>
|
||||
with SingleTickerProviderStateMixin {
|
||||
bool _optionsShown = true;
|
||||
|
||||
late final AnimationController _controller;
|
||||
late final AnimationController _animationController;
|
||||
late final PageController _pageController;
|
||||
|
||||
late int _currentPage;
|
||||
late final _curvedAnimation = CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: Curves.easeOut,
|
||||
reverseCurve: Curves.easeIn,
|
||||
);
|
||||
|
||||
final _opacityTween = Tween<double>(begin: 1, end: 0);
|
||||
late final _opacityAnimation = _opacityTween.animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: const Interval(0, 0.6, curve: Curves.easeOut),
|
||||
),
|
||||
);
|
||||
|
||||
late final ValueNotifier<int> _currentPage = ValueNotifier(widget.startIndex);
|
||||
|
||||
final videoPackages = <String, VideoPackage>{};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
);
|
||||
_pageController = PageController(initialPage: widget.startIndex);
|
||||
_currentPage = widget.startIndex;
|
||||
for (var i = 0; i < widget.mediaAttachments.length; i++) {
|
||||
final attachment = widget.mediaAttachments[i];
|
||||
if (attachment.type != 'video') continue;
|
||||
@@ -116,41 +127,38 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: Stack(
|
||||
children: [
|
||||
AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, snapshot) => PageView.builder(
|
||||
controller: _pageController,
|
||||
onPageChanged: (val) {
|
||||
setState(() {
|
||||
_currentPage = val;
|
||||
});
|
||||
PageView.builder(
|
||||
controller: _pageController,
|
||||
onPageChanged: (val) {
|
||||
_currentPage.value = val;
|
||||
|
||||
if (videoPackages.isEmpty) {
|
||||
return;
|
||||
if (videoPackages.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final currentAttachment = widget.mediaAttachments[val];
|
||||
|
||||
for (final e in videoPackages.values) {
|
||||
if (e._attachment != currentAttachment) {
|
||||
e._chewieController?.pause();
|
||||
}
|
||||
}
|
||||
|
||||
final currentAttachment = widget.mediaAttachments[val];
|
||||
|
||||
for (final e in videoPackages.values) {
|
||||
if (e._attachment != currentAttachment) {
|
||||
e._chewieController?.pause();
|
||||
}
|
||||
}
|
||||
|
||||
if (widget.autoplayVideos &&
|
||||
currentAttachment.type == 'video') {
|
||||
final controller = videoPackages[currentAttachment.id]!;
|
||||
controller._chewieController?.play();
|
||||
}
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
final attachment = widget.mediaAttachments[index];
|
||||
if (attachment.type == 'image' ||
|
||||
attachment.type == 'giphy') {
|
||||
final imageUrl = attachment.imageUrl ??
|
||||
attachment.assetUrl ??
|
||||
attachment.thumbUrl;
|
||||
return PhotoView(
|
||||
if (widget.autoplayVideos &&
|
||||
currentAttachment.type == 'video') {
|
||||
final controller = videoPackages[currentAttachment.id]!;
|
||||
controller._chewieController?.play();
|
||||
}
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
final attachment = widget.mediaAttachments[index];
|
||||
if (attachment.type == 'image' || attachment.type == 'giphy') {
|
||||
final imageUrl = attachment.imageUrl ??
|
||||
attachment.assetUrl ??
|
||||
attachment.thumbUrl;
|
||||
return AnimatedBuilder(
|
||||
animation: _curvedAnimation,
|
||||
builder: (context, child) => PhotoView(
|
||||
loadingBuilder: (context, image) => const Offstage(),
|
||||
imageProvider: (imageUrl == null &&
|
||||
attachment.localUri != null &&
|
||||
@@ -166,97 +174,91 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
||||
color: ColorTween(
|
||||
begin: ChannelHeaderTheme.of(context).color,
|
||||
end: Colors.black,
|
||||
).lerp(_controller.value),
|
||||
).lerp(_curvedAnimation.value),
|
||||
),
|
||||
onTapUp: (a, b, c) {
|
||||
setState(() {
|
||||
_optionsShown = !_optionsShown;
|
||||
});
|
||||
if (_controller.isCompleted) {
|
||||
_controller.reverse();
|
||||
if (_animationController.isCompleted) {
|
||||
_animationController.reverse();
|
||||
} else {
|
||||
_controller.forward();
|
||||
_animationController.forward();
|
||||
}
|
||||
},
|
||||
);
|
||||
} else if (attachment.type == 'video') {
|
||||
final controller = videoPackages[attachment.id]!;
|
||||
if (!controller.initialized) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_optionsShown = !_optionsShown;
|
||||
});
|
||||
if (_controller.isCompleted) {
|
||||
_controller.reverse();
|
||||
} else {
|
||||
_controller.forward();
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 50,
|
||||
),
|
||||
child: Chewie(
|
||||
controller: controller.chewieController!,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else if (attachment.type == 'video') {
|
||||
final controller = videoPackages[attachment.id]!;
|
||||
if (!controller.initialized) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
},
|
||||
itemCount: widget.mediaAttachments.length,
|
||||
),
|
||||
),
|
||||
AnimatedOpacity(
|
||||
opacity: _optionsShown ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
GalleryHeader(
|
||||
userName: widget.userName,
|
||||
sentAt: context.translations.sentAtText(
|
||||
date: widget.message.createdAt,
|
||||
time: widget.message.createdAt,
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
if (_animationController.isCompleted) {
|
||||
_animationController.reverse();
|
||||
} else {
|
||||
_animationController.forward();
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 50,
|
||||
),
|
||||
child: Chewie(
|
||||
controller: controller.chewieController!,
|
||||
),
|
||||
),
|
||||
onBackPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
message: widget.message,
|
||||
currentIndex: _currentPage,
|
||||
onShowMessage: () {
|
||||
widget.onShowMessage?.call(
|
||||
widget.message,
|
||||
StreamChannel.of(context).channel,
|
||||
);
|
||||
},
|
||||
attachmentActionsModalBuilder:
|
||||
widget.attachmentActionsModalBuilder,
|
||||
),
|
||||
if (!widget.message.isEphemeral)
|
||||
GalleryFooter(
|
||||
currentPage: _currentPage,
|
||||
totalPages: widget.mediaAttachments.length,
|
||||
mediaAttachments: widget.mediaAttachments,
|
||||
);
|
||||
}
|
||||
return const SizedBox();
|
||||
},
|
||||
itemCount: widget.mediaAttachments.length,
|
||||
),
|
||||
FadeTransition(
|
||||
opacity: _opacityAnimation,
|
||||
child: ValueListenableBuilder<int>(
|
||||
valueListenable: _currentPage,
|
||||
builder: (context, value, child) => Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
GalleryHeader(
|
||||
userName: widget.userName,
|
||||
sentAt: context.translations.sentAtText(
|
||||
date: widget.message.createdAt,
|
||||
time: widget.message.createdAt,
|
||||
),
|
||||
onBackPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
message: widget.message,
|
||||
mediaSelectedCallBack: (val) {
|
||||
setState(() {
|
||||
_currentPage = val;
|
||||
currentIndex: value,
|
||||
onShowMessage: () {
|
||||
widget.onShowMessage?.call(
|
||||
widget.message,
|
||||
StreamChannel.of(context).channel,
|
||||
);
|
||||
},
|
||||
attachmentActionsModalBuilder:
|
||||
widget.attachmentActionsModalBuilder,
|
||||
),
|
||||
if (!widget.message.isEphemeral)
|
||||
GalleryFooter(
|
||||
currentPage: value,
|
||||
totalPages: widget.mediaAttachments.length,
|
||||
mediaAttachments: widget.mediaAttachments,
|
||||
message: widget.message,
|
||||
mediaSelectedCallBack: (val) {
|
||||
_currentPage.value = val;
|
||||
_pageController.animateToPage(
|
||||
val,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -264,9 +266,11 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() async {
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
_pageController.dispose();
|
||||
for (final package in videoPackages.values) {
|
||||
await package.dispose();
|
||||
package.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -356,6 +356,7 @@ class MessageInputState extends State<MessageInput>
|
||||
with RestorationMixin<MessageInput> {
|
||||
final _imagePicker = ImagePicker();
|
||||
late FocusNode _focusNode = widget.focusNode ?? FocusNode();
|
||||
late final _isInternalFocusNode = widget.focusNode == null;
|
||||
bool _inputEnabled = true;
|
||||
|
||||
bool get _commandEnabled => _effectiveController.value.command != null;
|
||||
@@ -1168,26 +1169,31 @@ class MessageInputState extends State<MessageInput>
|
||||
};
|
||||
}
|
||||
|
||||
return UserMentionsOverlay(
|
||||
query: query,
|
||||
mentionAllAppUsers: widget.mentionAllAppUsers,
|
||||
client: StreamChat.of(context).client,
|
||||
channel: channel,
|
||||
size: Size(renderObject.size.width - 16, 400),
|
||||
mentionsTileBuilder: tileBuilder,
|
||||
onMentionUserTap: (user) {
|
||||
_effectiveController.addMentionedUser(user);
|
||||
splits[splits.length - 1] = user.name;
|
||||
final rejoin = splits.join('@');
|
||||
return LayoutBuilder(
|
||||
builder: (context, snapshot) => UserMentionsOverlay(
|
||||
query: query,
|
||||
mentionAllAppUsers: widget.mentionAllAppUsers,
|
||||
client: StreamChat.of(context).client,
|
||||
channel: channel,
|
||||
size: Size(
|
||||
renderObject.size.width - 16,
|
||||
min(400, (snapshot.maxHeight - renderObject.size.height - 16).abs()),
|
||||
),
|
||||
mentionsTileBuilder: tileBuilder,
|
||||
onMentionUserTap: (user) {
|
||||
_effectiveController.addMentionedUser(user);
|
||||
splits[splits.length - 1] = user.name;
|
||||
final rejoin = splits.join('@');
|
||||
|
||||
_effectiveController.text = rejoin +
|
||||
_effectiveController.text.substring(
|
||||
_effectiveController.selectionStart,
|
||||
);
|
||||
_effectiveController.text = rejoin +
|
||||
_effectiveController.text.substring(
|
||||
_effectiveController.selectionStart,
|
||||
);
|
||||
|
||||
_onChangedDebounced.cancel();
|
||||
setState(() => _showMentionsOverlay = false);
|
||||
},
|
||||
_onChangedDebounced.cancel();
|
||||
setState(() => _showMentionsOverlay = false);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1816,6 +1822,7 @@ class MessageInputState extends State<MessageInput>
|
||||
.removeListener(_onChangedDebounced);
|
||||
_controller?.dispose();
|
||||
_focusNode.removeListener(_focusNodeListener);
|
||||
if (_isInternalFocusNode) _focusNode.dispose();
|
||||
_stopSlowMode();
|
||||
_onChangedDebounced.cancel();
|
||||
super.dispose();
|
||||
|
||||
@@ -709,20 +709,21 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
);
|
||||
},
|
||||
),
|
||||
BetterStreamBuilder<bool>(
|
||||
stream: streamChannel!.channel.state!.isUpToDateStream,
|
||||
initialData: streamChannel!.channel.state!.isUpToDate,
|
||||
builder: (context, snapshot) => ValueListenableBuilder<bool>(
|
||||
valueListenable: _showScrollToBottom,
|
||||
child: _buildScrollToBottom(),
|
||||
builder: (context, value, child) {
|
||||
if (!snapshot || value) {
|
||||
return child!;
|
||||
}
|
||||
return const Offstage();
|
||||
},
|
||||
if (widget.showScrollToBottom)
|
||||
BetterStreamBuilder<bool>(
|
||||
stream: streamChannel!.channel.state!.isUpToDateStream,
|
||||
initialData: streamChannel!.channel.state!.isUpToDate,
|
||||
builder: (context, snapshot) => ValueListenableBuilder<bool>(
|
||||
valueListenable: _showScrollToBottom,
|
||||
child: _buildScrollToBottom(),
|
||||
builder: (context, value, child) {
|
||||
if (!snapshot || value) {
|
||||
return child!;
|
||||
}
|
||||
return const Offstage();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.showFloatingDateDivider)
|
||||
_buildFloatingDateDivider(itemCount),
|
||||
],
|
||||
|
||||
@@ -84,9 +84,10 @@ class MessageText extends StatelessWidget {
|
||||
String _replaceMentions(String text) {
|
||||
var messageTextToRender = text;
|
||||
for (final user in message.mentionedUsers.toSet()) {
|
||||
final userId = user.id;
|
||||
final userName = user.name;
|
||||
messageTextToRender = messageTextToRender.replaceAll(
|
||||
'@$userName',
|
||||
'@$userId',
|
||||
'[@$userName](@${userName.replaceAll(' ', '')})',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: stream_chat_flutter
|
||||
homepage: https://github.com/GetStream/stream-chat-flutter
|
||||
description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter.
|
||||
version: 3.3.2
|
||||
version: 3.5.0
|
||||
repository: https://github.com/GetStream/stream-chat-flutter
|
||||
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
|
||||
|
||||
@@ -12,11 +12,11 @@ environment:
|
||||
dependencies:
|
||||
cached_network_image: ^3.0.0
|
||||
characters: ^1.1.0
|
||||
chewie: ^1.2.0
|
||||
chewie: ^1.3.0
|
||||
collection: ^1.15.0
|
||||
diacritic: ^0.1.3
|
||||
dio: ^4.0.0
|
||||
ezanimation: ^0.5.0
|
||||
ezanimation: ^0.6.0
|
||||
file_picker: ^4.1.3
|
||||
flutter:
|
||||
sdk: flutter
|
||||
@@ -27,7 +27,7 @@ dependencies:
|
||||
http_parser: ^4.0.0
|
||||
image_gallery_saver: ^1.7.0
|
||||
image_picker: ^0.8.2
|
||||
jiffy: ^4.1.0
|
||||
jiffy: ^5.0.0
|
||||
lottie: ^1.0.1
|
||||
meta: ^1.3.0
|
||||
path_provider: ^2.0.1
|
||||
@@ -36,7 +36,7 @@ dependencies:
|
||||
rxdart: ^0.27.0
|
||||
share_plus: ^3.0.4
|
||||
shimmer: ^2.0.0
|
||||
stream_chat_flutter_core: ^3.3.1
|
||||
stream_chat_flutter_core: ^3.5.0
|
||||
substring_highlight: ^1.0.26
|
||||
synchronized: ^3.0.0
|
||||
url_launcher: ^6.0.3
|
||||
@@ -57,6 +57,6 @@ dev_dependencies:
|
||||
dart_code_metrics: ^4.4.0
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
golden_toolkit: ^0.11.0
|
||||
golden_toolkit: ^0.13.0
|
||||
mocktail: ^0.2.0
|
||||
path: ^1.8.0
|
||||
|
||||
Reference in New Issue
Block a user