From 21e798b1344748da577ee8f3720c38b0ee0af68f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ya=C3=ABl=20Perret?= Date: Thu, 14 May 2026 17:23:14 +0200 Subject: [PATCH] feat : Widget commentaire --- lib/app/app.router.dart | 8 - lib/models/animator.dart | 20 +++ lib/models/animator.g.dart | 19 +++ lib/models/comment.dart | 23 +++ lib/models/comment.g.dart | 23 +++ lib/ui/common/comment_card_widget.dart | 161 ++++++++++++++++++ .../event_details_viewmodel.dart | 25 --- 7 files changed, 246 insertions(+), 33 deletions(-) create mode 100644 lib/models/animator.dart create mode 100644 lib/models/animator.g.dart create mode 100644 lib/models/comment.dart create mode 100644 lib/models/comment.g.dart create mode 100644 lib/ui/common/comment_card_widget.dart diff --git a/lib/app/app.router.dart b/lib/app/app.router.dart index ed75df9..ebbe82d 100644 --- a/lib/app/app.router.dart +++ b/lib/app/app.router.dart @@ -47,14 +47,6 @@ class StackedRouter extends _i1.RouterBase { Routes.mainView, page: _i4.MainView, ), - _i1.RouteDef( - Routes.homeView, - page: _i2.HomeView, - ), - _i1.RouteDef( - Routes.eventDetailsView, - page: _i5.EventDetailsView, - ), _i1.RouteDef( Routes.eventDetailsView, page: _i5.EventDetailsView, diff --git a/lib/models/animator.dart b/lib/models/animator.dart new file mode 100644 index 0000000..b254b0f --- /dev/null +++ b/lib/models/animator.dart @@ -0,0 +1,20 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'animator.g.dart'; + +@JsonSerializable() +class Animator { + final String id; + final String name; + final String content; + final String authorImageUrl = 'https://placehold.co/400x400/png'; + + Animator({ + required this.id, + required this.name, + required this.content, + }); + + factory Animator.fromJson(Map json) => _$AnimatorFromJson(json); + Map toJson() => _$AnimatorToJson(this); +} \ No newline at end of file diff --git a/lib/models/animator.g.dart b/lib/models/animator.g.dart new file mode 100644 index 0000000..ee58858 --- /dev/null +++ b/lib/models/animator.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'animator.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Animator _$AnimatorFromJson(Map json) => Animator( + id: json['id'] as String, + name: json['name'] as String, + content: json['content'] as String, + ); + +Map _$AnimatorToJson(Animator instance) => { + 'id': instance.id, + 'name': instance.name, + 'content': instance.content, + }; diff --git a/lib/models/comment.dart b/lib/models/comment.dart new file mode 100644 index 0000000..855eeab --- /dev/null +++ b/lib/models/comment.dart @@ -0,0 +1,23 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'comment.g.dart'; + +@JsonSerializable() +class Comment { + final String id; + final String postId; + final String content; + final String authorName; + final String authorImageUrl; + + Comment({ + required this.id, + required this.postId, + required this.content, + required this.authorName, + required this.authorImageUrl, + }); + + factory Comment.fromJson(Map json) => _$CommentFromJson(json); + Map toJson() => _$CommentToJson(this); +} \ No newline at end of file diff --git a/lib/models/comment.g.dart b/lib/models/comment.g.dart new file mode 100644 index 0000000..e18457d --- /dev/null +++ b/lib/models/comment.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'comment.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Comment _$CommentFromJson(Map json) => Comment( + id: json['id'] as String, + postId: json['postId'] as String, + content: json['content'] as String, + authorName: json['authorName'] as String, + authorImageUrl: json['authorImageUrl'] as String, + ); + +Map _$CommentToJson(Comment instance) => { + 'id': instance.id, + 'postId': instance.postId, + 'content': instance.content, + 'authorName': instance.authorName, + 'authorImageUrl': instance.authorImageUrl, + }; diff --git a/lib/ui/common/comment_card_widget.dart b/lib/ui/common/comment_card_widget.dart new file mode 100644 index 0000000..c1d0eeb --- /dev/null +++ b/lib/ui/common/comment_card_widget.dart @@ -0,0 +1,161 @@ +import 'package:flutter/material.dart'; + +class CommentCardWidget extends StatelessWidget { + final String comment; + final String authorName; + final String authorImageUrl; + final DateTime publishDate; + final int likesCount; + final int otherRepliesCount; + final bool isLiked; + final VoidCallback? onLike; + final VoidCallback? onReply; + final VoidCallback? onViewOtherReplies; + + const CommentCardWidget({ + super.key, + required this.comment, + required this.authorName, + required this.authorImageUrl, + required this.publishDate, + this.likesCount = 0, + this.otherRepliesCount = 0, + this.isLiked = false, + this.onLike, + this.onReply, + this.onViewOtherReplies, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CircleAvatar( + radius: 18, + backgroundImage: + authorImageUrl.isNotEmpty ? NetworkImage(authorImageUrl) : null, + onBackgroundImageError: (_, __) {}, + child: authorImageUrl.isEmpty + ? const Icon(Icons.person, size: 18) + : null, + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Flexible( + child: Text( + _firstName(authorName), + overflow: TextOverflow.ellipsis, + style: + Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + const SizedBox(width: 8), + Text( + _formatDate(publishDate), + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.grey, + ), + ), + ], + ), + const SizedBox(height: 6), + Text( + comment, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.white, + height: 1.35, + ), + ), + const SizedBox(height: 6), + Row( + children: [ + TextButton.icon( + onPressed: onLike, + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 8), + minimumSize: const Size(0, 32), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + icon: Icon( + isLiked ? Icons.favorite : Icons.favorite_outline, + size: 18, + color: isLiked ? Colors.redAccent : Colors.grey, + ), + label: Text( + likesCount > 0 ? '$likesCount' : 'Like', + style: const TextStyle(color: Colors.grey), + ), + ), + const SizedBox(width: 4), + TextButton( + onPressed: onReply, + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 8), + minimumSize: const Size(0, 32), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + child: const Text( + 'Répondre', + style: TextStyle(color: Colors.grey), + ), + ), + ], + ), + if (otherRepliesCount > 0) + TextButton( + onPressed: onViewOtherReplies, + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + minimumSize: const Size(0, 28), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + child: Text( + 'Voir $otherRepliesCount autres réponses', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.blueGrey[200], + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + ], + ), + ); + } + + String _firstName(String fullName) { + final trimmed = fullName.trim(); + if (trimmed.isEmpty) return ''; + return trimmed.split(RegExp(r'\s+')).first; + } + + String _formatDate(DateTime date) { + final now = DateTime.now(); + final difference = now.difference(date); + + if (difference.inDays > 7) { + return '${date.day}/${date.month}/${date.year}'; + } else if (difference.inDays > 0) { + return '${difference.inDays} jour${difference.inDays > 1 ? 's' : ''}'; + } else if (difference.inHours > 0) { + return '${difference.inHours}h'; + } else if (difference.inMinutes > 0) { + return '${difference.inMinutes}min'; + } else { + return 'À l\'instant'; + } + } +} diff --git a/lib/ui/views/event_details/event_details_viewmodel.dart b/lib/ui/views/event_details/event_details_viewmodel.dart index b6d9313..63f181b 100644 --- a/lib/ui/views/event_details/event_details_viewmodel.dart +++ b/lib/ui/views/event_details/event_details_viewmodel.dart @@ -20,29 +20,4 @@ class EventDetailsViewModel extends BaseViewModel { _navigationService.back(); } -} - -class Post { - final String id; - final String title; - final String content; - - Post({ - required this.id, - required this.title, - required this.content, - }); -} - -class Animator { - final String id; - final String name; - final String content; - final String authorImageUrl = 'https://placehold.co/400x400/png'; - - Animator({ - required this.id, - required this.name, - required this.content, - }); } \ No newline at end of file