Skip to content

Commit

Permalink
feat(UX-1314): Created comment component (#41)
Browse files Browse the repository at this point in the history
* feat(UX-1314): Created comment component

* chore(automated): Lint commit and format

* added inkewell padding and fixed custom thumbnail size

---------

Co-authored-by: github-actions <github-actions@github.com>
  • Loading branch information
mikecoomber and github-actions authored Nov 21, 2024
1 parent fa597fd commit 30395b0
Show file tree
Hide file tree
Showing 6 changed files with 382 additions and 7 deletions.
68 changes: 68 additions & 0 deletions example/lib/pages/components/comment.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:zds_flutter/zds_flutter.dart';

class CommentDemo extends StatefulWidget {
const CommentDemo({super.key});

@override
State<CommentDemo> createState() => _CommentDemoState();
}

class _CommentDemoState extends State<CommentDemo> {
@override
Widget build(BuildContext context) {
return Container(
color: Zeta.of(context).colors.surfaceDefault,
child: Column(
children: [
ZdsComment(
avatar: ZetaAvatar.initials(
initials: 'JP',
size: ZetaAvatarSize.xxxs,
),
author: 'John Doe',
downloadCallback: () {},
comment: 'This is a comment',
onReply: () {},
replySemanticLabel: 'Reply to comment',
onDelete: () {},
deleteSemanticLabel: 'Delete',
timeStamp: '09:30 AM',
attachment: ZdsChatAttachment(
type: ZdsChatAttachmentType.docNetwork,
name: 'Blueprints.xls',
size: '1234kb',
extension: 'xls',
),
),
ZdsComment(
avatar: ZetaAvatar.initials(
initials: 'JP',
size: ZetaAvatarSize.xxxs,
backgroundColor: Zeta.of(context).colors.surfaceAvatarPurple,
),
onDelete: () {},
deleteSemanticLabel: 'Delete',
isReply: true,
author: 'John Doe',
comment: 'This is a comment',
timeStamp: '09:30 AM',
),
ZdsComment(
avatar: ZetaAvatar.initials(
initials: 'JP',
size: ZetaAvatarSize.xxxs,
),
author: 'John Doe',
comment: 'This is a comment',
onReply: () {},
replySemanticLabel: 'Reply to comment',
onDelete: () {},
deleteSemanticLabel: 'Delete',
timeStamp: '09:30 AM',
),
],
),
);
}
}
10 changes: 10 additions & 0 deletions example/lib/routes.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import 'package:flutter/material.dart';
import 'package:zds_flutter_example/pages/components/chat.dart';
import 'package:zds_flutter_example/pages/components/comment.dart';

import 'home.dart';
import 'pages/assets/animations.dart';
Expand Down Expand Up @@ -92,6 +94,14 @@ final kRoutes = {
title: 'Card Actions',
child: CardActionsDemo(),
),
const DemoRoute(
title: 'Chat',
child: ChatDemo(),
),
const DemoRoute(
title: 'Comments',
child: CommentDemo(),
),
const DemoRoute(
title: 'Interactive Viewer',
wrapper: false,
Expand Down
1 change: 1 addition & 0 deletions lib/src/components/molecules.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export 'molecules/bottom_sheet.dart';
export 'molecules/card_actions.dart';
export 'molecules/card_header.dart';
export 'molecules/check_button.dart';
export 'molecules/comment.dart';
export 'molecules/date_range_picker.dart';
export 'molecules/date_time_picker.dart';
export 'molecules/dropdown.dart';
Expand Down
278 changes: 278 additions & 0 deletions lib/src/components/molecules/comment.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

import '../../../zds_flutter.dart';

/// Displays a comment with an optional attachment and delete and reply swipeable actions.
class ZdsComment extends StatelessWidget {
/// Constructs a [ZdsComment] widget.
const ZdsComment({
required this.comment,
required this.author,
this.isReply = false,
this.avatar,
this.timeStamp,
this.onDelete,
this.onReply,
super.key,
this.attachment,
this.downloadCallback,
this.deleteSemanticLabel,
this.replySemanticLabel,
this.attachmentThumbnail,
}) : assert(
onReply != null && replySemanticLabel != null || onReply == null && replySemanticLabel == null,
'replySemanticLabel must be not null if onReply is defined',
),
assert(
onDelete != null && deleteSemanticLabel != null || onDelete == null && deleteSemanticLabel == null,
'deleteSemanticLabel must be not null if onDelete is defined',
);

/// The comment text.
final String comment;

/// The avatar widget to display.
/// Should be a [ZetaAvatar]
final Widget? avatar;

/// The timestamp of the comment.
final String? timeStamp;

/// The author of the comment.
final String author;

/// Whether the comment is a reply.
/// If this is true, the reply action will automatically be hidden.
final bool isReply;

/// The callback to be called when the delete action is tapped.
/// If this is null, the delete action will be hidden.
/// If this is not null, [deleteSemanticLabel] must also be not null.
final VoidCallback? onDelete;

/// The semantic label for the delete action.
final String? deleteSemanticLabel;

/// The callback to be called when the reply action is tapped.
/// If this is null, the reply action will be hidden.
/// If this is not null, [replySemanticLabel] must also be not null.
final VoidCallback? onReply;

/// The semantic label for the reply action.
final String? replySemanticLabel;

/// The attachment to display.
final ZdsChatAttachment? attachment;

/// The callback to be called when the attachment is tapped.
final VoidCallback? downloadCallback;

/// The custom thumbnail to display for the attachment.
final Widget? attachmentThumbnail;

@override
Widget build(BuildContext context) {
final colors = Zeta.of(context).colors;
final spacing = Zeta.of(context).spacing;

return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (isReply)
Padding(
padding: EdgeInsets.only(
left: spacing.large,
right: spacing.minimum,
top: spacing.minimum,
),
child: const ZetaIcon(
ZetaIcons.reply,
size: 24,
applyTextScaling: true,
),
),
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
return ZdsSlidableListTile(
width: constraints.maxWidth,
elevation: 0,
actions: [
if (!isReply && onReply != null && replySemanticLabel != null)
ZdsSlidableAction(
icon: ZetaIcons.reply,
semanticLabel: replySemanticLabel,
foregroundColor: colors.primary,
backgroundColor: colors.surfacePrimarySubtle,
onPressed: (_) => onReply!(),
),
if (onDelete != null && deleteSemanticLabel != null)
ZdsSlidableAction(
icon: ZetaIcons.delete,
semanticLabel: deleteSemanticLabel,
onPressed: (_) {},
backgroundColor: colors.surfaceNegativeSubtle,
foregroundColor: colors.error,
),
],
child: Container(
decoration: BoxDecoration(
color: colors.surfaceDefault,
border: Border(
bottom: BorderSide(
color: colors.borderSubtle,
),
),
),
padding: EdgeInsets.symmetric(
vertical: spacing.large,
horizontal: spacing.medium,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: spacing.minimum),
child: Row(
children: [
if (avatar != null)
Padding(
padding: EdgeInsets.only(right: spacing.small),
child: avatar,
),
Text(
author,
style: ZetaTextStyles.labelLarge.copyWith(
fontWeight: FontWeight.w500,
),
),
const Spacer(),
if (timeStamp != null)
Padding(
padding: EdgeInsets.only(left: spacing.small),
child: Text(
timeStamp!,
style: ZetaTextStyles.bodyXSmall.copyWith(color: colors.textSubtle),
),
),
],
),
),
Padding(
padding: EdgeInsets.only(
top: spacing.small,
left: spacing.minimum,
right: spacing.minimum,
),
child: Text(
comment,
style: Theme.of(context).textTheme.bodyMedium,
),
),
if (attachment != null)
Padding(
padding: EdgeInsets.only(top: spacing.medium),
child: _AttachmentRow(
attachment: attachment!,
downloadCallback: downloadCallback,
customThumbnail: attachmentThumbnail,
),
),
],
),
),
);
},
),
),
],
);
}

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(StringProperty('comment', comment))
..add(StringProperty('timeStamp', timeStamp))
..add(StringProperty('author', author))
..add(DiagnosticsProperty<bool>('isReply', isReply))
..add(ObjectFlagProperty<VoidCallback?>.has('onDelete', onDelete))
..add(ObjectFlagProperty<VoidCallback?>.has('onReply', onReply))
..add(DiagnosticsProperty<ZdsChatAttachment?>('attachment', attachment))
..add(ObjectFlagProperty<VoidCallback?>.has('downloadCallback', downloadCallback))
..add(StringProperty('deleteSemanticLabel', deleteSemanticLabel))
..add(StringProperty('replySemanticLabel', replySemanticLabel));
}
}

class _AttachmentRow extends StatelessWidget {
const _AttachmentRow({
required this.attachment,
this.customThumbnail,
this.downloadCallback,
});

final ZdsChatAttachment attachment;
final VoidCallback? downloadCallback;
final Widget? customThumbnail;

@override
Widget build(BuildContext context) {
final spacing = Zeta.of(context).spacing;
final colors = Zeta.of(context).colors;
final radius = Zeta.of(context).radius;

return Material(
child: InkWell(
borderRadius: radius.minimal,
onTap: downloadCallback,
child: Padding(
padding: EdgeInsets.all(spacing.minimum),
child: Row(
children: [
if (customThumbnail != null)
SizedBox(
width: 40,
height: 40,
child: customThumbnail,
)
else
ZetaIcon(
extensionIcon('.${attachment.fileType}'),
color: iconColor('.${attachment.fileType}'),
size: 40,
),
SizedBox(width: spacing.small),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
attachment.name,
style: ZetaTextStyles.bodySmall,
),
if (attachment.size != null)
Text(
attachment.size!,
style: ZetaTextStyles.bodySmall.copyWith(color: colors.textSubtle),
),
],
),
],
),
),
),
);
}

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty<ZdsChatAttachment>('attachment', attachment))
..add(ObjectFlagProperty<VoidCallback?>.has('downloadCallback', downloadCallback));
}
}
Loading

0 comments on commit 30395b0

Please sign in to comment.