refactor: improve fullscreen_media, minor changes.

Signed-off-by: xsahil03x <xdsahil@gmail.com>
This commit is contained in:
Sahil Kumar
2023-07-28 20:04:13 +05:30
committed by xsahil03x
parent 572207c354
commit 93b4158897
12 changed files with 135 additions and 465 deletions
@@ -1,6 +1,7 @@
export 'attachment_error.dart';
export 'attachment_upload_state_builder.dart';
export 'file_attachment.dart';
export 'gallery_attachment.dart';
export 'giphy_attachment.dart';
export 'image_attachment.dart';
export 'url_attachment.dart';
export 'video_attachment.dart';
@@ -1,33 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// {@template attachmentError}
/// Widget for building in case of error
/// {@endtemplate}
class AttachmentError extends StatelessWidget {
/// {@macro attachmentError}
const AttachmentError({
super.key,
this.constraints,
});
/// constraints of error
final BoxConstraints? constraints;
@override
Widget build(BuildContext context) {
return Center(
child: Container(
constraints: constraints ?? const BoxConstraints.expand(),
color:
StreamChatTheme.of(context).colorTheme.accentError.withOpacity(0.1),
child: Center(
child: Icon(
Icons.error_outline,
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis,
),
),
),
);
}
}
@@ -1,51 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// {@template attachmentTitle}
/// Title for attachments
/// {@endtemplate}
class StreamAttachmentTitle extends StatelessWidget {
/// {@macro attachmentTitle}
const StreamAttachmentTitle({
super.key,
required this.attachment,
required this.messageTheme,
});
/// Theme to apply to text
final StreamMessageThemeData messageTheme;
/// Attachment data to display
final Attachment attachment;
@override
Widget build(BuildContext context) {
final ogScrapeUrl = attachment.ogScrapeUrl;
return GestureDetector(
onTap: () {
final ogScrapeUrl = attachment.ogScrapeUrl;
if (ogScrapeUrl != null) launchURL(context, ogScrapeUrl);
},
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
if (attachment.title != null)
Text(
attachment.title!,
overflow: TextOverflow.ellipsis,
style: messageTheme.messageTextStyle?.copyWith(
color: StreamChatTheme.of(context).colorTheme.accentPrimary,
fontWeight: FontWeight.bold,
),
),
if (ogScrapeUrl != null)
Text(ogScrapeUrl, style: messageTheme.messageTextStyle),
],
),
),
);
}
}
@@ -68,67 +68,39 @@ class StreamGiphyAttachment extends StatelessWidget {
borderRadius: BorderRadius.circular(14),
);
return Hero(
tag: giphy.id,
child: Container(
constraints: constraints,
clipBehavior: Clip.hardEdge,
decoration: ShapeDecoration(shape: shape),
child: AspectRatio(
aspectRatio: giphySize?.aspectRatio ?? 1,
child: Stack(
alignment: Alignment.center,
children: [
StreamGiphyAttachmentThumbnail(
type: type,
giphy: giphy,
fit: fit,
width: double.infinity,
height: double.infinity,
),
if (giphy.uploadState.isSuccess)
const Positioned(
bottom: 8,
left: 8,
child: GiphyChip(),
)
else
Padding(
padding: const EdgeInsets.all(8),
child: StreamAttachmentUploadStateBuilder(
message: message,
attachment: giphy,
),
return Container(
constraints: constraints,
clipBehavior: Clip.hardEdge,
decoration: ShapeDecoration(shape: shape),
child: AspectRatio(
aspectRatio: giphySize?.aspectRatio ?? 1,
child: Stack(
alignment: Alignment.center,
children: [
StreamGiphyAttachmentThumbnail(
type: type,
giphy: giphy,
fit: fit,
width: double.infinity,
height: double.infinity,
),
if (giphy.uploadState.isSuccess)
const Positioned(
bottom: 8,
left: 8,
child: GiphyChip(),
)
else
Padding(
padding: const EdgeInsets.all(8),
child: StreamAttachmentUploadStateBuilder(
message: message,
attachment: giphy,
),
],
),
),
],
),
),
);
}
// Future<void> _onTap(BuildContext context) async {
// if (onAttachmentTap != null) {
// return onAttachmentTap!();
// }
//
// await Navigator.of(context).push(
// MaterialPageRoute(
// builder: (_) {
// final channel = StreamChannel.of(context).channel;
// return StreamChannel(
// channel: channel,
// child: StreamFullScreenMediaBuilder(
// mediaAttachmentPackages: message.getAttachmentPackageList(),
// startIndex: message.attachments.indexOf(giphy),
// userName: message.user!.name,
// onShowMessage: onShowMessage,
// onReplyMessage: onReplyMessage,
// attachmentActionsModalBuilder: attachmentActionsModalBuilder,
// ),
// );
// },
// ),
// );
// }
}
@@ -103,31 +103,3 @@ class StreamImageAttachment extends StatelessWidget {
);
}
}
// Future<void> _onTap(
// BuildContext context,
// int index,
// ) async {
// if (onAttachmentTap != null) {
// return onAttachmentTap!();
// }
//
// final channel = StreamChannel.of(context).channel;
//
// Navigator.of(context).push(
// MaterialPageRoute(
// builder: (context) => StreamChannel(
// channel: channel,
// child: StreamFullScreenMediaBuilder(
// mediaAttachmentPackages: message.getAttachmentPackageList(),
// startIndex: index,
// userName: message.user!.name,
// onShowMessage: onShowMessage,
// onReplyMessage: onReplyMessage,
// attachmentActionsModalBuilder: attachmentActionsModalBuilder,
// ),
// ),
// ),
// );
// }
// }
@@ -2,14 +2,11 @@ import 'dart:async';
import 'dart:io';
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:stream_chat_flutter/platform_widget_builder/platform_widget_builder.dart';
import 'package:stream_chat_flutter/src/attachment/thumbnail/media_attachment_thumbnail.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/src/fullscreen_media/gallery_navigation_item.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:video_player/video_player.dart';
@@ -291,22 +288,16 @@ class _FullScreenMediaState extends State<StreamFullScreenMedia> {
? StreamChannelHeaderTheme.of(context).color
: Colors.black,
duration: kThemeAnimationDuration,
child: ContextMenuArea(
verticalPadding: 0,
builder: (_) => [
DownloadMenuItem(attachment: attachment),
],
child: PhotoView.customChild(
maxScale: PhotoViewComputedScale.covered,
minScale: PhotoViewComputedScale.contained,
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
),
child: StreamMediaAttachmentThumbnail(
media: attachment,
width: double.infinity,
height: double.infinity,
),
child: PhotoView.customChild(
maxScale: PhotoViewComputedScale.covered,
minScale: PhotoViewComputedScale.contained,
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
),
child: StreamMediaAttachmentThumbnail(
media: attachment,
width: double.infinity,
height: double.infinity,
),
),
),
@@ -318,19 +309,11 @@ class _FullScreenMediaState extends State<StreamFullScreenMedia> {
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!,
),
),
child: Chewie(
controller: controller.chewieController!,
),
);
}
@@ -344,74 +327,6 @@ class _FullScreenMediaState extends State<StreamFullScreenMedia> {
}
}
/// A widget for desktop and web users to be able to navigate left and right
/// through a gallery of images.
class GalleryNavigationItem extends StatelessWidget {
/// Builds a [GalleryNavigationItem].
const GalleryNavigationItem({
super.key,
required this.icon,
this.iconSize = 48,
required this.onPressed,
required this.opacityAnimation,
this.left,
this.right,
});
/// The icon to display.
final Widget icon;
/// The size of the icon.
///
/// Defaults to 48.
final double iconSize;
/// The callback to perform when the button is clicked.
final VoidCallback onPressed;
/// The animation for showing & hiding this widget.
final ValueListenable<bool> opacityAnimation;
/// The left-hand placement of the button.
final double? left;
/// The right-hand placement of the button.
final double? right;
@override
Widget build(BuildContext context) {
return PlatformWidgetBuilder(
desktop: (_, child) => child,
web: (_, child) => child,
child: Positioned(
left: left,
right: right,
top: MediaQuery.of(context).size.height / 2,
child: ValueListenableBuilder<bool>(
valueListenable: opacityAnimation,
builder: (context, shouldShow, child) {
return AnimatedOpacity(
opacity: shouldShow ? 1 : 0,
duration: kThemeAnimationDuration,
child: child,
);
},
child: Material(
color: Colors.transparent,
type: MaterialType.circle,
clipBehavior: Clip.antiAlias,
child: IconButton(
icon: icon,
iconSize: iconSize,
onPressed: onPressed,
),
),
),
),
);
}
}
/// Class for packaging up things required for videos
class VideoPackage {
/// Constructor for creating [VideoPackage]
@@ -1,13 +1,13 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:contextmenu/contextmenu.dart';
import 'package:dart_vlc/dart_vlc.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/attachment/thumbnail/media_attachment_thumbnail.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/src/fullscreen_media/gallery_navigation_item.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Returns an instance of [FullScreenMediaDesktop].
@@ -310,9 +310,6 @@ class _FullScreenMediaDesktopState extends State<FullScreenMediaDesktop> {
final attachment = currentAttachmentPackage.attachment;
if (attachment.type == AttachmentType.image ||
attachment.type == AttachmentType.giphy) {
final imageUrl = attachment.imageUrl ??
attachment.assetUrl ??
attachment.thumbUrl;
return ValueListenableBuilder<bool>(
valueListenable: _isDisplayingDetail,
builder: (context, isDisplayingDetail, _) =>
@@ -324,39 +321,19 @@ class _FullScreenMediaDesktopState extends State<FullScreenMediaDesktop> {
child: ContextMenuArea(
verticalPadding: 0,
builder: (_) => [
DownloadMenuItem(
attachment: attachment,
),
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,
);
},
child: PhotoView.customChild(
maxScale: PhotoViewComputedScale.covered,
minScale: PhotoViewComputedScale.contained,
heroAttributes: PhotoViewHeroAttributes(
tag: widget.mediaAttachmentPackages,
),
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
),
child: StreamMediaAttachmentThumbnail(
media: attachment,
width: double.infinity,
height: double.infinity,
),
),
),
),
@@ -364,29 +341,18 @@ class _FullScreenMediaDesktopState extends State<FullScreenMediaDesktop> {
} else if (attachment.type == AttachmentType.video) {
final package = videoPackages[attachment.id]!;
package.player.open(
Playlist(
medias: [
Media.network(package.attachment.assetUrl),
],
),
Media.network(package.attachment.assetUrl),
autoStart: widget.autoplayVideos,
);
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,
),
),
child: ContextMenuArea(
verticalPadding: 0,
builder: (_) => [
DownloadMenuItem(attachment: attachment),
],
child: Video(player: package.player),
),
);
}
@@ -399,74 +365,6 @@ class _FullScreenMediaDesktopState extends State<FullScreenMediaDesktop> {
}
}
/// A widget for desktop and web users to be able to navigate left and right
/// through a gallery of images.
class GalleryNavigationItem extends StatelessWidget {
/// Builds a [GalleryNavigationItem].
const GalleryNavigationItem({
super.key,
required this.icon,
this.iconSize = 48,
required this.onPressed,
required this.opacityAnimation,
this.left,
this.right,
});
/// The icon to display.
final Widget icon;
/// The size of the icon.
///
/// Defaults to 48.
final double iconSize;
/// The callback to perform when the button is clicked.
final VoidCallback onPressed;
/// The animation for showing & hiding this widget.
final ValueListenable<bool> opacityAnimation;
/// The left-hand placement of the button.
final double? left;
/// The right-hand placement of the button.
final double? right;
@override
Widget build(BuildContext context) {
return PlatformWidgetBuilder(
desktop: (_, child) => child,
web: (_, child) => child,
child: Positioned(
left: left,
right: right,
top: MediaQuery.of(context).size.height / 2,
child: ValueListenableBuilder<bool>(
valueListenable: opacityAnimation,
builder: (context, shouldShow, child) {
return AnimatedOpacity(
opacity: shouldShow ? 1 : 0,
duration: kThemeAnimationDuration,
child: child,
);
},
child: Material(
color: Colors.transparent,
type: MaterialType.circle,
clipBehavior: Clip.antiAlias,
child: IconButton(
icon: icon,
iconSize: iconSize,
onPressed: onPressed,
),
),
),
),
);
}
}
/// Class for packaging up things required for videos
class DesktopVideoPackage {
/// Constructor for creating [VideoPackage]
@@ -0,0 +1,71 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/platform_widget_builder/src/platform_widget_builder.dart';
/// A widget for desktop and web users to be able to navigate left and right
/// through a gallery of images.
class GalleryNavigationItem extends StatelessWidget {
/// Builds a [GalleryNavigationItem].
const GalleryNavigationItem({
super.key,
required this.icon,
this.iconSize = 48,
required this.onPressed,
required this.opacityAnimation,
this.left,
this.right,
});
/// The icon to display.
final Widget icon;
/// The size of the icon.
///
/// Defaults to 48.
final double iconSize;
/// The callback to perform when the button is clicked.
final VoidCallback onPressed;
/// The animation for showing & hiding this widget.
final ValueListenable<bool> opacityAnimation;
/// The left-hand placement of the button.
final double? left;
/// The right-hand placement of the button.
final double? right;
@override
Widget build(BuildContext context) {
return PlatformWidgetBuilder(
desktop: (_, child) => child,
web: (_, child) => child,
child: Positioned(
left: left,
right: right,
top: MediaQuery.of(context).size.height / 2,
child: ValueListenableBuilder<bool>(
valueListenable: opacityAnimation,
builder: (context, shouldShow, child) {
return AnimatedOpacity(
opacity: shouldShow ? 1 : 0,
duration: kThemeAnimationDuration,
child: child,
);
},
child: Material(
color: Colors.transparent,
type: MaterialType.circle,
clipBehavior: Clip.antiAlias,
child: IconButton(
icon: icon,
iconSize: iconSize,
onPressed: onPressed,
),
),
),
),
);
}
}
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/platform_widget_builder/platform_widget_builder.dart';
import 'package:stream_chat_flutter/src/attachment/thumbnail/file_attachment_thumbnail.dart';
import 'package:stream_chat_flutter/src/attachment/thumbnail/image_attachment_thumbnail.dart';
import 'package:stream_chat_flutter/src/message_input/clear_input_item_button.dart';
@@ -178,16 +177,10 @@ class _QuotedMessage extends StatelessWidget {
}
// Add clear button if needed.
if (onQuotedMessageClear != null) {
if (isDesktopDeviceOrWeb && onQuotedMessageClear != null) {
children.insert(
0,
PlatformWidgetBuilder(
web: (context, child) => child,
desktop: (context, child) => child,
child: ClearInputItemButton(
onTap: onQuotedMessageClear,
),
),
ClearInputItemButton(onTap: onQuotedMessageClear),
);
}
@@ -6,7 +6,6 @@ export 'package:stream_chat_flutter/src/message_widget/quoted_message.dart';
export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
export 'src/attachment/attachment.dart';
export 'src/attachment/attachment_title.dart';
export 'src/attachment/gallery_attachment.dart';
export 'src/attachment/handler/stream_attachment_handler.dart';
export 'src/attachment/image_attachment.dart';
@@ -1,25 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import '../mocks.dart';
void main() {
testWidgets('AttachmentError test', (tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (context, child) => StreamChat(
client: MockClient(),
child: child,
),
home: const Scaffold(
body: Center(
child: AttachmentError(),
),
),
),
);
expect(find.byType(Icon), findsOneWidget);
});
}
@@ -1,42 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import '../mocks.dart';
void main() {
testWidgets('AttachmentTitle renders properly', (tester) async {
final mockClient = MockClient();
await tester.pumpWidget(
MaterialApp(
builder: (context, child) => StreamChat(
client: mockClient,
streamChatThemeData: StreamChatThemeData.light(),
child: child,
),
home: Scaffold(
body: Builder(
builder: (context) {
return Center(
child: StreamAttachmentTitle(
attachment: Attachment(
title: 'Test Attachment',
type: 'video',
titleLink: 'https://www.youtube.com/watch?v=lytQi-slT5Y',
ogScrapeUrl: 'https://www.youtube.com/watch?v=lytQi-slT5Y',
),
messageTheme: StreamChatTheme.of(context).ownMessageTheme,
),
);
},
),
),
),
);
expect(find.byType(StreamAttachmentTitle), findsOneWidget);
expect(find.text('Test Attachment'), findsOneWidget);
expect(find.text('https://www.youtube.com/watch?v=lytQi-slT5Y'),
findsOneWidget);
});
}