feat: Now compatible with videos

This commit is contained in:
Deven Joshi
2020-11-26 18:32:46 +05:30
parent 146204ed3a
commit f409845818
9 changed files with 186 additions and 70 deletions
+129 -40
View File
@@ -1,16 +1,18 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:photo_view/photo_view.dart';
import 'package:stream_chat_flutter/src/image_footer.dart';
import 'package:stream_chat_flutter/src/image_header.dart';
import 'package:video_player/video_player.dart';
import '../stream_chat_flutter.dart';
/// A full screen image widget
class FullScreenImage extends StatefulWidget {
class FullScreenMedia extends StatefulWidget {
/// The url of the image
final List<String> urls;
final List<Attachment> mediaAttachments;
final Message message;
final int startIndex;
@@ -18,9 +20,9 @@ class FullScreenImage extends StatefulWidget {
final DateTime sentAt;
/// Instantiate a new FullScreenImage
const FullScreenImage({
const FullScreenMedia({
Key key,
@required this.urls,
@required this.mediaAttachments,
this.message,
this.startIndex = 0,
this.userName = '',
@@ -28,10 +30,10 @@ class FullScreenImage extends StatefulWidget {
}) : super(key: key);
@override
_FullScreenImageState createState() => _FullScreenImageState();
_FullScreenMediaState createState() => _FullScreenMediaState();
}
class _FullScreenImageState extends State<FullScreenImage>
class _FullScreenMediaState extends State<FullScreenMedia>
with SingleTickerProviderStateMixin {
bool _optionsShown = true;
@@ -40,6 +42,8 @@ class _FullScreenImageState extends State<FullScreenImage>
int _currentPage;
List<VideoPackage> videoPackages = [];
@override
void initState() {
super.initState();
@@ -47,6 +51,14 @@ class _FullScreenImageState extends State<FullScreenImage>
AnimationController(vsync: this, duration: Duration(milliseconds: 300));
_pageController = PageController(initialPage: widget.startIndex);
_currentPage = widget.startIndex;
widget.mediaAttachments
.where((element) => element.type == 'video')
.toList()
.forEach((element) {
videoPackages.add(VideoPackage(context, element, () {
setState(() {});
}));
});
}
@override
@@ -66,36 +78,63 @@ class _FullScreenImageState extends State<FullScreenImage>
});
},
itemBuilder: (context, position) {
return PhotoView(
imageProvider:
CachedNetworkImageProvider(widget.urls[position]),
maxScale: PhotoViewComputedScale.covered,
minScale: PhotoViewComputedScale.contained,
heroAttributes: PhotoViewHeroAttributes(
tag: widget.urls,
),
backgroundDecoration: BoxDecoration(
color: ColorTween(
begin: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.color,
end: Colors.black)
.lerp(_controller.value),
),
onTapUp: (a, b, c) {
setState(() {
_optionsShown = !_optionsShown;
});
if (_controller.isCompleted) {
_controller.reverse();
} else {
_controller.forward();
}
},
);
if (widget.mediaAttachments[position].type == 'image') {
return PhotoView(
imageProvider: CachedNetworkImageProvider(
widget.mediaAttachments[position].imageUrl ??
widget.mediaAttachments[position].assetUrl ??
widget.mediaAttachments[position].thumbUrl),
maxScale: PhotoViewComputedScale.covered,
minScale: PhotoViewComputedScale.contained,
heroAttributes: PhotoViewHeroAttributes(
tag: widget.mediaAttachments,
),
backgroundDecoration: BoxDecoration(
color: ColorTween(
begin: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.color,
end: Colors.black)
.lerp(_controller.value),
),
onTapUp: (a, b, c) {
setState(() {
_optionsShown = !_optionsShown;
});
if (_controller.isCompleted) {
_controller.reverse();
} else {
_controller.forward();
}
},
);
} else if (widget.mediaAttachments[position].type ==
'video') {
if (!videoPackages[position].initialised) {
return Center(
child: CircularProgressIndicator(),
);
}
return InkWell(
onTap: () {
setState(() {
_optionsShown = !_optionsShown;
});
if (_controller.isCompleted) {
_controller.reverse();
} else {
_controller.forward();
}
},
child: Chewie(
controller: videoPackages[position].chewieController,
),
);
}
return Container();
},
itemCount: widget.urls.length,
itemCount: widget.mediaAttachments.length,
);
}),
AnimatedOpacity(
@@ -106,19 +145,20 @@ class _FullScreenImageState extends State<FullScreenImage>
children: [
ImageHeader(
userName: widget.userName,
sentAt:
'Sent ${getDay(widget.message.createdAt)} at ${Jiffy(widget.sentAt.toLocal()).format('HH:mm')}',
sentAt: widget.message.createdAt == null
? ''
: 'Sent ${getDay(widget.message.createdAt)} at ${Jiffy(widget.sentAt.toLocal()).format('HH:mm')}',
onBackPressed: () {
Navigator.of(context).pop();
},
message: widget.message,
urls: widget.urls,
urls: widget.mediaAttachments,
currentIndex: _currentPage,
),
ImageFooter(
currentPage: _currentPage,
totalPages: widget.urls.length,
urls: widget.urls,
totalPages: widget.mediaAttachments.length,
urls: widget.mediaAttachments,
message: widget.message,
),
],
@@ -144,4 +184,53 @@ class _FullScreenImageState extends State<FullScreenImage>
return 'on ${Jiffy(dateTime).format("MMM do")}';
}
}
@override
void dispose() {
videoPackages.forEach((element) {
element.dispose();
});
super.dispose();
}
}
class VideoPackage {
VideoPlayerController _videoPlayerController;
ChewieController _chewieController;
bool initialised = false;
VoidCallback onInit;
BuildContext context;
///
VideoPackage(this.context, Attachment attachment, this.onInit) {
_videoPlayerController = VideoPlayerController.network(attachment.assetUrl);
_videoPlayerController.initialize().whenComplete(() {
initialised = true;
_chewieController = ChewieController(
videoPlayerController: _videoPlayerController,
autoInitialize: false,
aspectRatio: _videoPlayerController.value.aspectRatio,
);
onInit();
});
VoidCallback errorListener;
errorListener = () {
if (_videoPlayerController.value.hasError) {
Navigator.pop(context);
launchURL(context, attachment.titleLink);
}
_videoPlayerController.removeListener(errorListener);
};
_videoPlayerController.addListener(errorListener);
}
get videoPlayer => _videoPlayerController;
get chewieController => _chewieController;
void dispose() {
_videoPlayerController.dispose();
_chewieController.dispose();
}
}
+6 -10
View File
@@ -67,11 +67,9 @@ class GiphyAttachment extends StatelessWidget {
return StreamChannel(
channel: channel,
child: FullScreenImage(
urls: [
attachment.imageUrl ??
attachment.assetUrl ??
attachment.thumbUrl
child: FullScreenMedia(
mediaAttachments: [
attachment,
],
userName: message.user.name,
sentAt: message.createdAt,
@@ -295,11 +293,9 @@ class GiphyAttachment extends StatelessWidget {
return StreamChannel(
channel: channel,
child: FullScreenImage(
urls: [
attachment.imageUrl ??
attachment.assetUrl ??
attachment.thumbUrl
child: FullScreenMedia(
mediaAttachments: [
attachment,
],
userName: message.user.name,
sentAt: message.createdAt,
+22 -3
View File
@@ -1,16 +1,22 @@
import 'dart:io';
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:gallery_saver/gallery_saver.dart';
import '../stream_chat_flutter.dart';
import 'stream_icons.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart';
class ImageActionsModal extends StatelessWidget {
final Message message;
final String userName;
final String sentAt;
final List<String> urls;
final List<Attachment> urls;
final currentIndex;
ImageActionsModal(
@@ -118,8 +124,11 @@ class ImageActionsModal extends StatelessWidget {
}),
_buildButton(context, 'Save Image', StreamIcons.save_1,
() async {
await GallerySaver.saveImage(
urls[currentIndex].split('?')[0]);
var url = urls[currentIndex].imageUrl ??
urls[currentIndex].assetUrl ??
urls[currentIndex].thumbUrl;
var file = await _downloadFile(url);
Navigator.pop(context);
}),
if (StreamChat.of(context).user.id == message.user.id)
@@ -171,4 +180,14 @@ class ImageActionsModal extends StatelessWidget {
),
);
}
static Future<File> _downloadFile(String url) async {
var _client = http.Client();
var req = await _client.get(Uri.parse(url));
var bytes = req.bodyBytes;
var dir = (await getTemporaryDirectory()).path;
var file = File('$dir/${basename(url)}');
await file.writeAsBytes(bytes);
return file;
}
}
+3 -5
View File
@@ -47,11 +47,9 @@ class ImageAttachment extends StatelessWidget {
return StreamChannel(
channel: channel,
child: FullScreenImage(
urls: [
attachment.imageUrl ??
attachment.assetUrl ??
attachment.thumbUrl
child: FullScreenMedia(
mediaAttachments: [
attachment,
],
userName: message.user.name,
sentAt: message.createdAt,
+14 -5
View File
@@ -36,7 +36,7 @@ class ImageFooter extends StatefulWidget {
final int currentPage;
final int totalPages;
final List<String> urls;
final List<Attachment> urls;
final Message message;
/// Creates a channel header
@@ -186,7 +186,9 @@ class _ImageFooterState extends State<ImageFooter> {
padding: const EdgeInsets.all(1.0),
child: AspectRatio(
child: CachedNetworkImage(
imageUrl: widget.urls[position],
imageUrl: widget.urls[position].imageUrl ??
widget.urls[position].assetUrl ??
widget.urls[position].thumbUrl,
fit: BoxFit.cover,
),
aspectRatio: 1.0,
@@ -378,8 +380,13 @@ class _ImageFooterState extends State<ImageFooter> {
child: Material(
child: InkWell(
onTap: () async {
await GallerySaver.saveImage(widget
.urls[widget.currentPage]
await GallerySaver.saveImage((widget
.urls[widget.currentPage]
.imageUrl ??
widget.urls[widget.currentPage]
.assetUrl ??
widget.urls[widget.currentPage]
.thumbUrl)
.split('?')[0]);
Navigator.pop(context);
},
@@ -518,7 +525,9 @@ class _ImageFooterState extends State<ImageFooter> {
),
iconSize: 24.0,
onPressed: () async {
var url = widget.urls[widget.currentPage];
var url = widget.urls[widget.currentPage].imageUrl ??
widget.urls[widget.currentPage].assetUrl ??
widget.urls[widget.currentPage].thumbUrl;
var type = url?.split('?')?.first?.split('.')?.last ?? 'jpg';
var request = await HttpClient().getUrl(Uri.parse(url));
var response = await request.close();
+2 -4
View File
@@ -115,10 +115,8 @@ class ImageGroup extends StatelessWidget {
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: channel,
child: FullScreenImage(
urls: images
.map((e) => e.imageUrl ?? e.thumbUrl ?? e.assetUrl)
.toList(),
child: FullScreenMedia(
mediaAttachments: images,
startIndex: index,
userName: message.user.name,
sentAt: message.createdAt,
+1 -1
View File
@@ -30,7 +30,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
final String userName;
final String sentAt;
final List<String> urls;
final List<Attachment> urls;
final currentIndex;
/// Creates a channel header
+1
View File
@@ -180,6 +180,7 @@ class MessageWidget extends StatefulWidget {
MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(context).size.height * 0.3,
),
message: message,
);
},
'giphy': (context, message, attachment) {
+8 -2
View File
@@ -1,6 +1,7 @@
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_image.dart';
import 'package:stream_chat_flutter/src/full_screen_video.dart';
import 'package:stream_chat_flutter/src/utils.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -13,11 +14,13 @@ class VideoAttachment extends StatefulWidget {
final Attachment attachment;
final MessageTheme messageTheme;
final Size size;
final Message message;
VideoAttachment({
Key key,
@required this.attachment,
@required this.messageTheme,
this.message,
this.size,
}) : super(key: key);
@@ -84,8 +87,11 @@ class _VideoAttachmentState extends State<VideoAttachment> {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => FullScreenVideo(
attachment: widget.attachment,
builder: (_) => FullScreenMedia(
mediaAttachments: [widget.attachment],
userName: widget.message.user.name,
sentAt: widget.message.createdAt,
message: widget.message,
),
),
);