## 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
297 lines
8.8 KiB
Dart
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();
|
|
}
|
|
}
|