refactoring and checkpoint

This commit is contained in:
DahoudG
2024-09-24 00:32:20 +00:00
parent dc73ba7dcc
commit 6b12cfeb41
159 changed files with 8119 additions and 1535 deletions

View File

@@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
class AnimatedActionButton extends StatelessWidget {
final IconData icon;
final String label;
const AnimatedActionButton({
super.key,
required this.icon,
required this.label,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
Icon(icon, color: Colors.white, size: 30),
const SizedBox(height: 5),
Text(label, style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.w600)),
],
);
}
}

View File

@@ -0,0 +1,79 @@
import 'dart:io';
import 'package:camerawesome/camerawesome_plugin.dart';
import 'package:flutter/material.dart';
import 'package:logger/logger.dart';
import 'package:path_provider/path_provider.dart';
import '../../core/constants/colors.dart';
class CreateStoryPage extends StatefulWidget {
const CreateStoryPage({super.key});
@override
_CreateStoryPageState createState() => _CreateStoryPageState();
}
class _CreateStoryPageState extends State<CreateStoryPage> {
final Logger logger = Logger();
@override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true, // Permet à l'AppBar de passer en mode transparent
appBar: AppBar(
title: const Text('Créer une nouvelle story'),
backgroundColor: Colors.transparent, // Transparence
elevation: 0, // Pas d'ombre pour l'en-tête
leading: IconButton(
icon: Icon(Icons.arrow_back, color: AppColors.onPrimary), // Couleur adaptative
onPressed: () {
Navigator.of(context).pop(); // Bouton retour
},
),
),
body: Stack(
children: [
CameraAwesomeBuilder.awesome(
saveConfig: SaveConfig.photoAndVideo(
photoPathBuilder: (sensors) async {
final sensor = sensors.first; // Utilisation du premier capteur
final Directory extDir = await getTemporaryDirectory();
final Directory testDir = await Directory('${extDir.path}/camerawesome').create(recursive: true);
final String filePath = '${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.jpg';
return SingleCaptureRequest(filePath, sensor); // CaptureRequest pour la photo
},
videoPathBuilder: (sensors) async {
final sensor = sensors.first; // Utilisation du premier capteur
final Directory extDir = await getTemporaryDirectory();
final Directory testDir = await Directory('${extDir.path}/camerawesome').create(recursive: true);
final String filePath = '${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.mp4';
return SingleCaptureRequest(filePath, sensor); // CaptureRequest pour la vidéo
},
),
sensorConfig: SensorConfig.single(
sensor: Sensor.position(SensorPosition.back), // Configuration correcte du capteur
),
onMediaTap: (mediaCapture) async {
final captureRequest = mediaCapture.captureRequest;
if (captureRequest is SingleCaptureRequest) {
final filePath = captureRequest.path; // Accès au chemin de fichier
if (filePath != null) {
logger.i('Média capturé : $filePath');
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Média sauvegardé à $filePath'),
backgroundColor: AppColors.accentColor, // Couleur adaptative du snack bar
));
} else {
logger.e('Erreur : Aucun fichier capturé.');
}
} else {
logger.e('Erreur : Capture non reconnue.');
}
},
),
],
),
);
}
}

View File

@@ -7,7 +7,7 @@ class CustomDrawer extends StatelessWidget {
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
const DrawerHeader(
decoration: BoxDecoration(
color: Colors.blueAccent,
),

View File

@@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'package:afterwork/data/models/event_model.dart';
import '../screens/event/event_card.dart';
class EventList extends StatelessWidget {
final List<EventModel> events;
const EventList({Key? key, required this.events}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: events.length,
itemBuilder: (context, index) {
final event = events[index];
return EventCard(
event: event,
userId: 'user_id_here', // Vous pouvez passer l'ID réel de l'utilisateur connecté
userName: 'John', // Vous pouvez passer le prénom réel de l'utilisateur
userLastName: 'Doe', // Vous pouvez passer le nom réel de l'utilisateur
onReact: () => _handleReact(event),
onComment: () => _handleComment(event),
onShare: () => _handleShare(event),
onParticipate: () => _handleParticipate(event),
onCloseEvent: () => _handleCloseEvent(event),
onReopenEvent: () => _handleReopenEvent(event),
);
},
);
}
// Gestion des actions
void _handleReact(EventModel event) {
print('Réaction ajoutée à l\'événement ${event.title}');
}
void _handleComment(EventModel event) {
print('Commentaire ajouté à l\'événement ${event.title}');
}
void _handleShare(EventModel event) {
print('Événement partagé : ${event.title}');
}
void _handleParticipate(EventModel event) {
print('Participation confirmée à l\'événement ${event.title}');
}
void _handleCloseEvent(EventModel event) {
print('Événement ${event.title} fermé');
}
void _handleReopenEvent(EventModel event) {
print('Événement ${event.title} réouvert');
}
}

View File

@@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
class FriendSuggestions extends StatelessWidget {
final Size size;
const FriendSuggestions({required this.size, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: List.generate(3, (index) {
return Container(
margin: const EdgeInsets.only(bottom: 20),
padding: const EdgeInsets.all(16.0),
width: size.width,
decoration: BoxDecoration(
color: Colors.grey[850],
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 6,
offset: const Offset(0, 3),
),
],
),
child: Row(
children: [
const CircleAvatar(
radius: 30,
backgroundImage: AssetImage('lib/assets/images/friend_placeholder.png'),
),
const SizedBox(width: 10),
const Expanded(
child: Text(
'Nom d\'utilisateur',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
ElevatedButton(
onPressed: () {
print('Ajouter comme ami');
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.teal,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text('Ajouter'),
),
],
),
);
}),
);
}
}

View File

@@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
class GroupList extends StatelessWidget {
final Size size;
const GroupList({required this.size, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: List.generate(3, (index) {
return Container(
margin: const EdgeInsets.only(bottom: 20),
padding: const EdgeInsets.all(16.0),
width: size.width,
decoration: BoxDecoration(
color: Colors.grey[850],
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 6,
offset: const Offset(0, 3),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const CircleAvatar(
radius: 30,
backgroundImage: AssetImage('lib/assets/images/group_placeholder.png'),
),
const SizedBox(width: 10),
const Expanded(
child: Text(
'Club des Amateurs de Cinéma',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
ElevatedButton(
onPressed: () {
print('Rejoindre le groupe');
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blueAccent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text('Rejoindre'),
),
],
),
);
}),
);
}
}

View File

@@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
class PopularActivityList extends StatelessWidget {
final Size size;
const PopularActivityList({required this.size, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
height: 200,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: 3,
separatorBuilder: (context, index) => const SizedBox(width: 15),
itemBuilder: (context, index) {
return Container(
width: size.width * 0.8,
decoration: BoxDecoration(
color: Colors.grey[800],
borderRadius: BorderRadius.circular(12),
image: const DecorationImage(
image: AssetImage('lib/assets/images/activity_placeholder.png'),
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(Colors.black38, BlendMode.darken),
),
),
child: const Padding(
padding: EdgeInsets.all(12.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Soirée Stand-up Comedy',
style: TextStyle(
color: Colors.white,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 5),
Text(
'Vendredi, 20h00',
style: TextStyle(
color: Colors.white70,
fontSize: 14,
),
),
],
),
),
);
},
),
);
}
}

View File

@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
class QuickActionButton extends StatelessWidget {
final String label;
final IconData icon;
final Color color;
final double fontSize; // Ajout d'un paramètre pour personnaliser la taille du texte
const QuickActionButton({
required this.label,
required this.icon,
required this.color,
this.fontSize = 14, // Valeur par défaut
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
CircleAvatar(
radius: 30,
backgroundColor: color.withOpacity(0.2),
child: Icon(icon, color: color, size: 28),
),
const SizedBox(height: 8),
Text(
label,
style: TextStyle(color: Colors.white, fontSize: fontSize), // Utilisation de fontSize
textAlign: TextAlign.center,
),
],
);
}
}

View File

@@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
class RecommendedEventList extends StatelessWidget {
final Size size;
const RecommendedEventList({required this.size, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
height: 240,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: 3, // Nombre d'événements fictifs
separatorBuilder: (context, index) => const SizedBox(width: 15),
itemBuilder: (context, index) {
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: size.width * 0.8,
decoration: BoxDecoration(
color: Colors.grey[800],
borderRadius: BorderRadius.circular(12),
image: const DecorationImage(
image: AssetImage('lib/assets/images/event_placeholder.png'),
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(Colors.black38, BlendMode.darken),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.5),
blurRadius: 6,
offset: const Offset(0, 3),
),
],
),
child: const Padding(
padding: EdgeInsets.all(12.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Concert de Jazz',
style: TextStyle(
color: Colors.white,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 5),
Text(
'Samedi, 18h00',
style: TextStyle(
color: Colors.white70,
fontSize: 14,
),
),
],
),
),
);
},
),
);
}
}

View File

@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
class SectionHeader extends StatelessWidget {
final String title;
final IconData icon;
final TextStyle? textStyle; // Ajout de la possibilité de personnaliser le style du texte
const SectionHeader({
required this.title,
required this.icon,
this.textStyle, // Paramètre optionnel
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: textStyle ?? const TextStyle( // Utilisation du style fourni ou d'un style par défaut
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Icon(icon, color: Colors.white),
],
);
}
}

View File

@@ -0,0 +1,112 @@
import 'package:flutter/material.dart';
import 'package:afterwork/presentation/widgets/story_video_player.dart';
import '../../../core/utils/calculate_time_ago.dart';
import 'animated_action_button.dart';
class StoryDetail extends StatefulWidget {
final String username;
final DateTime publicationDate;
final String mediaUrl;
final String userImage;
final bool isVideo;
const StoryDetail({
super.key,
required this.username,
required this.publicationDate,
required this.mediaUrl,
required this.userImage,
required this.isVideo,
});
@override
StoryDetailState createState() => StoryDetailState();
}
class StoryDetailState extends State<StoryDetail> {
late Offset _startDragOffset;
late Offset _currentDragOffset;
bool _isDragging = false;
// Gestion du swipe vertical pour fermer la story
void _onVerticalDragStart(DragStartDetails details) {
_startDragOffset = details.globalPosition;
}
void _onVerticalDragUpdate(DragUpdateDetails details) {
_currentDragOffset = details.globalPosition;
if (_currentDragOffset.dy - _startDragOffset.dy > 100) {
setState(() {
_isDragging = true;
});
Navigator.pop(context);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
iconTheme: const IconThemeData(color: Colors.white),
),
body: GestureDetector(
onVerticalDragStart: _onVerticalDragStart,
onVerticalDragUpdate: _onVerticalDragUpdate,
child: Stack(
children: [
Positioned.fill(
child: AnimatedOpacity(
opacity: _isDragging ? 0.5 : 1.0,
duration: const Duration(milliseconds: 300),
child: widget.isVideo
? StoryVideoPlayer(mediaUrl: widget.mediaUrl)
: Image.asset(widget.mediaUrl, fit: BoxFit.cover),
),
),
// Informations sur l'utilisateur
Positioned(
top: 40,
left: 20,
child: Row(
children: [
CircleAvatar(radius: 32, backgroundImage: AssetImage(widget.userImage)),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.username,
style: const TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
),
Text(
'Il y a ${calculateTimeAgo(widget.publicationDate)}',
style: const TextStyle(color: Colors.white70, fontSize: 14),
),
],
),
],
),
),
// Boutons d'actions flottants à droite
const Positioned(
right: 20,
bottom: 100,
child: Column(
children: [
AnimatedActionButton(icon: Icons.favorite_border, label: 'J\'aime'),
SizedBox(height: 20),
AnimatedActionButton(icon: Icons.comment, label: 'Commenter'),
SizedBox(height: 20),
AnimatedActionButton(icon: Icons.share, label: 'Partager'),
],
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,194 @@
import 'package:flutter/material.dart';
import 'package:logger/logger.dart';
import '../../core/constants/colors.dart';
import '../../core/utils/calculate_time_ago.dart';
import 'story_detail.dart';
import 'create_story.dart';
/// La classe StorySection représente la section des stories dans l'interface.
/// Elle affiche une liste horizontale de stories et permet à l'utilisateur d'ajouter une nouvelle story.
/// Les logs sont utilisés pour tracer chaque action réalisée dans l'interface.
class StorySection extends StatelessWidget {
final Size size;
final Logger logger = Logger(); // Logger pour tracer les événements et actions
StorySection({required this.size, super.key});
@override
Widget build(BuildContext context) {
logger.i('Construction de la section des stories');
return Container(
padding: const EdgeInsets.symmetric(vertical: 8),
child: SizedBox(
height: size.height / 4.5, // Hauteur ajustée pour éviter le débordement
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: 6, // Nombre de stories à afficher
separatorBuilder: (context, index) => const SizedBox(width: 8),
itemBuilder: (context, index) {
if (index == 0) return _buildAddStoryCard(context);
DateTime publicationDate = DateTime.now().subtract(Duration(hours: (index - 1) * 6));
logger.i('Affichage de la story $index avec la date $publicationDate');
return GestureDetector(
onTap: () {
logger.i('Clic sur la story $index, affichage du détail');
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => FadeTransition(
opacity: animation,
child: StoryDetail(
username: 'Utilisateur ${index - 1}',
publicationDate: publicationDate,
mediaUrl: 'https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4',
userImage: 'lib/assets/images/user_placeholder.png',
isVideo: true,
),
),
),
);
},
child: _buildStoryCard(index, publicationDate),
);
},
),
),
);
}
/// Construit une carte de story à partir de l'index et de la date de publication.
Widget _buildStoryCard(int index, DateTime publicationDate) {
return Column( // Utilisation de Column sans Expanded pour éviter les erreurs
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: size.width / 4.5,
height: size.height / 5.5, // Hauteur ajustée pour éviter le dépassement
decoration: BoxDecoration(
color: AppColors.cardColor, // Utilisation des couleurs automatiques pour le fond des cartes
borderRadius: BorderRadius.circular(20), // Bords arrondis à 20 pixels
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 3),
),
],
),
child: Stack(
alignment: Alignment.center,
children: [
Positioned.fill(child: _buildGradientOverlay()),
Positioned(top: 6, right: 6, child: _buildAvatar(index)),
Positioned(bottom: 10, left: 10, child: _buildUsername(index)),
],
),
),
const SizedBox(height: 4), // Espace entre la carte et la date
_buildPublicationDate(publicationDate),
],
);
}
/// Construit un overlay en dégradé pour la story.
Widget _buildGradientOverlay() {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.black.withOpacity(0.4), Colors.transparent],
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
),
borderRadius: BorderRadius.circular(20), // Assure que l'overlay suit les bords arrondis
),
);
}
/// Construit l'avatar de l'utilisateur pour la story.
Widget _buildAvatar(int index) {
return Hero(
tag: 'avatar-$index',
child: CircleAvatar(
radius: 16,
backgroundColor: Colors.grey.withOpacity(0.2),
child: const CircleAvatar(
radius: 14,
backgroundImage: AssetImage('lib/assets/images/user_placeholder.png'),
),
),
);
}
/// Construit le nom d'utilisateur affiché sous la story.
Widget _buildUsername(int index) {
return Text(
'Utilisateur ${index - 1}',
style: TextStyle(
fontFamily: 'Montserrat',
color: AppColors.textPrimary, // Texte principal avec couleur dynamique
fontSize: 12,
fontWeight: FontWeight.bold,
),
);
}
/// Affiche la date de publication sous forme relative ("il y a X heures").
Widget _buildPublicationDate(DateTime publicationDate) {
return Text(
'Il y a ${calculateTimeAgo(publicationDate)}',
style: TextStyle(
color: AppColors.textSecondary, // Texte secondaire avec couleur dynamique
fontSize: 11,
),
);
}
/// Construit une carte spéciale pour ajouter une nouvelle story.
Widget _buildAddStoryCard(BuildContext context) {
return GestureDetector(
onTap: () {
logger.i('Clic sur l\'ajout d\'une nouvelle story');
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const CreateStoryPage()),
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: size.width / 4.5,
height: size.height / 5.5, // Hauteur ajustée pour éviter le dépassement
decoration: BoxDecoration(
color: AppColors.cardColor, // Utilisation des couleurs automatiques pour le fond des cartes
borderRadius: BorderRadius.circular(20), // Bords arrondis à 20 pixels
border: Border.all(color: AppColors.accentColor.withOpacity(0.3), width: 2),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 3),
),
],
),
child: Center(
child: Icon(
Icons.add_circle_outline,
color: AppColors.accentColor, // Utilisation des couleurs automatiques pour les icônes
size: 40,
),
),
),
const SizedBox(height: 4),
Text(
'Créer une story',
style: TextStyle(color: AppColors.textSecondary, fontSize: 13), // Texte secondaire avec couleur dynamique
),
],
),
);
}
}

View File

@@ -0,0 +1,75 @@
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart'; // Pour la lecture des vidéos
class StoryVideoPlayer extends StatefulWidget {
final String mediaUrl;
const StoryVideoPlayer({super.key, required this.mediaUrl});
@override
StoryVideoPlayerState createState() => StoryVideoPlayerState(); // Classe publique
}
class StoryVideoPlayerState extends State<StoryVideoPlayer> {
VideoPlayerController? _videoPlayerController;
bool _loadingError = false;
@override
void initState() {
super.initState();
_initializeVideoPlayer();
}
void _initializeVideoPlayer() async {
_videoPlayerController = VideoPlayerController.networkUrl(Uri.parse(widget.mediaUrl));
try {
await _videoPlayerController!.initialize();
setState(() {
_loadingError = false;
_videoPlayerController!.play();
});
} catch (e) {
setState(() {
_loadingError = true;
});
}
}
@override
void dispose() {
_videoPlayerController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_loadingError) {
return _buildRetryUI();
} else if (_videoPlayerController != null && _videoPlayerController!.value.isInitialized) {
return VideoPlayer(_videoPlayerController!);
} else {
return const Center(child: CircularProgressIndicator());
}
}
Widget _buildRetryUI() {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Problème de connexion ou de chargement', style: TextStyle(color: Colors.white)),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
setState(() {
_initializeVideoPlayer();
});
},
child: const Text('Réessayer'),
),
],
),
);
}
}