Files
unionflow-mobile-apps/lib/presentation/explore/network_page.dart
dahoud d094d6db9c Initial commit: unionflow-mobile-apps
Application Flutter complète (sans build artifacts).

Signed-off-by: lions dev Team
2026-03-15 16:30:08 +00:00

183 lines
7.1 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../widgets/shared/mini_header_bar.dart';
import '../../shared/widgets/core_card.dart';
import '../../shared/widgets/mini_avatar.dart';
import '../../shared/widgets/core_shimmer.dart';
import '../../shared/widgets/info_badge.dart';
import '../../shared/design_system/tokens/app_typography.dart';
import '../../shared/design_system/tokens/app_colors.dart';
import '../../core/di/injection.dart';
import '../../features/explore/presentation/bloc/network_bloc.dart';
import '../../features/explore/presentation/bloc/network_event.dart';
import '../../features/explore/presentation/bloc/network_state.dart';
/// UnionFlow Mobile - Onglet Réseau/Découverte (Mode DRY)
/// Affiche les membres et organisations. Strict minimalisme.
class NetworkPage extends StatelessWidget {
const NetworkPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => getIt<NetworkBloc>()..add(LoadNetworkRequested()),
child: const _NetworkView(),
);
}
}
class _NetworkView extends StatefulWidget {
const _NetworkView();
@override
State<_NetworkView> createState() => _NetworkViewState();
}
class _NetworkViewState extends State<_NetworkView> {
final TextEditingController _searchController = TextEditingController();
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
void _onSearchChanged(String query) {
context.read<NetworkBloc>().add(SearchNetworkRequested(query));
}
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
appBar: const MiniHeaderBar(title: 'Réseau'),
body: Column(
children: [
// Barre de recherche collante (Twitter Style)
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
border: Border(
bottom: BorderSide(
color: isDark ? AppColors.darkBorder : AppColors.lightBorder,
width: 1,
),
),
),
child: TextField(
controller: _searchController,
onChanged: _onSearchChanged,
style: AppTypography.actionText,
decoration: InputDecoration(
hintText: 'Rechercher des membres, organisations...',
hintStyle: AppTypography.subtitleSmall,
prefixIcon: const Icon(Icons.search, size: 20, color: AppColors.textSecondaryLight),
filled: true,
fillColor: isDark ? AppColors.darkSurface : AppColors.lightSurface,
contentPadding: const EdgeInsets.symmetric(vertical: 0), // Garder petit
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: BorderSide.none,
),
),
),
),
Expanded(
child: BlocBuilder<NetworkBloc, NetworkState>(
builder: (context, state) {
if (state is NetworkInitial || state is NetworkLoading) {
return const Padding(
padding: EdgeInsets.all(8.0),
child: CoreShimmer(itemCount: 6),
);
}
if (state is NetworkError) {
return Center(
child: Text(
state.message,
style: AppTypography.bodyTextSmall.copyWith(color: AppColors.error),
),
);
}
if (state is NetworkLoaded) {
if (state.items.isEmpty) {
return const Center(
child: Text('Aucun résultat trouvé.', style: AppTypography.subtitleSmall),
);
}
return ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: state.items.length,
itemBuilder: (context, index) {
final item = state.items[index];
return CoreCard(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
children: [
MiniAvatar(
imageUrl: item.avatarUrl,
fallbackText: item.name.isNotEmpty ? item.name[0] : 'U',
size: 40,
isOnline: item.isConnected, // Pastille verte simulée
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.name,
style: AppTypography.actionText,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (item.subtitle != null)
Text(
item.subtitle!,
style: AppTypography.subtitleSmall,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
const SizedBox(width: 8),
// Badge dynamique fonction du type ; tap pour Suivre / Ne plus suivre
if (item.type == 'Organization')
InfoBadge.neutral('Organisation')
else
GestureDetector(
onTap: () {
context.read<NetworkBloc>().add(ToggleFollowRequested(item.id));
},
child: InfoBadge(
text: item.isConnected ? 'Connecté' : 'Suivre',
backgroundColor: item.isConnected ? AppColors.lightSurface : AppColors.primaryGreen,
textColor: item.isConnected ? AppColors.textPrimaryLight : Colors.white,
),
),
],
),
);
},
);
}
return const SizedBox.shrink();
},
),
),
],
),
);
}
}