lint changes image

This commit is contained in:
Deven Joshi
2021-05-05 14:59:21 +05:30
parent c7b7618edf
commit a688ab3a10
14 changed files with 561 additions and 569 deletions
@@ -192,29 +192,30 @@ class AttachmentActionsModal extends StatelessWidget {
VoidCallback? onTap, { VoidCallback? onTap, {
Color? color, Color? color,
Key? key, Key? key,
}) => Material( }) =>
key: key, Material(
color: StreamChatTheme.of(context).colorTheme.white, key: key,
child: InkWell( color: StreamChatTheme.of(context).colorTheme.white,
onTap: onTap, child: InkWell(
child: Padding( onTap: onTap,
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), child: Padding(
child: Row( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
children: [ child: Row(
icon, children: [
SizedBox(width: 16), icon,
Text( SizedBox(width: 16),
title, Text(
style: StreamChatTheme.of(context) title,
.textTheme style: StreamChatTheme.of(context)
.body .textTheme
.copyWith(color: color), .body
), .copyWith(color: color),
], ),
],
),
), ),
), ),
), );
);
Widget _buildDownloadProgressDialog( Widget _buildDownloadProgressDialog(
BuildContext context, BuildContext context,
@@ -5,7 +5,6 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Back button implementation /// Back button implementation
class StreamBackButton extends StatelessWidget { class StreamBackButton extends StatelessWidget {
/// Constructor for creating back button /// Constructor for creating back button
const StreamBackButton({ const StreamBackButton({
Key? key, Key? key,
@@ -25,37 +24,37 @@ class StreamBackButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) => Stack( Widget build(BuildContext context) => Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
RawMaterialButton( RawMaterialButton(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
elevation: 0, elevation: 0,
highlightElevation: 0, highlightElevation: 0,
focusElevation: 0, focusElevation: 0,
hoverElevation: 0, hoverElevation: 0,
onPressed: () { onPressed: () {
if (onPressed != null) { if (onPressed != null) {
onPressed!(); onPressed!();
} else { } else {
Navigator.maybePop(context); Navigator.maybePop(context);
} }
}, },
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
child: StreamSvgIcon.left( child: StreamSvgIcon.left(
size: 24, size: 24,
color: StreamChatTheme.of(context).colorTheme.black, color: StreamChatTheme.of(context).colorTheme.black,
),
),
if (showUnreads)
Positioned(
top: 7,
right: 7,
child: UnreadIndicator(
cid: cid,
), ),
), ),
], if (showUnreads)
); Positioned(
top: 7,
right: 7,
child: UnreadIndicator(
cid: cid,
),
),
],
);
} }
@@ -98,23 +98,23 @@ class ChannelImage extends StatelessWidget {
(users) => users[otherMember?.userId] ?? otherMember!.user!), (users) => users[otherMember?.userId] ?? otherMember!.user!),
initialData: otherMember!.user, initialData: otherMember!.user,
builder: (context, snapshot) => UserAvatar( builder: (context, snapshot) => UserAvatar(
borderRadius: borderRadius ?? borderRadius: borderRadius ??
StreamChatTheme.of(context) StreamChatTheme.of(context)
.channelPreviewTheme .channelPreviewTheme
.avatarTheme .avatarTheme
?.borderRadius, ?.borderRadius,
user: snapshot.data ?? otherMember.user!, user: snapshot.data ?? otherMember.user!,
constraints: constraints ?? constraints: constraints ??
StreamChatTheme.of(context) StreamChatTheme.of(context)
.channelPreviewTheme .channelPreviewTheme
.avatarTheme .avatarTheme
?.constraints, ?.constraints,
onTap: onTap != null ? (_) => onTap!() : null, onTap: onTap != null ? (_) => onTap!() : null,
selected: selected, selected: selected,
selectionColor: selectionColor ?? selectionColor: selectionColor ??
StreamChatTheme.of(context).colorTheme.accentBlue, StreamChatTheme.of(context).colorTheme.accentBlue,
selectionThickness: selectionThickness, selectionThickness: selectionThickness,
)); ));
} else { } else {
final images = channel.state?.members final images = channel.state?.members
.where((member) => .where((member) =>
@@ -167,16 +167,16 @@ class ChannelImage extends StatelessWidget {
CachedNetworkImage( CachedNetworkImage(
imageUrl: image, imageUrl: image,
errorWidget: (_, __, ___) => Center( errorWidget: (_, __, ___) => Center(
child: Text( child: Text(
snapshot.data?.containsKey('name') ?? false snapshot.data?.containsKey('name') ?? false
? snapshot.data!['name'][0] ? snapshot.data!['name'][0]
: '', : '',
style: TextStyle( style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.white, color: StreamChatTheme.of(context).colorTheme.white,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
),
), ),
), ),
),
fit: BoxFit.cover, fit: BoxFit.cover,
) )
else else
@@ -217,64 +217,65 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
); );
Widget _buildConnectingTitleState(BuildContext context) => Row( Widget _buildConnectingTitleState(BuildContext context) => Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const SizedBox( const SizedBox(
height: 16, height: 16,
width: 16, width: 16,
child: Center( child: Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
),
), ),
), const SizedBox(width: 10),
const SizedBox(width: 10), Text(
Text( 'Searching for Network',
'Searching for Network',
style: StreamChatTheme.of(context)
.channelListHeaderTheme
.title
?.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
],
);
Widget _buildDisconnectedTitleState(
BuildContext context,
StreamChatClient client,
) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Offline...',
style: StreamChatTheme.of(context)
.channelListHeaderTheme
.title
?.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
TextButton(
onPressed: () async {
await client.disconnect();
await client.connect();
},
child: Text(
'Try Again',
style: StreamChatTheme.of(context) style: StreamChatTheme.of(context)
.channelListHeaderTheme .channelListHeaderTheme
.title .title
?.copyWith( ?.copyWith(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: StreamChatTheme.of(context).colorTheme.accentBlue,
), ),
), ),
), ],
], );
);
Widget _buildDisconnectedTitleState(
BuildContext context,
StreamChatClient client,
) =>
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Offline...',
style: StreamChatTheme.of(context)
.channelListHeaderTheme
.title
?.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
TextButton(
onPressed: () async {
await client.disconnect();
await client.connect();
},
child: Text(
'Try Again',
style: StreamChatTheme.of(context)
.channelListHeaderTheme
.title
?.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
color: StreamChatTheme.of(context).colorTheme.accentBlue,
),
),
),
],
);
@override @override
Size get preferredSize => const Size.fromHeight(kToolbarHeight); Size get preferredSize => const Size.fromHeight(kToolbarHeight);
@@ -9,7 +9,6 @@ import 'package:stream_chat_flutter/src/utils.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.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_core/stream_chat_flutter_core.dart';
/// Callback called when tapping on a channel /// Callback called when tapping on a channel
typedef ChannelTapCallback = void Function(Channel, Widget?); typedef ChannelTapCallback = void Function(Channel, Widget?);
@@ -3,7 +3,6 @@ import 'package:flutter/rendering.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.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_core/stream_chat_flutter_core.dart';
/// It shows the current [Channel] name using a [Text] widget. /// It shows the current [Channel] name using a [Text] widget.
/// ///
/// The widget uses a [StreamBuilder] to render the channel information /// The widget uses a [StreamBuilder] to render the channel information
@@ -60,9 +59,8 @@ class ChannelName extends StatelessWidget {
final exceedingMembers = final exceedingMembers =
otherMembers.length - currentMembers.length; otherMembers.length - currentMembers.length;
title = title = '${currentMembers.map((e) => e.user?.name).join(', ')} '
'${currentMembers.map((e) => e.user?.name).join(', ')} ' '${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}';
'${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}';
} else { } else {
title = 'No title'; title = 'No title';
} }
@@ -21,7 +21,6 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// [StreamChatTheme]. /// [StreamChatTheme].
/// Modify it to change the widget appearance. /// Modify it to change the widget appearance.
class ChannelPreview extends StatelessWidget { class ChannelPreview extends StatelessWidget {
/// Constructor for creating [ChannelPreview] /// Constructor for creating [ChannelPreview]
const ChannelPreview({ const ChannelPreview({
required this.channel, required this.channel,
@@ -4,7 +4,6 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
/// It shows a date divider depending on the date difference /// It shows a date divider depending on the date difference
class DateDivider extends StatelessWidget { class DateDivider extends StatelessWidget {
/// Constructor for creating a [DateDivider] /// Constructor for creating a [DateDivider]
const DateDivider({ const DateDivider({
Key? key, Key? key,
@@ -5,7 +5,6 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
/// Widget to display deleted message /// Widget to display deleted message
class DeletedMessage extends StatelessWidget { class DeletedMessage extends StatelessWidget {
/// Constructor to create [DeletedMessage] /// Constructor to create [DeletedMessage]
const DeletedMessage({ const DeletedMessage({
Key? key, Key? key,
@@ -33,43 +32,43 @@ class DeletedMessage extends StatelessWidget {
@override @override
Widget build(BuildContext context) => Transform( Widget build(BuildContext context) => Transform(
transform: Matrix4.rotationY(reverse ? pi : 0), transform: Matrix4.rotationY(reverse ? pi : 0),
alignment: Alignment.center, alignment: Alignment.center,
child: Material( child: Material(
color: messageTheme.messageBackgroundColor, color: messageTheme.messageBackgroundColor,
shape: shape ?? shape: shape ??
RoundedRectangleBorder( RoundedRectangleBorder(
borderRadius: borderRadiusGeometry ?? BorderRadius.zero, borderRadius: borderRadiusGeometry ?? BorderRadius.zero,
side: borderSide ?? side: borderSide ??
BorderSide( BorderSide(
color: Theme.of(context).brightness == Brightness.dark color: Theme.of(context).brightness == Brightness.dark
? StreamChatTheme.of(context) ? StreamChatTheme.of(context)
.colorTheme .colorTheme
.white .white
.withAlpha(24) .withAlpha(24)
: StreamChatTheme.of(context) : StreamChatTheme.of(context)
.colorTheme .colorTheme
.black .black
.withAlpha(24), .withAlpha(24),
), ),
),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 16,
), ),
child: Padding( child: Transform(
padding: const EdgeInsets.symmetric( transform: Matrix4.rotationY(reverse ? pi : 0),
vertical: 8, alignment: Alignment.center,
horizontal: 16, child: Text(
), 'Message deleted',
child: Transform( style: messageTheme.messageText?.copyWith(
transform: Matrix4.rotationY(reverse ? pi : 0), fontStyle: FontStyle.italic,
alignment: Alignment.center, color: messageTheme.createdAt?.color,
child: Text( ),
'Message deleted',
style: messageTheme.messageText?.copyWith(
fontStyle: FontStyle.italic,
color: messageTheme.createdAt?.color,
), ),
), ),
), ),
), ),
), );
);
} }
@@ -13,7 +13,6 @@ import 'package:video_player/video_player.dart';
/// Return action for coming back from pages /// Return action for coming back from pages
enum ReturnActionType { enum ReturnActionType {
/// No return action /// No return action
none, none,
@@ -252,10 +251,10 @@ class _FullScreenMediaState extends State<FullScreenMedia>
class VideoPackage { class VideoPackage {
/// Constructor for creating [VideoPackage] /// Constructor for creating [VideoPackage]
VideoPackage( VideoPackage(
Attachment attachment, { Attachment attachment, {
bool showControls = false, bool showControls = false,
bool autoInitialize = true, bool autoInitialize = true,
}) : _showControls = showControls, }) : _showControls = showControls,
_autoInitialize = autoInitialize, _autoInitialize = autoInitialize,
_videoPlayerController = attachment.localUri != null _videoPlayerController = attachment.localUri != null
? VideoPlayerController.file(File.fromUri(attachment.localUri!)) ? VideoPlayerController.file(File.fromUri(attachment.localUri!))
@@ -12,7 +12,22 @@ import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.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_core/stream_chat_flutter_core.dart';
/// Footer widget for media display
class ImageFooter extends StatefulWidget implements PreferredSizeWidget { class ImageFooter extends StatefulWidget implements PreferredSizeWidget {
/// Creates a channel header
const ImageFooter({
Key? key,
required this.message,
this.onBackPressed,
this.onTitleTap,
this.onImageTap,
this.currentPage = 0,
this.totalPages = 0,
this.mediaAttachments = const [],
this.mediaSelectedCallBack,
}) : preferredSize = const Size.fromHeight(kToolbarHeight),
super(key: key);
/// Callback to call when pressing the back button. /// Callback to call when pressing the back button.
/// By default it calls [Navigator.pop] /// By default it calls [Navigator.pop]
final VoidCallback? onBackPressed; final VoidCallback? onBackPressed;
@@ -23,28 +38,21 @@ class ImageFooter extends StatefulWidget implements PreferredSizeWidget {
/// Callback to call when the image is tapped. /// Callback to call when the image is tapped.
final VoidCallback? onImageTap; final VoidCallback? onImageTap;
/// Stores the current index of media shown
final int currentPage; final int currentPage;
/// Total number of pages of media
final int totalPages; final int totalPages;
/// All attachments to show
final List<Attachment> mediaAttachments; final List<Attachment> mediaAttachments;
/// Message which attachments are attached to
final Message message; final Message message;
/// Callback when media is selected
final ValueChanged<int>? mediaSelectedCallBack; final ValueChanged<int>? mediaSelectedCallBack;
/// Creates a channel header
ImageFooter({
Key? key,
required this.message,
this.onBackPressed,
this.onTitleTap,
this.onImageTap,
this.currentPage = 0,
this.totalPages = 0,
this.mediaAttachments = const [],
this.mediaSelectedCallBack,
}) : preferredSize = Size.fromHeight(kToolbarHeight),
super(key: key);
@override @override
_ImageFooterState createState() => _ImageFooterState(); _ImageFooterState createState() => _ImageFooterState();
@@ -119,7 +127,7 @@ class _ImageFooterState extends State<ImageFooter> {
), ),
InkWell( InkWell(
onTap: widget.onTitleTap, onTap: widget.onTitleTap,
child: Container( child: SizedBox(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -169,115 +177,115 @@ class _ImageFooterState extends State<ImageFooter> {
expand: false, expand: false,
initialChildSize: initialChildSize / size.height, initialChildSize: initialChildSize / size.height,
minChildSize: initialChildSize / size.height, minChildSize: initialChildSize / size.height,
builder: (context, scrollController) { builder: (context, scrollController) => SingleChildScrollView(
return SingleChildScrollView( controller: scrollController,
controller: scrollController, child: Column(
child: Column( mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, children: [
children: [ Stack(
Stack( children: [
children: [ Center(
Center( child: Padding(
child: Padding( padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16), child: Text(
child: Text( 'Photos',
'Photos', style: StreamChatTheme.of(context)
style: StreamChatTheme.of(context) .textTheme
.textTheme .headlineBold,
.headlineBold,
),
), ),
), ),
Align( ),
alignment: Alignment.centerRight, Align(
child: IconButton( alignment: Alignment.centerRight,
icon: StreamSvgIcon.close( child: IconButton(
color: StreamChatTheme.of(context).colorTheme.black, icon: StreamSvgIcon.close(
), color: StreamChatTheme.of(context).colorTheme.black,
onPressed: () => Navigator.maybePop(context),
), ),
onPressed: () => Navigator.maybePop(context),
), ),
], ),
), ],
Flexible( ),
child: GridView.builder( Flexible(
shrinkWrap: true, child: GridView.builder(
physics: const NeverScrollableScrollPhysics(), shrinkWrap: true,
itemCount: widget.mediaAttachments.length, physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.all(1), itemCount: widget.mediaAttachments.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( padding: const EdgeInsets.all(1),
crossAxisCount: crossAxisCount, // ignore: lines_longer_than_80_chars
mainAxisSpacing: 2, gridDelegate:
crossAxisSpacing: 2, const SliverGridDelegateWithFixedCrossAxisCount(
), crossAxisCount: crossAxisCount,
itemBuilder: (context, index) { mainAxisSpacing: 2,
Widget media; crossAxisSpacing: 2,
final attachment = widget.mediaAttachments[index]; ),
if (attachment.type == 'video') { itemBuilder: (context, index) {
media = InkWell( Widget media;
onTap: () => widget.mediaSelectedCallBack!(index), final attachment = widget.mediaAttachments[index];
child: FittedBox( if (attachment.type == 'video') {
media = InkWell(
onTap: () => widget.mediaSelectedCallBack!(index),
child: FittedBox(
fit: BoxFit.cover,
child: VideoThumbnailImage(
video: (attachment.file?.path ??
attachment.assetUrl)!,
),
),
);
} else {
media = InkWell(
onTap: () => widget.mediaSelectedCallBack!(index),
child: AspectRatio(
aspectRatio: 1,
child: CachedNetworkImage(
imageUrl: attachment.imageUrl ??
attachment.assetUrl ??
attachment.thumbUrl!,
fit: BoxFit.cover, fit: BoxFit.cover,
child: VideoThumbnailImage(
video: (attachment.file?.path ??
attachment.assetUrl)!,
),
), ),
); ),
} else { );
media = InkWell( }
onTap: () => widget.mediaSelectedCallBack!(index),
child: AspectRatio(
aspectRatio: 1,
child: CachedNetworkImage(
imageUrl: attachment.imageUrl ??
attachment.assetUrl ??
attachment.thumbUrl!,
fit: BoxFit.cover,
),
),
);
}
return Stack( return Stack(
children: [ children: [
media, media,
if (widget.message.user != null) if (widget.message.user != null)
Padding( Padding(
padding: EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Container( child: Container(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: Colors.white.withOpacity(0.6), color: Colors.white.withOpacity(0.6),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
blurRadius: 8, blurRadius: 8,
color: StreamChatTheme.of(context) color: StreamChatTheme.of(context)
.colorTheme .colorTheme
.black .black
.withOpacity(0.3), .withOpacity(0.3),
), ),
], ],
), ),
padding: const EdgeInsets.all(2), padding: const EdgeInsets.all(2),
child: UserAvatar( child: UserAvatar(
user: widget.message.user!, user: widget.message.user!,
constraints: constraints:
BoxConstraints.tight(Size(24, 24)), BoxConstraints.tight(const Size(24, 24)),
showOnlineStatus: false, showOnlineStatus: false,
),
), ),
), ),
], ),
); ],
}, );
), },
), ),
], ),
), ],
); ),
}, ),
); );
}, },
); );
@@ -304,29 +312,3 @@ class _ImageFooterState extends State<ImageFooter> {
Navigator.pop(context); Navigator.pop(context);
} }
} }
/// Used for clipping textfield prefix icon
class IconClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final leftX = size.width / 5;
final rightX = 4 * size.width / 5;
final topY = size.height / 5;
final bottomY = 4 * size.height / 5;
final path = Path();
path.moveTo(leftX, topY);
path.lineTo(leftX, bottomY);
path.lineTo(rightX, bottomY);
path.lineTo(rightX, topY);
path.lineTo(leftX, topY);
path.lineTo(0, 0);
path.close();
return path;
}
@override
bool shouldReclip(CustomClipper oldClipper) {
return false;
}
}
@@ -3,7 +3,9 @@ import 'package:stream_chat_flutter/src/full_screen_media.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.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_core/stream_chat_flutter_core.dart';
/// Widget for constructing a group of images in message
class ImageGroup extends StatelessWidget { class ImageGroup extends StatelessWidget {
/// Constructor for creating [ImageGroup] widget
const ImageGroup({ const ImageGroup({
Key? key, Key? key,
required this.images, required this.images,
@@ -13,91 +15,98 @@ class ImageGroup extends StatelessWidget {
this.onShowMessage, this.onShowMessage,
}) : super(key: key); }) : super(key: key);
/// List of attachments to show
final List<Attachment> images; final List<Attachment> images;
/// Message which images are attached to
final Message message; final Message message;
/// [MessageTheme] to apply to message
final MessageTheme messageTheme; final MessageTheme messageTheme;
/// Size of iamges
final Size size; final Size size;
/// Callback for when show message is tapped
final ShowMessageCallback? onShowMessage; final ShowMessageCallback? onShowMessage;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => ConstrainedBox(
return ConstrainedBox( constraints: BoxConstraints.loose(size),
constraints: BoxConstraints.loose(size), child: Flex(
child: Flex( direction: Axis.vertical,
direction: Axis.vertical, children: <Widget>[
children: <Widget>[
Flexible(
fit: FlexFit.tight,
child: Flex(
crossAxisAlignment: CrossAxisAlignment.stretch,
direction: Axis.horizontal,
children: [
Flexible(
fit: FlexFit.tight,
child: _buildImage(context, 0),
),
Flexible(
fit: FlexFit.tight,
child: Padding(
padding: const EdgeInsets.only(left: 2),
child: _buildImage(context, 1),
),
),
],
),
),
if (images.length >= 3)
Flexible( Flexible(
fit: FlexFit.tight, fit: FlexFit.tight,
child: Padding( child: Flex(
padding: const EdgeInsets.only(top: 2), crossAxisAlignment: CrossAxisAlignment.stretch,
child: Flex( direction: Axis.horizontal,
direction: Axis.horizontal, children: [
crossAxisAlignment: CrossAxisAlignment.stretch, Flexible(
children: <Widget>[ fit: FlexFit.tight,
Flexible( child: _buildImage(context, 0),
fit: FlexFit.tight, ),
child: _buildImage(context, 2), Flexible(
fit: FlexFit.tight,
child: Padding(
padding: const EdgeInsets.only(left: 2),
child: _buildImage(context, 1),
), ),
if (images.length >= 4) ),
],
),
),
if (images.length >= 3)
Flexible(
fit: FlexFit.tight,
child: Padding(
padding: const EdgeInsets.only(top: 2),
child: Flex(
direction: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Flexible( Flexible(
fit: FlexFit.tight, fit: FlexFit.tight,
child: Padding( child: _buildImage(context, 2),
padding: const EdgeInsets.only(left: 2), ),
child: Stack( if (images.length >= 4)
fit: StackFit.expand, Flexible(
children: <Widget>[ fit: FlexFit.tight,
_buildImage(context, 3), child: Padding(
if (images.length > 4) padding: const EdgeInsets.only(left: 2),
Positioned.fill( child: Stack(
child: GestureDetector( fit: StackFit.expand,
onTap: () => _onTap(context, 3), children: <Widget>[
child: Material( _buildImage(context, 3),
color: Colors.black38, if (images.length > 4)
child: Center( Positioned.fill(
child: Text( child: GestureDetector(
'+ ${images.length - 4}', onTap: () => _onTap(context, 3),
style: TextStyle( child: Material(
color: Colors.white, color: Colors.black38,
fontSize: 26, child: Center(
child: Text(
'+ ${images.length - 4}',
style: const TextStyle(
color: Colors.white,
fontSize: 26,
),
), ),
), ),
), ),
), ),
), ),
), ],
], ),
), ),
), ),
), ],
], ),
), ),
), ),
), ],
], ),
), );
);
}
void _onTap( void _onTap(
BuildContext context, BuildContext context,
@@ -122,13 +131,11 @@ class ImageGroup extends StatelessWidget {
); );
} }
Widget _buildImage(BuildContext context, int index) { Widget _buildImage(BuildContext context, int index) => ImageAttachment(
return ImageAttachment( attachment: images[index],
attachment: images[index], size: size,
size: size, message: message,
message: message, messageTheme: messageTheme,
messageTheme: messageTheme, onAttachmentTap: () => _onTap(context, index),
onAttachmentTap: () => _onTap(context, index), );
);
}
} }
@@ -1,11 +1,26 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/attachment_actions_modal.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'attachment_actions_modal.dart'; /// Header/AppBar widget for media display screen
class ImageHeader extends StatelessWidget implements PreferredSizeWidget { class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
/// Creates a channel header
const ImageHeader({
Key? key,
required this.message,
this.currentIndex = 0,
this.showBackButton = true,
this.onBackPressed,
this.onShowMessage,
this.onTitleTap,
this.onImageTap,
this.userName = '',
this.sentAt = '',
}) : preferredSize = const Size.fromHeight(kToolbarHeight),
super(key: key);
/// True if this header shows the leading back button /// True if this header shows the leading back button
final bool showBackButton; final bool showBackButton;
@@ -22,83 +37,72 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
/// Callback to call when the image is tapped. /// Callback to call when the image is tapped.
final VoidCallback? onImageTap; final VoidCallback? onImageTap;
/// Message which attachments are attached to
final Message message; final Message message;
/// Username of sender
final String userName; final String userName;
/// Text which connotes the time the message was sent
final String sentAt; final String sentAt;
/// Stores the current index of media shown
final int currentIndex; final int currentIndex;
/// Creates a channel header
ImageHeader({
Key? key,
required this.message,
this.currentIndex = 0,
this.showBackButton = true,
this.onBackPressed,
this.onShowMessage,
this.onTitleTap,
this.onImageTap,
this.userName = '',
this.sentAt = '',
}) : preferredSize = Size.fromHeight(kToolbarHeight),
super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => AppBar(
return AppBar( brightness: Theme.of(context).brightness,
brightness: Theme.of(context).brightness, elevation: 1,
elevation: 1, leading: showBackButton
leading: showBackButton ? IconButton(
? IconButton( icon: StreamSvgIcon.close(
icon: StreamSvgIcon.close( color: StreamChatTheme.of(context).colorTheme.black,
color: StreamChatTheme.of(context).colorTheme.black, size: 24,
size: 24,
),
onPressed: onBackPressed,
)
: SizedBox(),
backgroundColor:
StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color,
actions: <Widget>[
if (message.type != 'ephemeral')
IconButton(
icon: StreamSvgIcon.iconMenuPoint(
color: StreamChatTheme.of(context).colorTheme.black,
),
onPressed: () {
_showMessageActionModalBottomSheet(context);
},
),
],
centerTitle: true,
title: message.type != 'ephemeral'
? InkWell(
onTap: onTitleTap,
child: Container(
height: preferredSize.height,
width: preferredSize.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
userName,
style: StreamChatTheme.of(context).textTheme.headlineBold,
),
Text(
sentAt,
style: StreamChatTheme.of(context)
.channelPreviewTheme
.subtitle,
),
],
), ),
onPressed: onBackPressed,
)
: const SizedBox(),
backgroundColor:
StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color,
actions: <Widget>[
if (message.type != 'ephemeral')
IconButton(
icon: StreamSvgIcon.iconMenuPoint(
color: StreamChatTheme.of(context).colorTheme.black,
), ),
) onPressed: () {
: SizedBox(), _showMessageActionModalBottomSheet(context);
); },
} ),
],
centerTitle: true,
title: message.type != 'ephemeral'
? InkWell(
onTap: onTitleTap,
child: SizedBox(
height: preferredSize.height,
width: preferredSize.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
userName,
style:
StreamChatTheme.of(context).textTheme.headlineBold,
),
Text(
sentAt,
style: StreamChatTheme.of(context)
.channelPreviewTheme
.subtitle,
),
],
),
),
)
: const SizedBox(),
);
@override @override
final Size preferredSize; final Size preferredSize;
@@ -109,16 +113,14 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
final result = await showDialog( final result = await showDialog(
context: context, context: context,
barrierColor: StreamChatTheme.of(context).colorTheme.overlay, barrierColor: StreamChatTheme.of(context).colorTheme.overlay,
builder: (context) { builder: (context) => StreamChannel(
return StreamChannel( channel: channel,
channel: channel, child: AttachmentActionsModal(
child: AttachmentActionsModal( message: message,
message: message, currentIndex: currentIndex,
currentIndex: currentIndex, onShowMessage: onShowMessage,
onShowMessage: onShowMessage, ),
), ),
);
},
); );
if (result != null) { if (result != null) {
@@ -812,116 +812,123 @@ class MessageInputState extends State<MessageInput> {
final renderBox = context.findRenderObject() as RenderBox; final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size; final size = renderBox.size;
return OverlayEntry(builder: (context) => Positioned( return OverlayEntry(
bottom: size.height + MediaQuery.of(context).viewInsets.bottom, builder: (context) => Positioned(
left: 0, bottom: size.height + MediaQuery.of(context).viewInsets.bottom,
right: 0, left: 0,
child: TweenAnimationBuilder<double>( right: 0,
tween: Tween(begin: 0, end: 1), child: TweenAnimationBuilder<double>(
duration: Duration(milliseconds: 300), tween: Tween(begin: 0, end: 1),
curve: Curves.easeInOutExpo, duration: Duration(milliseconds: 300),
builder: (context, val, wid) => Transform.scale( curve: Curves.easeInOutExpo,
scale: val, builder: (context, val, wid) => Transform.scale(
child: Padding( scale: val,
padding: const EdgeInsets.all(8), child: Padding(
child: Card( padding: const EdgeInsets.all(8),
elevation: 2, child: Card(
shape: RoundedRectangleBorder( elevation: 2,
borderRadius: BorderRadius.circular(8), shape: RoundedRectangleBorder(
), borderRadius: BorderRadius.circular(8),
color: StreamChatTheme.of(context).colorTheme.white, ),
clipBehavior: Clip.antiAlias, color: StreamChatTheme.of(context).colorTheme.white,
child: Container( clipBehavior: Clip.antiAlias,
constraints: BoxConstraints.loose(Size.fromHeight(400)), child: Container(
decoration: BoxDecoration( constraints:
color: StreamChatTheme.of(context).colorTheme.white, BoxConstraints.loose(Size.fromHeight(400)),
borderRadius: BorderRadius.circular(8)), decoration: BoxDecoration(
child: ListView( color: StreamChatTheme.of(context)
padding: const EdgeInsets.all(0), .colorTheme
shrinkWrap: true, .white,
children: [ borderRadius: BorderRadius.circular(8)),
if (commands.isNotEmpty) child: ListView(
Padding( padding: const EdgeInsets.all(0),
padding: const EdgeInsets.only(top: 8), shrinkWrap: true,
child: Row(
children: [ children: [
Padding( if (commands.isNotEmpty)
padding: const EdgeInsets.symmetric( Padding(
horizontal: 8, padding: const EdgeInsets.only(top: 8),
), child: Row(
child: StreamSvgIcon.lightning( children: [
color: StreamChatTheme.of(context) Padding(
.colorTheme padding: const EdgeInsets.symmetric(
.accentBlue, horizontal: 8,
),
child: StreamSvgIcon.lightning(
color: StreamChatTheme.of(context)
.colorTheme
.accentBlue,
),
),
Text(
'Instant Commands',
style: TextStyle(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5),
),
)
],
),
), ),
const SizedBox(
height: 10,
), ),
Text( ...commands
'Instant Commands', .map(
style: TextStyle( (c) => InkWell(
color: StreamChatTheme.of(context) onTap: () {
.colorTheme _setCommand(c);
.black },
.withOpacity(.5), child: SizedBox(
), height: 40,
) child: Row(
children: [
const SizedBox(
width: 16,
),
_buildCommandIcon(c.name),
const SizedBox(
width: 8,
),
Text.rich(
TextSpan(
text: c.name.capitalize(),
style: const TextStyle(
fontWeight:
FontWeight.bold),
children: [
TextSpan(
text:
' /${c.name} ${c.args}',
style:
StreamChatTheme.of(
context)
.textTheme
.body
.copyWith(
color: StreamChatTheme.of(
context)
.colorTheme
.grey,
),
),
],
),
),
],
),
),
),
)
.toList(),
], ],
), ),
), ),
const SizedBox(
height: 10,
), ),
...commands ),
.map( )),
(c) => InkWell( ));
onTap: () {
_setCommand(c);
},
child: SizedBox(
height: 40,
child: Row(
children: [
const SizedBox(
width: 16,
),
_buildCommandIcon(c.name),
const SizedBox(
width: 8,
),
Text.rich(
TextSpan(
text: c.name.capitalize(),
style: const TextStyle(
fontWeight: FontWeight.bold),
children: [
TextSpan(
text: ' /${c.name} ${c.args}',
style:
StreamChatTheme.of(context)
.textTheme
.body
.copyWith(
color: StreamChatTheme
.of(context)
.colorTheme
.grey,
),
),
],
),
),
],
),
),
),
)
.toList(),
],
),
),
),
),
)),
));
} }
Widget _buildFilePickerSection() { Widget _buildFilePickerSection() {