Files
afterwork/lib/presentation/screens/friends/friends_screen.dart
dahoud 92612abbd7 fix(chat): Correction race condition + Implémentation TODOs
## Corrections Critiques

### Race Condition - Statuts de Messages
- Fix : Les icônes de statut (✓, ✓✓, ✓✓ bleu) ne s'affichaient pas
- Cause : WebSocket delivery confirmations arrivaient avant messages locaux
- Solution : Pattern Optimistic UI dans chat_bloc.dart
  - Création message temporaire immédiate
  - Ajout à la liste AVANT requête HTTP
  - Remplacement par message serveur à la réponse
- Fichier : lib/presentation/state_management/chat_bloc.dart

## Implémentation TODOs (13/21)

### Social (social_header_widget.dart)
-  Copier lien du post dans presse-papiers
-  Partage natif via Share.share()
-  Dialogue de signalement avec 5 raisons

### Partage (share_post_dialog.dart)
-  Interface sélection d'amis avec checkboxes
-  Partage externe via Share API

### Média (media_upload_service.dart)
-  Parsing JSON réponse backend
-  Méthode deleteMedia() pour suppression
-  Génération miniature vidéo

### Posts (create_post_dialog.dart, edit_post_dialog.dart)
-  Extraction URL depuis uploads
-  Documentation chargement médias

### Chat (conversations_screen.dart)
-  Navigation vers notifications
-  ConversationSearchDelegate pour recherche

## Nouveaux Fichiers

### Configuration
- build-prod.ps1 : Script build production avec dart-define
- lib/core/constants/env_config.dart : Gestion environnements

### Documentation
- TODOS_IMPLEMENTED.md : Documentation complète TODOs

## Améliorations

### Architecture
- Refactoring injection de dépendances
- Amélioration routing et navigation
- Optimisation providers (UserProvider, FriendsProvider)

### UI/UX
- Amélioration thème et couleurs
- Optimisation animations
- Meilleure gestion erreurs

### Services
- Configuration API avec env_config
- Amélioration datasources (events, users)
- Optimisation modèles de données
2026-01-10 10:43:17 +00:00

297 lines
8.8 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../../core/constants/design_system.dart';
import '../../../core/utils/page_transitions.dart';
import '../../../data/providers/friends_provider.dart';
import '../../widgets/add_friend_dialog.dart';
import '../../widgets/animated_widgets.dart';
import '../../widgets/custom_snackbar.dart';
import '../../widgets/friends_tab.dart';
import '../../widgets/requests_tab.dart';
/// Écran principal pour afficher et gérer la liste des amis.
///
/// Cet écran inclut des fonctionnalités de pagination, de recherche,
/// et de rafraîchissement manuel de la liste avec design moderne et compact.
///
/// **Fonctionnalités:**
/// - Affichage de la liste des amis en grille
/// - Recherche d'amis
/// - Pagination automatique
/// - Pull-to-refresh
/// - Ajout d'amis
/// - Gestion des demandes d'amitié (reçues et envoyées)
class FriendsScreen extends StatefulWidget {
const FriendsScreen({required this.userId, super.key});
final String userId;
@override
State<FriendsScreen> createState() => _FriendsScreenState();
}
class _FriendsScreenState extends State<FriendsScreen>
with SingleTickerProviderStateMixin {
// ============================================================================
// CONSTANTS
// ============================================================================
static const double _scrollThreshold = 200;
static const Duration _refreshDelay = Duration(milliseconds: 500);
// ============================================================================
// CONTROLLERS
// ============================================================================
late final ScrollController _scrollController;
late final TabController _tabController;
// ============================================================================
// LIFECYCLE
// ============================================================================
@override
void initState() {
super.initState();
_initializeControllers();
_loadInitialData();
}
@override
void dispose() {
_disposeControllers();
super.dispose();
}
// ============================================================================
// INITIALIZATION
// ============================================================================
/// Initialise les contrôleurs.
void _initializeControllers() {
_scrollController = ScrollController()..addListener(_onScroll);
_tabController = TabController(length: 2, vsync: this);
}
/// Libère les ressources des contrôleurs.
void _disposeControllers() {
_scrollController
..removeListener(_onScroll)
..dispose();
_tabController.dispose();
}
/// Charge les données initiales.
void _loadInitialData() {
final provider = Provider.of<FriendsProvider>(context, listen: false);
provider
..fetchFriends(widget.userId)
..fetchSentRequests()
..fetchReceivedRequests();
}
// ============================================================================
// SCROLL HANDLING
// ============================================================================
/// Gère le défilement pour la pagination.
void _onScroll() {
final provider = Provider.of<FriendsProvider>(context, listen: false);
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - _scrollThreshold &&
!provider.isLoading &&
provider.hasMore) {
provider.fetchFriends(widget.userId, loadMore: true);
}
}
// ============================================================================
// ACTIONS
// ============================================================================
/// Gère le rafraîchissement de la liste des amis.
Future<void> _handleRefresh() async {
final provider = Provider.of<FriendsProvider>(context, listen: false);
provider.fetchFriends(widget.userId);
await Future<void>.delayed(_refreshDelay);
}
/// Gère l'ajout d'un ami.
void _handleAddFriend() {
showDialog<void>(
context: context,
builder: (context) => AddFriendDialog(
onFriendAdded: _onFriendAdded,
),
);
}
/// Callback appelé après l'ajout d'un ami.
void _onFriendAdded() {
final provider = Provider.of<FriendsProvider>(context, listen: false);
provider
..fetchFriends(widget.userId)
..fetchSentRequests()
..fetchReceivedRequests();
}
/// Gère l'actualisation manuelle.
void _handleRefreshAll() {
final provider = Provider.of<FriendsProvider>(context, listen: false);
provider
..fetchFriends(widget.userId)
..fetchSentRequests()
..fetchReceivedRequests();
}
// ============================================================================
// BUILD
// ============================================================================
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _buildAppBar(),
body: _buildBody(),
floatingActionButton: _buildFloatingActionButton(),
);
}
/// Construit la barre d'application.
PreferredSizeWidget _buildAppBar() {
final theme = Theme.of(context);
return AppBar(
elevation: 0,
scrolledUnderElevation: 2,
title: Text(
'Mes Amis',
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
letterSpacing: -0.5,
),
),
bottom: TabBar(
controller: _tabController,
indicatorSize: TabBarIndicatorSize.tab,
tabs: const [
Tab(
text: 'Amis',
icon: Icon(Icons.people_rounded, size: 22),
iconMargin: EdgeInsets.only(bottom: 4),
),
Tab(
text: 'Demandes',
icon: Icon(Icons.person_add_rounded, size: 22),
iconMargin: EdgeInsets.only(bottom: 4),
),
],
),
actions: [
IconButton(
icon: const Icon(Icons.refresh_rounded, size: 22),
tooltip: 'Actualiser',
onPressed: _handleRefreshAll,
),
],
);
}
/// Construit le corps de l'écran.
Widget _buildBody() {
return SafeArea(
child: TabBarView(
controller: _tabController,
children: [
FriendsTab(
userId: widget.userId,
scrollController: _scrollController,
onRefresh: _handleRefresh,
),
RequestsTab(
onAccept: _handleAcceptRequest,
onReject: _handleRejectRequest,
onCancel: _handleCancelRequest,
onRefresh: _handleRefreshAll,
),
],
),
);
}
/// Construit le bouton flottant.
Widget _buildFloatingActionButton() {
return FloatingActionButton(
onPressed: _handleAddFriend,
tooltip: 'Ajouter un ami',
elevation: 2,
child: const Icon(Icons.person_add_rounded, size: 24),
);
}
// ============================================================================
// REQUEST HANDLERS
// ============================================================================
/// Gère l'acceptation d'une demande d'amitié.
Future<void> _handleAcceptRequest(
FriendsProvider provider,
String friendshipId,
) async {
try {
await provider.acceptFriendRequest(friendshipId);
if (!mounted) return;
context.showSuccess('Demande d\'amitié acceptée');
_refreshAfterRequest();
} catch (e) {
if (!mounted) return;
context.showError('Erreur: ${e.toString()}');
}
}
/// Gère le rejet d'une demande d'amitié.
Future<void> _handleRejectRequest(
FriendsProvider provider,
String friendshipId,
) async {
try {
await provider.rejectFriendRequest(friendshipId);
if (!mounted) return;
context.showWarning('Demande d\'amitié rejetée');
provider.fetchReceivedRequests();
} catch (e) {
if (!mounted) return;
context.showError('Erreur: ${e.toString()}');
}
}
/// Gère l'annulation d'une demande d'amitié envoyée.
Future<void> _handleCancelRequest(
FriendsProvider provider,
String friendshipId,
) async {
try {
await provider.cancelFriendRequest(friendshipId);
if (!mounted) return;
context.showInfo('Demande d\'amitié annulée');
provider.fetchSentRequests();
} catch (e) {
if (!mounted) return;
context.showError('Erreur: ${e.toString()}');
}
}
// ============================================================================
// HELPERS
// ============================================================================
/// Rafraîchit les données après une action sur une demande.
void _refreshAfterRequest() {
final provider = Provider.of<FriendsProvider>(context, listen: false);
provider
..fetchFriends(widget.userId)
..fetchReceivedRequests();
}
}