Merge branch 'master' into reactions-optimistic-update
This commit is contained in:
@@ -33,6 +33,8 @@ class ChannelMediaDisplayScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
||||
Map<String, VideoPlayerController> controllerCache = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -147,8 +149,15 @@ class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
||||
.forEach((e) {
|
||||
VideoPlayerController controller;
|
||||
if (e.type == 'video') {
|
||||
controller = VideoPlayerController.network(e.assetUrl);
|
||||
controller.initialize();
|
||||
var cachedController = controllerCache[e.assetUrl];
|
||||
|
||||
if (cachedController == null) {
|
||||
controller = VideoPlayerController.network(e.assetUrl);
|
||||
controller.initialize();
|
||||
controllerCache[e.assetUrl] = controller;
|
||||
} else {
|
||||
controller = cachedController;
|
||||
}
|
||||
}
|
||||
media.add(_AssetPackage(e, item.message, controller));
|
||||
});
|
||||
@@ -221,6 +230,14 @@ class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
||||
stream: messageSearchBloc.messagesStream,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
for (var c in controllerCache.values) {
|
||||
c.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _AssetPackage {
|
||||
|
||||
@@ -61,9 +61,14 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
||||
.where((element) => element.type == 'video')
|
||||
.toList()
|
||||
.forEach((element) {
|
||||
videoPackages.add(VideoPackage(context, element, () {
|
||||
setState(() {});
|
||||
}));
|
||||
videoPackages.add(VideoPackage(
|
||||
context,
|
||||
element,
|
||||
() {
|
||||
setState(() {});
|
||||
},
|
||||
showControls: true,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -233,15 +238,18 @@ class VideoPackage {
|
||||
bool initialised = false;
|
||||
VoidCallback onInit;
|
||||
BuildContext context;
|
||||
bool showControls;
|
||||
|
||||
///
|
||||
VideoPackage(this.context, Attachment attachment, this.onInit) {
|
||||
VideoPackage(this.context, Attachment attachment, this.onInit,
|
||||
{this.showControls = false}) {
|
||||
_videoPlayerController = VideoPlayerController.network(attachment.assetUrl);
|
||||
_videoPlayerController.initialize().whenComplete(() {
|
||||
initialised = true;
|
||||
_chewieController = ChewieController(
|
||||
videoPlayerController: _videoPlayerController,
|
||||
autoInitialize: false,
|
||||
autoInitialize: true,
|
||||
showControls: showControls,
|
||||
aspectRatio: _videoPlayerController.value.aspectRatio,
|
||||
);
|
||||
onInit();
|
||||
|
||||
@@ -4,10 +4,10 @@ import 'dart:ui';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:stream_chat_flutter/src/reaction_picker.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||
import 'package:stream_chat_flutter/src/utils.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
|
||||
import 'extension.dart';
|
||||
import 'message_input.dart';
|
||||
@@ -33,6 +33,7 @@ class MessageActionsModal extends StatefulWidget {
|
||||
final ShapeBorder messageShape;
|
||||
final ShapeBorder attachmentShape;
|
||||
final DisplayWidget showUserAvatar;
|
||||
final Map<String, VideoPackage> videoPackages;
|
||||
|
||||
const MessageActionsModal({
|
||||
Key key,
|
||||
@@ -53,6 +54,7 @@ class MessageActionsModal extends StatefulWidget {
|
||||
this.messageShape,
|
||||
this.attachmentShape,
|
||||
this.reverse = false,
|
||||
this.videoPackages,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -180,6 +182,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
showSendingIndicator: false,
|
||||
shape: widget.messageShape,
|
||||
attachmentShape: widget.attachmentShape,
|
||||
videoPackages: widget.videoPackages,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
|
||||
@@ -239,6 +239,8 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
|
||||
final MessageListController _messageListController = MessageListController();
|
||||
|
||||
final Map<String, VideoPackage> videoPackages = {};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MessageListCore(
|
||||
@@ -776,6 +778,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
break;
|
||||
}
|
||||
},
|
||||
videoPackages: videoPackages,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -936,6 +939,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
break;
|
||||
}
|
||||
},
|
||||
videoPackages: videoPackages,
|
||||
);
|
||||
|
||||
if (!message.isDeleted && !message.isSystem && !message.isEphemeral) {
|
||||
@@ -1052,6 +1056,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
streamChannel.reloadChannel();
|
||||
}
|
||||
_messageNewListener?.cancel();
|
||||
videoPackages.values.forEach((e) => e.dispose());
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.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/reaction_bubble.dart';
|
||||
import 'package:stream_chat_flutter/src/reaction_picker.dart';
|
||||
@@ -22,6 +23,7 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
final ShapeBorder messageShape;
|
||||
final ShapeBorder attachmentShape;
|
||||
final void Function(User) onUserAvatarTap;
|
||||
final Map<String, VideoPackage> videoPackages;
|
||||
|
||||
const MessageReactionsModal({
|
||||
Key key,
|
||||
@@ -35,6 +37,7 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
this.reverse = false,
|
||||
this.showUserAvatar = DisplayWidget.show,
|
||||
this.onUserAvatarTap,
|
||||
this.videoPackages,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -143,6 +146,7 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
(message.status ==
|
||||
MessageSendingStatus.sent ||
|
||||
message.status == null),
|
||||
videoPackages: videoPackages,
|
||||
),
|
||||
),
|
||||
if (message.latestReactions?.isNotEmpty == true) ...[
|
||||
|
||||
@@ -142,6 +142,9 @@ class MessageWidget extends StatefulWidget {
|
||||
/// Function called when quotedMessage is tapped
|
||||
final OnQuotedMessageTap onQuotedMessageTap;
|
||||
|
||||
/// The cache for the video controllers of attachments IDed as message ID + attachment index
|
||||
final Map<String, VideoPackage> videoPackages;
|
||||
|
||||
///
|
||||
MessageWidget({
|
||||
Key key,
|
||||
@@ -190,6 +193,7 @@ class MessageWidget extends StatefulWidget {
|
||||
this.attachmentPadding = EdgeInsets.zero,
|
||||
this.allRead = false,
|
||||
this.onQuotedMessageTap,
|
||||
this.videoPackages,
|
||||
}) : attachmentBuilders = {
|
||||
'image': (context, message, attachment) {
|
||||
return ImageAttachment(
|
||||
@@ -740,6 +744,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
!isFailedState &&
|
||||
widget.onThreadTap != null,
|
||||
showFlagButton: widget.showFlagButton,
|
||||
videoPackages: widget.videoPackages,
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -768,6 +773,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
editMessageInputBuilder: widget.editMessageInputBuilder,
|
||||
onThreadTap: widget.onThreadTap,
|
||||
showReactions: widget.showReactions,
|
||||
videoPackages: widget.videoPackages,
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -832,6 +838,41 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
children: widget.message.attachments
|
||||
?.where((element) => element.ogScrapeUrl == null)
|
||||
?.map((attachment) {
|
||||
if (attachment.type == 'video') {
|
||||
VideoPackage package;
|
||||
|
||||
if (widget.videoPackages == null) {
|
||||
package = VideoPackage(context, attachment, () {});
|
||||
} else {
|
||||
package = widget?.videoPackages[
|
||||
'${widget.message.id}${widget.message.attachments.indexOf(attachment)}'] ??
|
||||
VideoPackage(context, attachment, () {});
|
||||
}
|
||||
|
||||
if (widget.videoPackages != null) {
|
||||
widget.videoPackages[
|
||||
'${widget.message.id}${widget.message.attachments.indexOf(attachment)}'] =
|
||||
package;
|
||||
}
|
||||
|
||||
return Transform(
|
||||
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
||||
alignment: Alignment.center,
|
||||
child: VideoAttachment(
|
||||
attachment: attachment,
|
||||
messageTheme: widget.messageTheme,
|
||||
size: Size(
|
||||
MediaQuery.of(context).size.width * 0.8,
|
||||
MediaQuery.of(context).size.height * 0.3,
|
||||
),
|
||||
message: widget.message,
|
||||
onShowMessage: widget.onShowMessage,
|
||||
onReturnAction: widget.onReturnAction,
|
||||
videoPackage: package,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final attachmentBuilder =
|
||||
widget.attachmentBuilders[attachment.type];
|
||||
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:chewie/chewie.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/full_screen_media.dart';
|
||||
import 'package:stream_chat_flutter/src/utils.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
import 'attachment_error.dart';
|
||||
import 'attachment_title.dart';
|
||||
|
||||
class VideoAttachment extends StatefulWidget {
|
||||
@@ -16,11 +12,13 @@ class VideoAttachment extends StatefulWidget {
|
||||
final Message message;
|
||||
final ShowMessageCallback onShowMessage;
|
||||
final ValueChanged<ReturnActionType> onReturnAction;
|
||||
final VideoPackage videoPackage;
|
||||
|
||||
VideoAttachment({
|
||||
Key key,
|
||||
@required this.attachment,
|
||||
@required this.messageTheme,
|
||||
this.videoPackage,
|
||||
this.message,
|
||||
this.size,
|
||||
this.onShowMessage,
|
||||
@@ -32,13 +30,21 @@ class VideoAttachment extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _VideoAttachmentState extends State<VideoAttachment> {
|
||||
ChewieController _chewieController;
|
||||
VideoPlayerController _videoPlayerController;
|
||||
bool initialized = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.videoPackage.onInit = () {
|
||||
setState(() {
|
||||
initialized = true;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!initialized) {
|
||||
if (!widget.videoPackage.initialised) {
|
||||
return Container(
|
||||
height: widget.size?.height ?? 100,
|
||||
width: widget.size?.width ?? 100,
|
||||
@@ -47,43 +53,6 @@ class _VideoAttachmentState extends State<VideoAttachment> {
|
||||
),
|
||||
);
|
||||
}
|
||||
_chewieController = ChewieController(
|
||||
videoPlayerController: _videoPlayerController,
|
||||
autoInitialize: true,
|
||||
showControls: false,
|
||||
aspectRatio: _videoPlayerController.value.aspectRatio,
|
||||
errorBuilder: (_, e) {
|
||||
if (widget.attachment.thumbUrl != null) {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: widget.size?.height,
|
||||
width: widget.size?.width,
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
fit: BoxFit.cover,
|
||||
image: CachedNetworkImageProvider(
|
||||
widget.attachment.thumbUrl,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.attachment.titleLink != null)
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () =>
|
||||
launchURL(context, widget.attachment.titleLink),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return AttachmentError(
|
||||
attachment: widget.attachment,
|
||||
size: widget.size,
|
||||
);
|
||||
});
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
@@ -123,7 +92,7 @@ class _VideoAttachmentState extends State<VideoAttachment> {
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Chewie(
|
||||
controller: _chewieController,
|
||||
controller: widget.videoPackage.chewieController,
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Center(
|
||||
@@ -153,23 +122,4 @@ class _VideoAttachmentState extends State<VideoAttachment> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_videoPlayerController =
|
||||
VideoPlayerController.network(widget.attachment.assetUrl);
|
||||
_videoPlayerController.initialize().whenComplete(() {
|
||||
setState(() {
|
||||
initialized = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_videoPlayerController?.dispose();
|
||||
_chewieController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ void main() {
|
||||
testWidgets(
|
||||
'StreamChatCore should disconnect on background',
|
||||
(WidgetTester tester) async {
|
||||
await fakeAsync((_async) {
|
||||
fakeAsync((_async) {
|
||||
final client = MockClient();
|
||||
final clientState = MockClientState();
|
||||
final channel = MockChannel();
|
||||
@@ -123,6 +123,7 @@ void main() {
|
||||
_async.elapse(Duration(seconds: 5));
|
||||
|
||||
verify(client.disconnect()).called(1);
|
||||
eventStreamController.close();
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -174,6 +175,7 @@ void main() {
|
||||
await untilCalled(showLocalNotificationMock(event));
|
||||
|
||||
verify(showLocalNotificationMock(event)).called(1);
|
||||
eventStreamController.close();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user