Première grosse version fonctionnel

This commit is contained in:
2026-04-22 23:30:31 +02:00
commit ccbe5ad801
329 changed files with 29518 additions and 0 deletions
@@ -0,0 +1,28 @@
class Activity {
final int id;
final String title;
final String description;
final String category; // 'Méditation', 'Musique', 'Respiration', etc.
final String? imageUrl;
final String? videoUrl;
Activity({
required this.id,
required this.title,
required this.description,
required this.category,
this.imageUrl,
this.videoUrl,
});
factory Activity.fromJson(Map<String, dynamic> json) {
return Activity(
id: json['id'],
title: json['title'] ?? '',
description: json['description'] ?? '',
category: json['category'] ?? 'Détente',
imageUrl: json['image_url'],
videoUrl: json['video_url'],
);
}
}
@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import '../models/activity.dart';
import '../services/relaxation_service.dart';
class RelaxationProvider with ChangeNotifier {
final RelaxationService _service = RelaxationService();
List<Activity> _activities = [];
bool _isLoading = false;
String _selectedCategory = 'Toutes';
List<Activity> get activities {
if (_selectedCategory == 'Toutes') return _activities;
return _activities.where((a) => a.category == _selectedCategory).toList();
}
bool get isLoading => _isLoading;
String get selectedCategory => _selectedCategory;
Future<void> loadActivities() async {
_isLoading = true;
notifyListeners();
_activities = await _service.fetchActivities();
_isLoading = false;
notifyListeners();
}
void setCategory(String category) {
_selectedCategory = category;
notifyListeners();
}
}
@@ -0,0 +1,131 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/relaxation_provider.dart';
class RelaxationScreen extends StatefulWidget {
const RelaxationScreen({super.key});
@override
State<RelaxationScreen> createState() => _RelaxationScreenState();
}
class _RelaxationScreenState extends State<RelaxationScreen> {
@override
void initState() {
super.initState();
Future.microtask(() =>
Provider.of<RelaxationProvider>(context, listen: false).loadActivities());
}
@override
Widget build(BuildContext context) {
final provider = Provider.of<RelaxationProvider>(context);
final categories = ['Toutes', 'Méditation', 'Musique', 'Lecture', 'Sport'];
return Scaffold(
appBar: AppBar(
title: const Text('Espace Détente'),
elevation: 0,
),
body: Column(
children: [
// Barre de catégories
Container(
height: 60,
padding: const EdgeInsets.symmetric(vertical: 10),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: categories.length,
padding: const EdgeInsets.symmetric(horizontal: 16),
itemBuilder: (context, index) {
final cat = categories[index];
final isSelected = provider.selectedCategory == cat;
return Padding(
padding: const EdgeInsets.only(right: 10),
child: FilterChip(
label: Text(cat),
selected: isSelected,
onSelected: (_) => provider.setCategory(cat),
selectedColor: const Color(0xFF000080).withOpacity(0.2),
checkmarkColor: const Color(0xFF000080),
),
);
},
),
),
// Liste des activités
Expanded(
child: provider.isLoading
? const Center(child: CircularProgressIndicator())
: provider.activities.isEmpty
? const Center(child: Text('Aucune activité trouvée.'))
: ListView.builder(
itemCount: provider.activities.length,
padding: const EdgeInsets.all(16),
itemBuilder: (context, index) {
final activity = provider.activities[index];
return Card(
margin: const EdgeInsets.only(bottom: 16),
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (activity.imageUrl != null)
Image.network(
activity.imageUrl!,
height: 150,
width: double.infinity,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) => Container(
height: 150,
color: Colors.grey.shade200,
child: const Icon(Icons.image_not_supported),
),
),
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
activity.category,
style: TextStyle(
color: Colors.orange.shade700,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
if (activity.videoUrl != null)
const Icon(Icons.play_circle_fill, color: Color(0xFF000080)),
],
),
const SizedBox(height: 8),
Text(
activity.title,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text(
activity.description,
style: const TextStyle(color: Colors.grey),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
);
},
),
),
],
),
);
}
}
@@ -0,0 +1,21 @@
import 'package:dio/dio.dart';
import '../../../core/network/dio_client.dart';
import '../models/activity.dart';
class RelaxationService {
final Dio _dio = DioClient().dio;
Future<List<Activity>> fetchActivities() async {
try {
final response = await _dio.get('/activities');
if (response.statusCode == 200) {
List<dynamic> data = response.data;
return data.map((item) => Activity.fromJson(item)).toList();
}
return [];
} catch (e) {
print("Erreur fetchActivities: $e");
return [];
}
}
}