feat : Widget commentaire

This commit is contained in:
Yaël Perret
2026-05-14 17:23:14 +02:00
parent a812a70b1d
commit 21e798b134
7 changed files with 246 additions and 33 deletions

View File

@@ -47,14 +47,6 @@ class StackedRouter extends _i1.RouterBase {
Routes.mainView, Routes.mainView,
page: _i4.MainView, page: _i4.MainView,
), ),
_i1.RouteDef(
Routes.homeView,
page: _i2.HomeView,
),
_i1.RouteDef(
Routes.eventDetailsView,
page: _i5.EventDetailsView,
),
_i1.RouteDef( _i1.RouteDef(
Routes.eventDetailsView, Routes.eventDetailsView,
page: _i5.EventDetailsView, page: _i5.EventDetailsView,

20
lib/models/animator.dart Normal file
View File

@@ -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<String, dynamic> json) => _$AnimatorFromJson(json);
Map<String, dynamic> toJson() => _$AnimatorToJson(this);
}

View File

@@ -0,0 +1,19 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'animator.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Animator _$AnimatorFromJson(Map<String, dynamic> json) => Animator(
id: json['id'] as String,
name: json['name'] as String,
content: json['content'] as String,
);
Map<String, dynamic> _$AnimatorToJson(Animator instance) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
'content': instance.content,
};

23
lib/models/comment.dart Normal file
View File

@@ -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<String, dynamic> json) => _$CommentFromJson(json);
Map<String, dynamic> toJson() => _$CommentToJson(this);
}

23
lib/models/comment.g.dart Normal file
View File

@@ -0,0 +1,23 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'comment.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Comment _$CommentFromJson(Map<String, dynamic> 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<String, dynamic> _$CommentToJson(Comment instance) => <String, dynamic>{
'id': instance.id,
'postId': instance.postId,
'content': instance.content,
'authorName': instance.authorName,
'authorImageUrl': instance.authorImageUrl,
};

View File

@@ -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';
}
}
}

View File

@@ -20,29 +20,4 @@ class EventDetailsViewModel extends BaseViewModel {
_navigationService.back(); _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,
});
} }