feat : post respect ratio and height
This commit is contained in:
478
lib/ui/common/post_card_widget copy.dart
Normal file
478
lib/ui/common/post_card_widget copy.dart
Normal file
@@ -0,0 +1,478 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class PostCardWidget extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final String content;
|
||||||
|
final String authorName;
|
||||||
|
final String authorImageUrl;
|
||||||
|
final DateTime publishDate;
|
||||||
|
final List<String>? imageUrls;
|
||||||
|
final int likesCount;
|
||||||
|
final int commentsCount;
|
||||||
|
final int sharesCount;
|
||||||
|
final VoidCallback? onLike;
|
||||||
|
final VoidCallback? onComment;
|
||||||
|
final VoidCallback? onShare;
|
||||||
|
|
||||||
|
const PostCardWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.title,
|
||||||
|
required this.content,
|
||||||
|
required this.authorName,
|
||||||
|
required this.authorImageUrl,
|
||||||
|
required this.publishDate,
|
||||||
|
this.imageUrls,
|
||||||
|
this.likesCount = 0,
|
||||||
|
this.commentsCount = 0,
|
||||||
|
this.sharesCount = 0,
|
||||||
|
this.onLike,
|
||||||
|
this.onComment,
|
||||||
|
this.onShare,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 16.0),
|
||||||
|
//elevation: 2,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// En-tête avec auteur et date
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Photo de profil de l'auteur
|
||||||
|
CircleAvatar(
|
||||||
|
radius: 20,
|
||||||
|
backgroundImage: NetworkImage(authorImageUrl),
|
||||||
|
onBackgroundImageError: (_, __) {},
|
||||||
|
child: authorImageUrl.isEmpty
|
||||||
|
? const Icon(Icons.person, size: 20)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
// Nom et date
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
authorName,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
_formatDate(publishDate),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Menu options
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.more_vert, color: Colors.grey),
|
||||||
|
onPressed: () {
|
||||||
|
// Action pour le menu
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Contenu du post
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Contenu
|
||||||
|
Text(
|
||||||
|
content,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Colors.white,
|
||||||
|
height: 1.4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Images avec boutons d'action si présentes
|
||||||
|
if (imageUrls != null && imageUrls!.isNotEmpty)
|
||||||
|
Container(
|
||||||
|
//margin: const EdgeInsets.symmetric(vertical: 12.0),
|
||||||
|
height: 200,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// Images
|
||||||
|
PageView.builder(
|
||||||
|
itemCount: imageUrls!.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return Container(
|
||||||
|
//margin: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(0),
|
||||||
|
topRight: Radius.circular(0),
|
||||||
|
bottomLeft: Radius.circular(8),
|
||||||
|
bottomRight: Radius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(0),
|
||||||
|
topRight: Radius.circular(0),
|
||||||
|
bottomLeft: Radius.circular(8),
|
||||||
|
bottomRight: Radius.circular(8),
|
||||||
|
),
|
||||||
|
child: _buildImage(imageUrls![index]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// Boutons d'action en overlay à droite
|
||||||
|
Positioned(
|
||||||
|
right: 24,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
_VerticalActionButton(
|
||||||
|
icon: Icons.favorite_outline,
|
||||||
|
count: likesCount,
|
||||||
|
onPressed: onLike,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_VerticalActionButton(
|
||||||
|
icon: Icons.comment_outlined,
|
||||||
|
count: commentsCount,
|
||||||
|
onPressed: onComment,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_VerticalActionButton(
|
||||||
|
icon: Icons.share_outlined,
|
||||||
|
count: sharesCount,
|
||||||
|
onPressed: onShare,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Si pas d'image, boutons d'action en bas à droite
|
||||||
|
if (imageUrls == null || imageUrls!.isEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 16.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_HorizontalActionButton(
|
||||||
|
icon: Icons.favorite_outline,
|
||||||
|
count: likesCount,
|
||||||
|
onPressed: onLike,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
_HorizontalActionButton(
|
||||||
|
icon: Icons.comment_outlined,
|
||||||
|
count: commentsCount,
|
||||||
|
onPressed: onComment,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
_HorizontalActionButton(
|
||||||
|
icon: Icons.share_outlined,
|
||||||
|
count: sharesCount,
|
||||||
|
onPressed: onShare,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildImage(String imageUrl) {
|
||||||
|
// Vérifie si c'est une image d'assets ou une URL réseau
|
||||||
|
if (imageUrl.startsWith('assets/')) {
|
||||||
|
return Image.asset(
|
||||||
|
imageUrl,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
print('Erreur de chargement d\'asset: $error');
|
||||||
|
return _buildErrorWidget();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Image.network(
|
||||||
|
imageUrl,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
loadingBuilder: (context, child, loadingProgress) {
|
||||||
|
if (loadingProgress == null) return child;
|
||||||
|
return Container(
|
||||||
|
color: Colors.grey[300],
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
value: loadingProgress.expectedTotalBytes != null
|
||||||
|
? loadingProgress.cumulativeBytesLoaded /
|
||||||
|
loadingProgress.expectedTotalBytes!
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
print('Erreur de chargement d\'image réseau: $error');
|
||||||
|
return _buildErrorWidget();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildErrorWidget() {
|
||||||
|
return Container(
|
||||||
|
color: Colors.grey[300],
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.broken_image,
|
||||||
|
size: 50,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Image non disponible',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey[600],
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VerticalActionButton extends StatefulWidget {
|
||||||
|
final IconData icon;
|
||||||
|
final int? count;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
|
const _VerticalActionButton({
|
||||||
|
required this.icon,
|
||||||
|
this.count,
|
||||||
|
this.onPressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_VerticalActionButton> createState() => _VerticalActionButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VerticalActionButtonState extends State<_VerticalActionButton>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late AnimationController _animationController;
|
||||||
|
late Animation<double> _scaleAnimation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_animationController = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 150),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
_scaleAnimation = Tween<double>(
|
||||||
|
begin: 1.0,
|
||||||
|
end: 0.85,
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_animationController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleTap() async {
|
||||||
|
await _animationController.forward();
|
||||||
|
await _animationController.reverse();
|
||||||
|
widget.onPressed?.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: _handleTap,
|
||||||
|
child: AnimatedBuilder(
|
||||||
|
animation: _scaleAnimation,
|
||||||
|
builder: (context, child) {
|
||||||
|
return Transform.scale(
|
||||||
|
scale: _scaleAnimation.value,
|
||||||
|
child: Container(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black.withOpacity(0.6),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
widget.icon,
|
||||||
|
size: 24,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
if (widget.count != null && widget.count! > 0) ...[
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
widget.count! > 999 ? '999+' : '${widget.count}',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HorizontalActionButton extends StatefulWidget {
|
||||||
|
final IconData icon;
|
||||||
|
final int? count;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
|
const _HorizontalActionButton({
|
||||||
|
required this.icon,
|
||||||
|
this.count,
|
||||||
|
this.onPressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_HorizontalActionButton> createState() => _HorizontalActionButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HorizontalActionButtonState extends State<_HorizontalActionButton>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late AnimationController _animationController;
|
||||||
|
late Animation<double> _scaleAnimation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_animationController = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 150),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
_scaleAnimation = Tween<double>(
|
||||||
|
begin: 1.0,
|
||||||
|
end: 0.85,
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_animationController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleTap() async {
|
||||||
|
await _animationController.forward();
|
||||||
|
await _animationController.reverse();
|
||||||
|
widget.onPressed?.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: _handleTap,
|
||||||
|
child: AnimatedBuilder(
|
||||||
|
animation: _scaleAnimation,
|
||||||
|
builder: (context, child) {
|
||||||
|
return Transform.scale(
|
||||||
|
scale: _scaleAnimation.value,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[100],
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
widget.icon,
|
||||||
|
size: 20,
|
||||||
|
color: Colors.grey[900],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
if (widget.count != null && widget.count! > 0) ...[
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
widget.count! > 999 ? '999+' : '${widget.count}',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey[100],
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
class PostCardWidget extends StatelessWidget {
|
class PostCardWidget extends StatefulWidget {
|
||||||
final String title;
|
final String title;
|
||||||
final String content;
|
final String content;
|
||||||
final String authorName;
|
final String authorName;
|
||||||
@@ -13,6 +14,7 @@ class PostCardWidget extends StatelessWidget {
|
|||||||
final VoidCallback? onLike;
|
final VoidCallback? onLike;
|
||||||
final VoidCallback? onComment;
|
final VoidCallback? onComment;
|
||||||
final VoidCallback? onShare;
|
final VoidCallback? onShare;
|
||||||
|
final double? aspectRatio; // Nouveau paramètre pour le ratio (largeur/hauteur)
|
||||||
|
|
||||||
const PostCardWidget({
|
const PostCardWidget({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -28,8 +30,15 @@ class PostCardWidget extends StatelessWidget {
|
|||||||
this.onLike,
|
this.onLike,
|
||||||
this.onComment,
|
this.onComment,
|
||||||
this.onShare,
|
this.onShare,
|
||||||
|
this.aspectRatio, // null = ratio naturel de l'image, ex: 16/9, 4/3, 1/1
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PostCardWidget> createState() => _PostCardWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostCardWidgetState extends State<PostCardWidget> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
@@ -49,9 +58,9 @@ class PostCardWidget extends StatelessWidget {
|
|||||||
// Photo de profil de l'auteur
|
// Photo de profil de l'auteur
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 20,
|
radius: 20,
|
||||||
backgroundImage: NetworkImage(authorImageUrl),
|
backgroundImage: NetworkImage(widget.authorImageUrl),
|
||||||
onBackgroundImageError: (_, __) {},
|
onBackgroundImageError: (_, __) {},
|
||||||
child: authorImageUrl.isEmpty
|
child: widget.authorImageUrl.isEmpty
|
||||||
? const Icon(Icons.person, size: 20)
|
? const Icon(Icons.person, size: 20)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
@@ -62,7 +71,7 @@ class PostCardWidget extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
authorName,
|
widget.authorName,
|
||||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
@@ -70,7 +79,7 @@ class PostCardWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
_formatDate(publishDate),
|
_formatDate(widget.publishDate),
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
color: Colors.grey,
|
color: Colors.grey,
|
||||||
),
|
),
|
||||||
@@ -97,7 +106,7 @@ class PostCardWidget extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
// Contenu
|
// Contenu
|
||||||
Text(
|
Text(
|
||||||
content,
|
widget.content,
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
height: 1.4,
|
height: 1.4,
|
||||||
@@ -108,72 +117,82 @@ class PostCardWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
|
|
||||||
// Images avec boutons d'action si présentes
|
// Images avec boutons d'action si présentes
|
||||||
if (imageUrls != null && imageUrls!.isNotEmpty)
|
if (widget.imageUrls != null && widget.imageUrls!.isNotEmpty)
|
||||||
Container(
|
FutureBuilder<double>(
|
||||||
//margin: const EdgeInsets.symmetric(vertical: 12.0),
|
future: getImageHeightWithRatio(
|
||||||
height: 200,
|
widget.imageUrls![0], // Utilise la première image pour calculer la hauteur
|
||||||
child: Stack(
|
MediaQuery.of(context).size.width - 32, // Largeur du conteneur (avec padding)
|
||||||
children: [
|
widget.aspectRatio, // Ratio forcé ou null pour le ratio naturel
|
||||||
// Images
|
|
||||||
PageView.builder(
|
|
||||||
itemCount: imageUrls!.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return Container(
|
|
||||||
//margin: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(0),
|
|
||||||
topRight: Radius.circular(0),
|
|
||||||
bottomLeft: Radius.circular(8),
|
|
||||||
bottomRight: Radius.circular(8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(0),
|
|
||||||
topRight: Radius.circular(0),
|
|
||||||
bottomLeft: Radius.circular(8),
|
|
||||||
bottomRight: Radius.circular(8),
|
|
||||||
),
|
|
||||||
child: _buildImage(imageUrls![index]),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// Boutons d'action en overlay à droite
|
|
||||||
Positioned(
|
|
||||||
right: 24,
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
_VerticalActionButton(
|
|
||||||
icon: Icons.favorite_outline,
|
|
||||||
count: likesCount,
|
|
||||||
onPressed: onLike,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_VerticalActionButton(
|
|
||||||
icon: Icons.comment_outlined,
|
|
||||||
count: commentsCount,
|
|
||||||
onPressed: onComment,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_VerticalActionButton(
|
|
||||||
icon: Icons.share_outlined,
|
|
||||||
count: sharesCount,
|
|
||||||
onPressed: onShare,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final imageHeight = snapshot.data ?? 200.0; // Hauteur par défaut si pas encore calculée
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
height: imageHeight,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// Images
|
||||||
|
PageView.builder(
|
||||||
|
itemCount: widget.imageUrls!.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return Container(
|
||||||
|
//margin: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(0),
|
||||||
|
topRight: Radius.circular(0),
|
||||||
|
bottomLeft: Radius.circular(8),
|
||||||
|
bottomRight: Radius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(0),
|
||||||
|
topRight: Radius.circular(0),
|
||||||
|
bottomLeft: Radius.circular(8),
|
||||||
|
bottomRight: Radius.circular(8),
|
||||||
|
),
|
||||||
|
child: _buildImage(widget.imageUrls![index]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// Boutons d'action en overlay à droite
|
||||||
|
Positioned(
|
||||||
|
right: 24,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
_VerticalActionButton(
|
||||||
|
icon: Icons.favorite_outline,
|
||||||
|
count: widget.likesCount,
|
||||||
|
onPressed: widget.onLike,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_VerticalActionButton(
|
||||||
|
icon: Icons.comment_outlined,
|
||||||
|
count: widget.commentsCount,
|
||||||
|
onPressed: widget.onComment,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_VerticalActionButton(
|
||||||
|
icon: Icons.share_outlined,
|
||||||
|
count: widget.sharesCount,
|
||||||
|
onPressed: widget.onShare,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
// Si pas d'image, boutons d'action en bas à droite
|
// Si pas d'image, boutons d'action en bas à droite
|
||||||
if (imageUrls == null || imageUrls!.isEmpty)
|
if (widget.imageUrls == null || widget.imageUrls!.isEmpty)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 16.0),
|
padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 16.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -182,20 +201,20 @@ class PostCardWidget extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
_HorizontalActionButton(
|
_HorizontalActionButton(
|
||||||
icon: Icons.favorite_outline,
|
icon: Icons.favorite_outline,
|
||||||
count: likesCount,
|
count: widget.likesCount,
|
||||||
onPressed: onLike,
|
onPressed: widget.onLike,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
_HorizontalActionButton(
|
_HorizontalActionButton(
|
||||||
icon: Icons.comment_outlined,
|
icon: Icons.comment_outlined,
|
||||||
count: commentsCount,
|
count: widget.commentsCount,
|
||||||
onPressed: onComment,
|
onPressed: widget.onComment,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
_HorizontalActionButton(
|
_HorizontalActionButton(
|
||||||
icon: Icons.share_outlined,
|
icon: Icons.share_outlined,
|
||||||
count: sharesCount,
|
count: widget.sharesCount,
|
||||||
onPressed: onShare,
|
onPressed: widget.onShare,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -223,6 +242,66 @@ class PostCardWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Récupère les dimensions de l'image (largeur et hauteur)
|
||||||
|
Future<Size?> getImageDimensions(String imageUrl) async {
|
||||||
|
try {
|
||||||
|
ImageProvider imageProvider;
|
||||||
|
|
||||||
|
if (imageUrl.startsWith('assets/')) {
|
||||||
|
imageProvider = AssetImage(imageUrl);
|
||||||
|
} else {
|
||||||
|
imageProvider = NetworkImage(imageUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ImageStream stream = imageProvider.resolve(const ImageConfiguration());
|
||||||
|
final Completer<Size?> completer = Completer();
|
||||||
|
|
||||||
|
late ImageStreamListener listener;
|
||||||
|
listener = ImageStreamListener((ImageInfo info, bool synchronousCall) {
|
||||||
|
final double width = info.image.width.toDouble();
|
||||||
|
final double height = info.image.height.toDouble();
|
||||||
|
stream.removeListener(listener);
|
||||||
|
completer.complete(Size(width, height));
|
||||||
|
}, onError: (dynamic exception, StackTrace? stackTrace) {
|
||||||
|
stream.removeListener(listener);
|
||||||
|
completer.complete(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.addListener(listener);
|
||||||
|
return completer.future;
|
||||||
|
} catch (e) {
|
||||||
|
print('Erreur lors de la récupération des dimensions de l\'image: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Récupère uniquement la hauteur de l'image
|
||||||
|
Future<double?> getImageHeight(String imageUrl) async {
|
||||||
|
final dimensions = await getImageDimensions(imageUrl);
|
||||||
|
return dimensions?.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calcule la hauteur de l'image en fonction de la largeur du conteneur
|
||||||
|
Future<double?> getImageHeightForWidth(String imageUrl, double containerWidth) async {
|
||||||
|
final dimensions = await getImageDimensions(imageUrl);
|
||||||
|
if (dimensions == null) return null;
|
||||||
|
|
||||||
|
final aspectRatio = dimensions.width / dimensions.height;
|
||||||
|
return containerWidth / aspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calcule la hauteur en respectant un ratio spécifique ou le ratio naturel
|
||||||
|
Future<double> getImageHeightWithRatio(String imageUrl, double containerWidth, double? forcedRatio) async {
|
||||||
|
if (forcedRatio != null) {
|
||||||
|
// Utilise le ratio forcé (largeur/hauteur)
|
||||||
|
return containerWidth / forcedRatio;
|
||||||
|
} else {
|
||||||
|
// Utilise le ratio naturel de l'image
|
||||||
|
final naturalHeight = await getImageHeightForWidth(imageUrl, containerWidth);
|
||||||
|
return naturalHeight ?? 200.0; // Fallback si l'image ne peut pas être chargée
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildImage(String imageUrl) {
|
Widget _buildImage(String imageUrl) {
|
||||||
// Vérifie si c'est une image d'assets ou une URL réseau
|
// Vérifie si c'est une image d'assets ou une URL réseau
|
||||||
if (imageUrl.startsWith('assets/')) {
|
if (imageUrl.startsWith('assets/')) {
|
||||||
@@ -354,7 +433,7 @@ class _VerticalActionButtonState extends State<_VerticalActionButton>
|
|||||||
),
|
),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
widget.icon,
|
widget.icon,
|
||||||
size: 24,
|
size: 30,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -365,7 +444,7 @@ class _VerticalActionButtonState extends State<_VerticalActionButton>
|
|||||||
widget.count! > 999 ? '999+' : '${widget.count}',
|
widget.count! > 999 ? '999+' : '${widget.count}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 12,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -214,6 +214,7 @@ class EventDetailsView extends StackedView<EventDetailsViewModel> {
|
|||||||
imageUrls: index % 3 == 0 ? ['assets/images/Affiche.jpg'] : null,
|
imageUrls: index % 3 == 0 ? ['assets/images/Affiche.jpg'] : null,
|
||||||
likesCount: (index + 1) * 5,
|
likesCount: (index + 1) * 5,
|
||||||
commentsCount: (index + 1) * 2,
|
commentsCount: (index + 1) * 2,
|
||||||
|
aspectRatio: 4/5,
|
||||||
onLike: () {
|
onLike: () {
|
||||||
// Action lors du clic sur "J'aime"
|
// Action lors du clic sur "J'aime"
|
||||||
print('Like publication ${index + 1}');
|
print('Like publication ${index + 1}');
|
||||||
|
|||||||
Reference in New Issue
Block a user