3974 lines
151 KiB
Dart
3974 lines
151 KiB
Dart
// ===== lib/pages/main_navigation.dart =====
|
|
import 'dart:convert';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:provider/provider.dart';
|
|
import 'package:wtpe/pages/session_manager.dart';
|
|
import '../main.dart';
|
|
import 'home.dart';
|
|
import 'dashboard.dart';
|
|
import 'login.dart';
|
|
import '../widgets/responsive_helper.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|
import 'package:url_launcher/url_launcher.dart'; // Vous devrez ajouter cette dépendance
|
|
|
|
// ===== CONFIGURATION API =====
|
|
const String baseUrl = 'https://api.live.wortis.cg/tpe';
|
|
const String apiBaseUrl = '$baseUrl/api';
|
|
const Map<String, String> apiHeaders = {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
};
|
|
|
|
// ===== COULEURS GLOBALES =====
|
|
class AppColors {
|
|
static const Color primary = Color(0xFF006699);
|
|
static const Color secondary = Color(0xFF0088CC);
|
|
static const Color accent = Color(0xFF006699);
|
|
static const Color background = Color(0xFFF8FAFC);
|
|
static const Color surface = Colors.white;
|
|
static const Color success = Color(0xFF006699);
|
|
static const Color purple = Color(0xFF006699);
|
|
}
|
|
|
|
// ===== NAVIGATION ITEM MODEL =====
|
|
class NavigationItem {
|
|
final IconData icon;
|
|
final IconData activeIcon;
|
|
final String label;
|
|
final Color color;
|
|
|
|
NavigationItem({
|
|
required this.icon,
|
|
required this.activeIcon,
|
|
required this.label,
|
|
required this.color,
|
|
});
|
|
}
|
|
|
|
// ===== MAIN NAVIGATION PAGE =====
|
|
class MainNavigationPage extends StatefulWidget {
|
|
final int initialIndex;
|
|
|
|
const MainNavigationPage({super.key, this.initialIndex = 0});
|
|
|
|
@override
|
|
State<MainNavigationPage> createState() => _MainNavigationPageState();
|
|
}
|
|
|
|
class _MainNavigationPageState extends State<MainNavigationPage>
|
|
with TickerProviderStateMixin {
|
|
late int _currentIndex;
|
|
late PageController _pageController;
|
|
late AnimationController _animationController;
|
|
late AnimationController _iconAnimationController;
|
|
late Animation<double> _scaleAnimation;
|
|
late List<Animation<double>> _iconAnimations;
|
|
|
|
final List<NavigationItem> _navigationItems = [
|
|
NavigationItem(
|
|
icon: Icons.home_rounded,
|
|
activeIcon: Icons.home,
|
|
label: 'Services',
|
|
color: AppColors.primary,
|
|
),
|
|
NavigationItem(
|
|
icon: Icons.dashboard_outlined,
|
|
activeIcon: Icons.dashboard,
|
|
label: 'Dashboard',
|
|
color: AppColors.primary,
|
|
),
|
|
NavigationItem(
|
|
icon: Icons.person_outline_rounded,
|
|
activeIcon: Icons.person,
|
|
label: 'Profil',
|
|
color: AppColors.primary,
|
|
),
|
|
];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_currentIndex = widget.initialIndex;
|
|
_pageController = PageController(initialPage: _currentIndex);
|
|
|
|
_animationController = AnimationController(
|
|
duration: const Duration(milliseconds: 300),
|
|
vsync: this,
|
|
);
|
|
|
|
_iconAnimationController = AnimationController(
|
|
duration: const Duration(milliseconds: 200),
|
|
vsync: this,
|
|
);
|
|
|
|
_scaleAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
|
CurvedAnimation(parent: _animationController, curve: Curves.elasticOut),
|
|
);
|
|
|
|
_iconAnimations =
|
|
_navigationItems.map((item) {
|
|
return Tween<double>(begin: 1.0, end: 1.2).animate(
|
|
CurvedAnimation(
|
|
parent: _iconAnimationController,
|
|
curve: Curves.elasticOut,
|
|
),
|
|
);
|
|
}).toList();
|
|
|
|
_animationController.forward();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_pageController.dispose();
|
|
_animationController.dispose();
|
|
_iconAnimationController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _onItemTapped(int index) {
|
|
if (index != _currentIndex) {
|
|
setState(() {
|
|
_currentIndex = index;
|
|
});
|
|
|
|
_pageController.animateToPage(
|
|
index,
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeInOut,
|
|
);
|
|
|
|
_iconAnimationController.forward().then((_) {
|
|
_iconAnimationController.reverse();
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: AppColors.background,
|
|
body: PageView(
|
|
controller: _pageController,
|
|
onPageChanged: (index) {
|
|
setState(() {
|
|
_currentIndex = index;
|
|
});
|
|
},
|
|
children: const [
|
|
HomePage(showAppBar: false),
|
|
DashboardPage(showAppBar: false),
|
|
ProfilePage(),
|
|
],
|
|
),
|
|
bottomNavigationBar: _buildCustomBottomNavigation(),
|
|
);
|
|
}
|
|
|
|
Widget _buildCustomBottomNavigation() {
|
|
return AnimatedBuilder(
|
|
animation: _scaleAnimation,
|
|
builder: (context, child) {
|
|
return Transform.scale(
|
|
scale: _scaleAnimation.value,
|
|
child: Container(
|
|
margin: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.surface,
|
|
borderRadius: BorderRadius.circular(25),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: AppColors.primary.withOpacity(0.15),
|
|
blurRadius: 20,
|
|
offset: const Offset(0, 8),
|
|
spreadRadius: 0,
|
|
),
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(25),
|
|
child: SizedBox(
|
|
height: 70,
|
|
child: Row(
|
|
children:
|
|
_navigationItems.asMap().entries.map((entry) {
|
|
final index = entry.key;
|
|
final item = entry.value;
|
|
final isSelected = _currentIndex == index;
|
|
|
|
return Expanded(
|
|
child: _buildNavItem(item, index, isSelected),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildNavItem(NavigationItem item, int index, bool isSelected) {
|
|
return GestureDetector(
|
|
onTap: () => _onItemTapped(index),
|
|
child: AnimatedContainer(
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeInOut,
|
|
padding: const EdgeInsets.symmetric(vertical: 6),
|
|
decoration: BoxDecoration(
|
|
gradient:
|
|
isSelected
|
|
? LinearGradient(
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
colors: [
|
|
item.color.withOpacity(0.1),
|
|
item.color.withOpacity(0.05),
|
|
],
|
|
)
|
|
: null,
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
AnimatedBuilder(
|
|
animation: _iconAnimationController,
|
|
builder: (context, child) {
|
|
final scale =
|
|
isSelected && _iconAnimationController.isAnimating
|
|
? _iconAnimations[index].value
|
|
: 1.0;
|
|
|
|
return Transform.scale(
|
|
scale: scale,
|
|
child: Container(
|
|
width: 36,
|
|
height: 36,
|
|
decoration: BoxDecoration(
|
|
color:
|
|
isSelected
|
|
? item.color.withOpacity(0.15)
|
|
: Colors.transparent,
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Icon(
|
|
isSelected ? item.activeIcon : item.icon,
|
|
color: isSelected ? item.color : Colors.grey[600],
|
|
size: 22,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
const SizedBox(height: 2),
|
|
AnimatedDefaultTextStyle(
|
|
duration: const Duration(milliseconds: 300),
|
|
style: TextStyle(
|
|
fontSize: isSelected ? 11 : 10,
|
|
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500,
|
|
color: isSelected ? item.color : Colors.grey[600],
|
|
),
|
|
child: Text(item.label),
|
|
),
|
|
if (isSelected) ...[
|
|
const SizedBox(height: 1),
|
|
AnimatedContainer(
|
|
duration: const Duration(milliseconds: 300),
|
|
width: 16,
|
|
height: 2,
|
|
decoration: BoxDecoration(
|
|
color: item.color,
|
|
borderRadius: BorderRadius.circular(1),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ===== PROFILE PAGE =====
|
|
// ===== PAGE PROFIL REDESIGNÉE =====
|
|
|
|
// Imports nécessaires (à ajuster selon votre structure)
|
|
// import 'package:your_app/controllers/auth_controller.dart';
|
|
// import 'package:your_app/utils/responsive_helper.dart';
|
|
// import 'package:your_app/utils/session_manager.dart';
|
|
// import 'package:your_app/utils/app_colors.dart';
|
|
// import 'package:your_app/pages/login_page.dart';
|
|
// ===== lib/pages/profile.dart AVEC SUPPORT ENTREPRISE COMPLET =====
|
|
|
|
class ProfilePage extends StatefulWidget {
|
|
const ProfilePage({super.key});
|
|
|
|
@override
|
|
State<ProfilePage> createState() => _ProfilePageState();
|
|
}
|
|
|
|
class _ProfilePageState extends State<ProfilePage>
|
|
with TickerProviderStateMixin {
|
|
late AnimationController _animationController;
|
|
late Animation<double> _fadeAnimation;
|
|
late Animation<Offset> _slideAnimation;
|
|
|
|
// Constants API
|
|
static const String apiBaseUrl = 'https://api.live.wortis.cg/tpe';
|
|
static const Map<String, String> apiHeaders = {
|
|
'Content-Type': 'application/json',
|
|
};
|
|
|
|
// NOUVEAU: Couleur entreprise
|
|
static const Color enterpriseColor = Color(0xFF8B5CF6);
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_initializeOrientations();
|
|
_setupAnimations();
|
|
}
|
|
|
|
void _initializeOrientations() {
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
ResponsiveHelper.initializeOrientations(context);
|
|
});
|
|
}
|
|
|
|
void _setupAnimations() {
|
|
_animationController = AnimationController(
|
|
duration: Duration(milliseconds: 1000),
|
|
vsync: this,
|
|
);
|
|
|
|
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
|
CurvedAnimation(parent: _animationController, curve: Curves.easeOut),
|
|
);
|
|
|
|
_slideAnimation = Tween<Offset>(
|
|
begin: Offset(0, 0.3),
|
|
end: Offset.zero,
|
|
).animate(
|
|
CurvedAnimation(parent: _animationController, curve: Curves.easeOutBack),
|
|
);
|
|
|
|
_animationController.forward();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_animationController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isSmallScreen = context.isSmallPhone;
|
|
final isMediumScreen = context.isPhone && !context.isSmallPhone;
|
|
final isTablet = context.isTablet;
|
|
|
|
return Scaffold(
|
|
backgroundColor: AppColors.background,
|
|
body: Stack(
|
|
children: [
|
|
// Gradient d'arrière-plan
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
colors: [
|
|
AppColors.primary.withOpacity(0.05),
|
|
AppColors.background,
|
|
AppColors.background,
|
|
],
|
|
stops: [0.0, 0.3, 1.0],
|
|
),
|
|
),
|
|
),
|
|
|
|
// Contenu principal
|
|
FadeTransition(
|
|
opacity: _fadeAnimation,
|
|
child: SlideTransition(
|
|
position: _slideAnimation,
|
|
child: CustomScrollView(
|
|
physics: BouncingScrollPhysics(),
|
|
slivers: [
|
|
SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: context.responsivePadding(
|
|
phone: EdgeInsets.fromLTRB(12, 40, 12, 120),
|
|
tablet: EdgeInsets.fromLTRB(20, 60, 20, 120),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
// Header Profil avec support entreprise
|
|
_buildResponsiveProfileHeader(
|
|
isSmallScreen,
|
|
isMediumScreen,
|
|
isTablet,
|
|
),
|
|
SizedBox(height: isSmallScreen ? 16 : 24),
|
|
|
|
// Badge commission disponible
|
|
_buildCommissionBadge(),
|
|
|
|
// Section Actions
|
|
_buildResponsiveActionsSection(
|
|
isSmallScreen,
|
|
isMediumScreen,
|
|
isTablet,
|
|
),
|
|
SizedBox(height: isSmallScreen ? 16 : 24),
|
|
|
|
// Section Paramètres
|
|
_buildSettingsSection(
|
|
isSmallScreen,
|
|
isMediumScreen,
|
|
isTablet,
|
|
),
|
|
|
|
SizedBox(height: isSmallScreen ? 16 : 24),
|
|
|
|
// Bouton déconnexion
|
|
_buildResponsiveLogoutButton(
|
|
context,
|
|
isSmallScreen,
|
|
isMediumScreen,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// ============= HEADER PROFIL AVEC SUPPORT ENTREPRISE =============
|
|
Widget _buildResponsiveProfileHeader(
|
|
bool isSmallScreen,
|
|
bool isMediumScreen,
|
|
bool isTablet,
|
|
) {
|
|
return Consumer<AuthController>(
|
|
builder: (context, authController, child) {
|
|
final isEnterprise = authController.isEnterpriseMember;
|
|
final enterprise = authController.enterprise;
|
|
|
|
return Container(
|
|
width: double.infinity,
|
|
padding: EdgeInsets.all(
|
|
isSmallScreen ? 20 : (isMediumScreen ? 24 : 28),
|
|
),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
colors:
|
|
isEnterprise
|
|
? [
|
|
enterpriseColor,
|
|
enterpriseColor.withOpacity(0.8),
|
|
enterpriseColor.withOpacity(0.6),
|
|
]
|
|
: [
|
|
AppColors.primary,
|
|
AppColors.primary.withOpacity(0.8),
|
|
AppColors.secondary,
|
|
],
|
|
stops: [0.0, 0.6, 1.0],
|
|
),
|
|
borderRadius: BorderRadius.circular(isSmallScreen ? 20 : 24),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: (isEnterprise ? enterpriseColor : AppColors.primary)
|
|
.withOpacity(0.3),
|
|
blurRadius: isSmallScreen ? 15 : 25,
|
|
offset: Offset(0, isSmallScreen ? 8 : 12),
|
|
spreadRadius: isSmallScreen ? 1 : 2,
|
|
),
|
|
BoxShadow(
|
|
color: Colors.white.withOpacity(0.1),
|
|
blurRadius: 5,
|
|
offset: Offset(-2, -2),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
children: [
|
|
// Avatar avec badge entreprise
|
|
Stack(
|
|
children: [
|
|
Container(
|
|
width: context.responsiveWidth(phone: 80, tablet: 100),
|
|
height: context.responsiveHeight(phone: 80, tablet: 100),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
Colors.white.withOpacity(0.3),
|
|
Colors.white.withOpacity(0.1),
|
|
],
|
|
),
|
|
borderRadius: BorderRadius.circular(
|
|
isSmallScreen ? 20 : 25,
|
|
),
|
|
border: Border.all(
|
|
color: Colors.white.withOpacity(0.4),
|
|
width: isSmallScreen ? 2 : 3,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 15,
|
|
offset: Offset(0, 5),
|
|
),
|
|
],
|
|
),
|
|
child: Icon(
|
|
isEnterprise ? Icons.business : Icons.person_outline,
|
|
size: isSmallScreen ? 40 : (isMediumScreen ? 45 : 50),
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
// Badge de statut
|
|
Positioned(
|
|
right: 0,
|
|
bottom: 0,
|
|
child: Container(
|
|
width: isSmallScreen ? 24 : 30,
|
|
height: isSmallScreen ? 24 : 30,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.success,
|
|
borderRadius: BorderRadius.circular(
|
|
isSmallScreen ? 12 : 15,
|
|
),
|
|
border: Border.all(
|
|
color: Colors.white,
|
|
width: isSmallScreen ? 2 : 3,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: AppColors.success.withOpacity(0.3),
|
|
blurRadius: 8,
|
|
offset: Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Icon(
|
|
Icons.check,
|
|
size: isSmallScreen ? 12 : 16,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: isSmallScreen ? 16 : 20),
|
|
|
|
// Badge type de compte
|
|
Container(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: isSmallScreen ? 10 : 12,
|
|
vertical: isSmallScreen ? 4 : 6,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.25),
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(
|
|
color: Colors.white.withOpacity(0.3),
|
|
width: 1,
|
|
),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(
|
|
isEnterprise ? Icons.business : Icons.person,
|
|
size: isSmallScreen ? 12 : 14,
|
|
color: Colors.white.withOpacity(0.9),
|
|
),
|
|
SizedBox(width: isSmallScreen ? 4 : 6),
|
|
Text(
|
|
isEnterprise ? 'Compte Entreprise' : 'Agent Individuel',
|
|
style: TextStyle(
|
|
color: Colors.white.withOpacity(0.95),
|
|
fontSize: isSmallScreen ? 10 : 12,
|
|
fontWeight: FontWeight.w600,
|
|
letterSpacing: 0.3,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(height: isSmallScreen ? 10 : 12),
|
|
|
|
// Nom de l'agent
|
|
ConstrainedBox(
|
|
constraints: BoxConstraints(maxWidth: double.infinity),
|
|
child: FittedBox(
|
|
fit: BoxFit.scaleDown,
|
|
child: Text(
|
|
authController.agentName ?? 'Agent Wortis',
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 22 : (isMediumScreen ? 25 : 28),
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
letterSpacing: 0.5,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
maxLines: 1,
|
|
),
|
|
),
|
|
),
|
|
|
|
// Nom de l'entreprise si membre
|
|
if (isEnterprise && enterprise?.nomEntreprise != null) ...[
|
|
SizedBox(height: isSmallScreen ? 6 : 8),
|
|
Container(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: isSmallScreen ? 12 : 16,
|
|
vertical: isSmallScreen ? 6 : 8,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.15),
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(
|
|
color: Colors.white.withOpacity(0.25),
|
|
width: 1,
|
|
),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(
|
|
Icons.apartment,
|
|
size: isSmallScreen ? 12 : 14,
|
|
color: Colors.white.withOpacity(0.9),
|
|
),
|
|
SizedBox(width: isSmallScreen ? 6 : 8),
|
|
Flexible(
|
|
child: Text(
|
|
enterprise!.nomEntreprise,
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 13 : 15,
|
|
color: Colors.white.withOpacity(0.95),
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
maxLines: 1,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
SizedBox(height: isSmallScreen ? 8 : 12),
|
|
|
|
// Badge ID
|
|
Container(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: isSmallScreen ? 16 : 20,
|
|
vertical: isSmallScreen ? 8 : 10,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(25),
|
|
border: Border.all(
|
|
color: Colors.white.withOpacity(0.3),
|
|
width: 1,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 10,
|
|
offset: Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(
|
|
Icons.badge,
|
|
size: isSmallScreen ? 14 : 16,
|
|
color: Colors.white.withOpacity(0.9),
|
|
),
|
|
SizedBox(width: isSmallScreen ? 6 : 8),
|
|
Flexible(
|
|
child: Text(
|
|
'ID: ${authController.agentId ?? "AGENT001"}',
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 14 : 16,
|
|
color: Colors.white.withOpacity(0.95),
|
|
fontWeight: FontWeight.w600,
|
|
letterSpacing: 0.5,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(height: isSmallScreen ? 12 : 16),
|
|
|
|
// Informations entreprise supplémentaires si membre
|
|
if (isEnterprise && enterprise != null) ...[
|
|
Container(
|
|
padding: EdgeInsets.all(isSmallScreen ? 12 : 14),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.15),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: Colors.white.withOpacity(0.25),
|
|
width: 1,
|
|
),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Column(
|
|
children: [
|
|
Text(
|
|
'${(enterprise as dynamic).nombreMembres ?? enterprise.membresIds.length}',
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 16 : 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
SizedBox(height: 2),
|
|
Text(
|
|
'Membres',
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 10 : 11,
|
|
color: Colors.white.withOpacity(0.8),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Builder(
|
|
builder: (context) {
|
|
// Essayer d'accéder au domaine d'activité de manière sûre
|
|
final domaineActivite =
|
|
(enterprise as dynamic).domaineActivite;
|
|
|
|
if (domaineActivite != null &&
|
|
domaineActivite.toString().isNotEmpty) {
|
|
return Row(
|
|
children: [
|
|
SizedBox(width: isSmallScreen ? 16 : 24),
|
|
Container(
|
|
width: 1,
|
|
height: 30,
|
|
color: Colors.white.withOpacity(0.3),
|
|
),
|
|
SizedBox(width: isSmallScreen ? 16 : 24),
|
|
Flexible(
|
|
child: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Secteur',
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 10 : 11,
|
|
color: Colors.white.withOpacity(0.8),
|
|
),
|
|
),
|
|
SizedBox(height: 2),
|
|
Text(
|
|
domaineActivite.toString(),
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 12 : 13,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.white,
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
return SizedBox.shrink();
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(height: isSmallScreen ? 12 : 16),
|
|
],
|
|
|
|
// Indicateur de statut
|
|
Container(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: isSmallScreen ? 12 : 16,
|
|
vertical: isSmallScreen ? 4 : 6,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.success.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(
|
|
color: AppColors.success.withOpacity(0.3),
|
|
width: 1,
|
|
),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
width: isSmallScreen ? 6 : 8,
|
|
height: isSmallScreen ? 6 : 8,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.success,
|
|
borderRadius: BorderRadius.circular(
|
|
isSmallScreen ? 3 : 4,
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: isSmallScreen ? 6 : 8),
|
|
Text(
|
|
isEnterprise ? 'Membre Actif' : 'Agent Actif',
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 12 : 14,
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
// ============= NOTIFICATION BADGE COMMISSION =============
|
|
Widget _buildCommissionBadge() {
|
|
final authController = Provider.of<AuthController>(context, listen: false);
|
|
|
|
return FutureBuilder<Map<String, dynamic>>(
|
|
future: _getCommissionBalance(authController.agentId ?? ''),
|
|
builder: (context, snapshot) {
|
|
if (!snapshot.hasData || !snapshot.data!['success']) {
|
|
return SizedBox.shrink();
|
|
}
|
|
|
|
final availableCommission =
|
|
snapshot.data!['commission_disponible'] ?? 0.0;
|
|
|
|
if (availableCommission <= 0) {
|
|
return SizedBox.shrink();
|
|
}
|
|
|
|
return Container(
|
|
margin: EdgeInsets.symmetric(horizontal: 0, vertical: 8),
|
|
child: GestureDetector(
|
|
onTap:
|
|
() => _showCommissionRechargeDialog(
|
|
context,
|
|
availableCommission,
|
|
snapshot.data!,
|
|
),
|
|
child: Container(
|
|
padding: EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
AppColors.success,
|
|
AppColors.success.withOpacity(0.8),
|
|
],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: AppColors.success.withOpacity(0.3),
|
|
blurRadius: 15,
|
|
offset: Offset(0, 5),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Icon(
|
|
Icons.monetization_on,
|
|
color: Colors.white,
|
|
size: 24,
|
|
),
|
|
),
|
|
SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'${availableCommission.toStringAsFixed(0)} XAF de commission disponible',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
Text(
|
|
'Transférez vers votre solde principal',
|
|
style: TextStyle(
|
|
color: Colors.white.withOpacity(0.9),
|
|
fontSize: 13,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Icon(Icons.arrow_forward_ios, color: Colors.white, size: 16),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
// ============= SECTION ACTIONS =============
|
|
Widget _buildResponsiveActionsSection(
|
|
bool isSmallScreen,
|
|
bool isMediumScreen,
|
|
bool isTablet,
|
|
) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: EdgeInsets.only(left: 4, bottom: isSmallScreen ? 12 : 16),
|
|
child: Text(
|
|
'Actions Rapides',
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 18 : (isMediumScreen ? 19 : 20),
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
),
|
|
|
|
_buildResponsiveMenuItem(
|
|
'Demander une recharge',
|
|
'Augmenter votre solde disponible',
|
|
Icons.add_circle_outline,
|
|
AppColors.accent,
|
|
() => _showRechargeOptionsDialog(context),
|
|
isPrimary: true,
|
|
delay: 100,
|
|
isSmallScreen: isSmallScreen,
|
|
isMediumScreen: isMediumScreen,
|
|
),
|
|
|
|
_buildResponsiveMenuItem(
|
|
'Historique des recharges',
|
|
'Suivez vos demandes de recharge',
|
|
Icons.history,
|
|
AppColors.secondary,
|
|
() => _showRechargeHistoryDialog(context),
|
|
delay: 200,
|
|
isSmallScreen: isSmallScreen,
|
|
isMediumScreen: isMediumScreen,
|
|
),
|
|
|
|
_buildResponsiveMenuItem(
|
|
'Historique des commissions',
|
|
'Consultez vos transferts de commission',
|
|
Icons.trending_up,
|
|
AppColors.success,
|
|
() => _showCommissionHistoryDialog(context),
|
|
delay: 250,
|
|
isSmallScreen: isSmallScreen,
|
|
isMediumScreen: isMediumScreen,
|
|
),
|
|
|
|
_buildResponsiveMenuItem(
|
|
'Support',
|
|
'Obtenez de l\'aide rapidement',
|
|
Icons.headset_mic,
|
|
AppColors.purple,
|
|
() => _showSupportDialog(context),
|
|
delay: 300,
|
|
isSmallScreen: isSmallScreen,
|
|
isMediumScreen: isMediumScreen,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildResponsiveMenuItem(
|
|
String title,
|
|
String subtitle,
|
|
IconData icon,
|
|
Color color,
|
|
VoidCallback onTap, {
|
|
bool isPrimary = false,
|
|
int delay = 0,
|
|
bool isSmallScreen = false,
|
|
bool isMediumScreen = false,
|
|
}) {
|
|
return TweenAnimationBuilder<double>(
|
|
duration: Duration(milliseconds: 600 + delay),
|
|
tween: Tween(begin: 0.0, end: 1.0),
|
|
curve: Curves.easeOutBack,
|
|
builder: (context, animation, child) {
|
|
final clampedAnimation = animation.clamp(0.0, 1.0);
|
|
|
|
return Transform.translate(
|
|
offset: Offset(50 * (1 - clampedAnimation), 0),
|
|
child: Opacity(
|
|
opacity: clampedAnimation,
|
|
child: Container(
|
|
margin: EdgeInsets.only(bottom: isSmallScreen ? 12 : 16),
|
|
decoration: BoxDecoration(
|
|
gradient:
|
|
isPrimary
|
|
? LinearGradient(
|
|
colors: [color, color.withOpacity(0.8)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
)
|
|
: null,
|
|
color: isPrimary ? null : AppColors.surface,
|
|
borderRadius: BorderRadius.circular(isSmallScreen ? 16 : 20),
|
|
border: Border.all(
|
|
color:
|
|
isPrimary ? Colors.transparent : color.withOpacity(0.15),
|
|
width: 2,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color:
|
|
isPrimary
|
|
? color.withOpacity(0.3)
|
|
: Colors.black.withOpacity(0.08),
|
|
blurRadius:
|
|
isPrimary
|
|
? (isSmallScreen ? 15 : 20)
|
|
: (isSmallScreen ? 10 : 15),
|
|
offset: Offset(
|
|
0,
|
|
isPrimary
|
|
? (isSmallScreen ? 6 : 8)
|
|
: (isSmallScreen ? 3 : 4),
|
|
),
|
|
spreadRadius: isPrimary ? (isSmallScreen ? 1 : 2) : 0,
|
|
),
|
|
if (!isPrimary)
|
|
BoxShadow(
|
|
color: Colors.white.withOpacity(0.9),
|
|
blurRadius: 3,
|
|
offset: Offset(-2, -2),
|
|
),
|
|
],
|
|
),
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
onTap: onTap,
|
|
borderRadius: BorderRadius.circular(isSmallScreen ? 16 : 20),
|
|
child: Padding(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: isSmallScreen ? 16 : 20,
|
|
vertical: isSmallScreen ? 16 : 20,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width:
|
|
isSmallScreen ? 48 : (isMediumScreen ? 52 : 56),
|
|
height:
|
|
isSmallScreen ? 48 : (isMediumScreen ? 52 : 56),
|
|
decoration: BoxDecoration(
|
|
color:
|
|
isPrimary
|
|
? Colors.white.withOpacity(0.2)
|
|
: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(
|
|
isSmallScreen ? 12 : 16,
|
|
),
|
|
border: Border.all(
|
|
color:
|
|
isPrimary
|
|
? Colors.white.withOpacity(0.3)
|
|
: color.withOpacity(0.2),
|
|
width: 1,
|
|
),
|
|
),
|
|
child: Icon(
|
|
icon,
|
|
color: isPrimary ? Colors.white : color,
|
|
size:
|
|
isSmallScreen ? 24 : (isMediumScreen ? 26 : 28),
|
|
),
|
|
),
|
|
SizedBox(width: isSmallScreen ? 12 : 16),
|
|
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: TextStyle(
|
|
fontSize:
|
|
isSmallScreen
|
|
? 14
|
|
: (isMediumScreen ? 15 : 16),
|
|
fontWeight: FontWeight.w700,
|
|
color:
|
|
isPrimary ? Colors.white : Colors.black87,
|
|
),
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
SizedBox(height: 4),
|
|
Text(
|
|
subtitle,
|
|
style: TextStyle(
|
|
fontSize:
|
|
isSmallScreen
|
|
? 11
|
|
: (isMediumScreen ? 12 : 13),
|
|
color:
|
|
isPrimary
|
|
? Colors.white.withOpacity(0.8)
|
|
: Colors.grey[600],
|
|
fontWeight: FontWeight.w400,
|
|
),
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
Container(
|
|
width: isSmallScreen ? 28 : 32,
|
|
height: isSmallScreen ? 28 : 32,
|
|
decoration: BoxDecoration(
|
|
color:
|
|
isPrimary
|
|
? Colors.white.withOpacity(0.2)
|
|
: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(
|
|
isSmallScreen ? 8 : 10,
|
|
),
|
|
),
|
|
child: Icon(
|
|
Icons.arrow_forward_ios,
|
|
color: isPrimary ? Colors.white : color,
|
|
size: isSmallScreen ? 14 : 16,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
// ============= BOUTON DÉCONNEXION =============
|
|
Widget _buildResponsiveLogoutButton(
|
|
BuildContext context,
|
|
bool isSmallScreen,
|
|
bool isMediumScreen,
|
|
) {
|
|
return TweenAnimationBuilder<double>(
|
|
duration: Duration(milliseconds: 800),
|
|
tween: Tween(begin: 0.0, end: 1.0),
|
|
curve: Curves.easeOutBack,
|
|
builder: (context, animation, child) {
|
|
return Transform.scale(
|
|
scale: animation,
|
|
child: Container(
|
|
width: double.infinity,
|
|
height: isSmallScreen ? 50 : (isMediumScreen ? 53 : 56),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [Colors.red[400]!, Colors.red[500]!],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(isSmallScreen ? 14 : 16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.red.withOpacity(0.3),
|
|
blurRadius: isSmallScreen ? 15 : 20,
|
|
offset: Offset(0, isSmallScreen ? 6 : 8),
|
|
spreadRadius: isSmallScreen ? 1 : 2,
|
|
),
|
|
],
|
|
),
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
onTap: () => _logout(context),
|
|
borderRadius: BorderRadius.circular(isSmallScreen ? 14 : 16),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.logout,
|
|
color: Colors.white,
|
|
size: isSmallScreen ? 18 : 22,
|
|
),
|
|
SizedBox(width: isSmallScreen ? 8 : 12),
|
|
Text(
|
|
'Se Déconnecter',
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 14 : 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.white,
|
|
letterSpacing: 0.5,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
// ============= APIs COMMISSION =============
|
|
Future<Map<String, dynamic>> _getCommissionBalance(String agentId) async {
|
|
if (agentId.isEmpty) {
|
|
return {'success': false, 'commission_disponible': 0.0};
|
|
}
|
|
|
|
try {
|
|
final baseUrl = AuthController.baseUrl ?? apiBaseUrl;
|
|
final url = '$baseUrl/agent/$agentId/commission/balance';
|
|
|
|
print('Récupération commission - URL: $url');
|
|
|
|
final response = await http.get(
|
|
Uri.parse(url),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
},
|
|
);
|
|
|
|
print('Réponse balance commission - Status: ${response.statusCode}');
|
|
print('Réponse balance commission - Body: ${response.body}');
|
|
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
if (data['success'] == true && data.containsKey('commission')) {
|
|
return {
|
|
'success': true,
|
|
'commission_disponible':
|
|
(data['commission']['commission_disponible'] ?? 0.0).toDouble(),
|
|
'commission_totale_gagnee':
|
|
(data['commission']['commission_totale_gagnee'] ?? 0.0)
|
|
.toDouble(),
|
|
'commission_totale_retiree':
|
|
(data['commission']['commission_totale_retiree'] ?? 0.0)
|
|
.toDouble(),
|
|
'nombre_transactions':
|
|
data['commission']['nombre_transactions'] ?? 0,
|
|
'derniers_retraits': data['commission']['derniers_retraits'] ?? [],
|
|
};
|
|
}
|
|
}
|
|
|
|
return {'success': false, 'commission_disponible': 0.0};
|
|
} catch (e) {
|
|
print('Erreur récupération commission: $e');
|
|
return {'success': false, 'commission_disponible': 0.0};
|
|
}
|
|
}
|
|
|
|
Future<Map<String, dynamic>> _getCommissionHistory(String agentId) async {
|
|
try {
|
|
final response = await http.get(
|
|
Uri.parse(
|
|
'${AuthController.baseUrl}/agent/$agentId/commission/history',
|
|
),
|
|
headers: AuthController.apiHeaders,
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
return data;
|
|
} else {
|
|
return {'success': false, 'message': 'Erreur lors du chargement'};
|
|
}
|
|
} catch (e) {
|
|
return {'success': false, 'message': 'Erreur de connexion'};
|
|
}
|
|
}
|
|
|
|
// ============= DIALOGS RECHARGE =============
|
|
Future<void> _showRechargeOptionsDialog(BuildContext context) async {
|
|
final authController = Provider.of<AuthController>(context, listen: false);
|
|
|
|
await showDialog(
|
|
context: context,
|
|
builder:
|
|
(context) => Dialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(24),
|
|
),
|
|
elevation: 10,
|
|
child: Container(
|
|
width: MediaQuery.of(context).size.width * 0.85,
|
|
constraints: BoxConstraints(maxWidth: 400),
|
|
child: FutureBuilder<Map<String, dynamic>>(
|
|
future: _getCommissionBalance(authController.agentId ?? ''),
|
|
builder: (context, snapshot) {
|
|
double availableCommission = 0.0;
|
|
bool isLoading = !snapshot.hasData;
|
|
Map<String, dynamic> commissionData = {};
|
|
|
|
if (snapshot.hasData && snapshot.data!['success']) {
|
|
availableCommission =
|
|
snapshot.data!['commission_disponible'] ?? 0.0;
|
|
commissionData = snapshot.data!;
|
|
}
|
|
|
|
return Container(
|
|
padding: EdgeInsets.all(24),
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// Header avec icône
|
|
Container(
|
|
width: 64,
|
|
height: 64,
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [AppColors.accent, AppColors.primary],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(20),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: AppColors.accent.withOpacity(0.3),
|
|
blurRadius: 15,
|
|
offset: Offset(0, 5),
|
|
),
|
|
],
|
|
),
|
|
child: Icon(
|
|
Icons.account_balance_wallet,
|
|
color: Colors.white,
|
|
size: 32,
|
|
),
|
|
),
|
|
SizedBox(height: 16),
|
|
|
|
Text(
|
|
'Recharger mon compte',
|
|
style: TextStyle(
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 8),
|
|
Text(
|
|
'Choisissez votre mode de recharge',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color: Colors.grey[600],
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
SizedBox(height: 24),
|
|
|
|
// Affichage du solde et commission
|
|
Container(
|
|
padding: EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
AppColors.primary.withOpacity(0.05),
|
|
AppColors.secondary.withOpacity(0.05),
|
|
],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(
|
|
color: AppColors.primary.withOpacity(0.15),
|
|
width: 1,
|
|
),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
// Solde actuel
|
|
Row(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.primary.withOpacity(
|
|
0.1,
|
|
),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Icon(
|
|
Icons.account_balance_wallet,
|
|
color: AppColors.primary,
|
|
size: 20,
|
|
),
|
|
),
|
|
SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Solde actuel',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: Colors.grey[600],
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
SizedBox(height: 2),
|
|
Text(
|
|
'${authController.balance.toStringAsFixed(0)} XAF',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: AppColors.primary,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 16),
|
|
Container(height: 1, color: Colors.grey[200]),
|
|
SizedBox(height: 16),
|
|
|
|
// Commission disponible
|
|
Row(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.success.withOpacity(
|
|
0.1,
|
|
),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Icon(
|
|
Icons.trending_up,
|
|
color: AppColors.success,
|
|
size: 20,
|
|
),
|
|
),
|
|
SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Commission disponible',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: Colors.grey[600],
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
SizedBox(height: 2),
|
|
if (isLoading)
|
|
Container(
|
|
width: 80,
|
|
height: 18,
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey[300],
|
|
borderRadius:
|
|
BorderRadius.circular(4),
|
|
),
|
|
)
|
|
else
|
|
Text(
|
|
'${availableCommission.toStringAsFixed(0)} XAF',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: AppColors.success,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
// Statistiques de commission si disponibles
|
|
if (!isLoading &&
|
|
commissionData.isNotEmpty) ...[
|
|
SizedBox(height: 12),
|
|
Container(
|
|
padding: EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey[50],
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Column(
|
|
children: [
|
|
Text(
|
|
'${commissionData['commission_totale_gagnee']?.toStringAsFixed(0) ?? '0'}',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
color: AppColors.success,
|
|
),
|
|
),
|
|
Text(
|
|
'Gagnée',
|
|
style: TextStyle(
|
|
fontSize: 11,
|
|
color: Colors.grey[600],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Column(
|
|
children: [
|
|
Text(
|
|
'${commissionData['commission_totale_retiree']?.toStringAsFixed(0) ?? '0'}',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.orange,
|
|
),
|
|
),
|
|
Text(
|
|
'Utilisée',
|
|
style: TextStyle(
|
|
fontSize: 11,
|
|
color: Colors.grey[600],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Column(
|
|
children: [
|
|
Text(
|
|
'${commissionData['nombre_transactions'] ?? 0}',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
color: AppColors.primary,
|
|
),
|
|
),
|
|
Text(
|
|
'Transactions',
|
|
style: TextStyle(
|
|
fontSize: 11,
|
|
color: Colors.grey[600],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
SizedBox(height: 24),
|
|
|
|
// Boutons d'action
|
|
Row(
|
|
children: [
|
|
// Demander une recharge
|
|
Expanded(
|
|
child: _buildRechargeOptionButton(
|
|
context: context,
|
|
title: 'Demander\nune recharge',
|
|
subtitle: 'Rechargeur requis',
|
|
icon: Icons.add_circle_outline,
|
|
color: AppColors.secondary,
|
|
onTap: () {
|
|
Navigator.of(context).pop();
|
|
_showRechargeRequestDialog(context);
|
|
},
|
|
),
|
|
),
|
|
SizedBox(width: 12),
|
|
|
|
// Utiliser la commission
|
|
Expanded(
|
|
child: _buildRechargeOptionButton(
|
|
context: context,
|
|
title: 'Utiliser la\ncommission',
|
|
subtitle: 'Transfert instantané',
|
|
icon: Icons.monetization_on,
|
|
color: AppColors.success,
|
|
onTap:
|
|
!isLoading && availableCommission > 0
|
|
? () {
|
|
Navigator.of(context).pop();
|
|
_showCommissionRechargeDialog(
|
|
context,
|
|
availableCommission,
|
|
commissionData,
|
|
);
|
|
}
|
|
: null,
|
|
isDisabled:
|
|
isLoading || availableCommission <= 0,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 16),
|
|
|
|
TextButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
child: Text(
|
|
'Fermer',
|
|
style: TextStyle(
|
|
color: Colors.grey[600],
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildRechargeOptionButton({
|
|
required BuildContext context,
|
|
required String title,
|
|
required String subtitle,
|
|
required IconData icon,
|
|
required Color color,
|
|
required VoidCallback? onTap,
|
|
bool isDisabled = false,
|
|
}) {
|
|
return GestureDetector(
|
|
onTap: isDisabled ? null : onTap,
|
|
child: AnimatedContainer(
|
|
duration: Duration(milliseconds: 200),
|
|
padding: EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: isDisabled ? Colors.grey[100] : Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(
|
|
color: isDisabled ? Colors.grey[300]! : color.withOpacity(0.3),
|
|
width: 2,
|
|
),
|
|
boxShadow:
|
|
isDisabled
|
|
? []
|
|
: [
|
|
BoxShadow(
|
|
color: color.withOpacity(0.15),
|
|
blurRadius: 10,
|
|
offset: Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
width: 48,
|
|
height: 48,
|
|
decoration: BoxDecoration(
|
|
color: isDisabled ? Colors.grey[300] : color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Icon(
|
|
icon,
|
|
color: isDisabled ? Colors.grey[500] : color,
|
|
size: 24,
|
|
),
|
|
),
|
|
SizedBox(height: 12),
|
|
Text(
|
|
title,
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
color: isDisabled ? Colors.grey[500] : Colors.black87,
|
|
height: 1.2,
|
|
),
|
|
),
|
|
SizedBox(height: 4),
|
|
Text(
|
|
subtitle,
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: isDisabled ? Colors.grey[400] : Colors.grey[600],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Dialog pour demander une recharge traditionnelle
|
|
Future<void> _showRechargeRequestDialog(BuildContext context) async {
|
|
final amountController = TextEditingController();
|
|
final pinController = TextEditingController();
|
|
|
|
final screenSize = MediaQuery.of(context).size;
|
|
final isSmallScreen = screenSize.height < 600;
|
|
|
|
await showDialog(
|
|
context: context,
|
|
builder:
|
|
(context) => Dialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(isSmallScreen ? 16 : 24),
|
|
),
|
|
elevation: 10,
|
|
child: ConstrainedBox(
|
|
constraints: BoxConstraints(
|
|
maxWidth: 400,
|
|
maxHeight: screenSize.height * (isSmallScreen ? 0.9 : 0.8),
|
|
),
|
|
child: Container(
|
|
width: screenSize.width * 0.85,
|
|
padding: EdgeInsets.all(isSmallScreen ? 16 : 24),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Flexible(
|
|
child: SingleChildScrollView(
|
|
physics: BouncingScrollPhysics(),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// Header
|
|
Container(
|
|
width: isSmallScreen ? 48 : 64,
|
|
height: isSmallScreen ? 48 : 64,
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
AppColors.secondary,
|
|
AppColors.accent,
|
|
],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(
|
|
isSmallScreen ? 16 : 20,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: AppColors.secondary.withOpacity(0.3),
|
|
blurRadius: isSmallScreen ? 10 : 15,
|
|
offset: Offset(0, isSmallScreen ? 3 : 5),
|
|
),
|
|
],
|
|
),
|
|
child: Icon(
|
|
Icons.add_circle_outline,
|
|
color: Colors.white,
|
|
size: isSmallScreen ? 24 : 32,
|
|
),
|
|
),
|
|
SizedBox(height: isSmallScreen ? 12 : 16),
|
|
|
|
Text(
|
|
'Demander une recharge',
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 18 : 22,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black87,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
SizedBox(height: isSmallScreen ? 6 : 8),
|
|
|
|
Text(
|
|
'Un rechargeur validera votre demande',
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 12 : 14,
|
|
color: Colors.grey[600],
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
SizedBox(height: isSmallScreen ? 16 : 20),
|
|
|
|
// Champ montant
|
|
TextField(
|
|
controller: amountController,
|
|
keyboardType: TextInputType.number,
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 14 : 16,
|
|
),
|
|
decoration: InputDecoration(
|
|
labelText: 'Montant à recharger',
|
|
hintText: 'Ex: 10000',
|
|
labelStyle: TextStyle(
|
|
fontSize: isSmallScreen ? 12 : 14,
|
|
),
|
|
hintStyle: TextStyle(
|
|
fontSize: isSmallScreen ? 12 : 14,
|
|
),
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: isSmallScreen ? 12 : 16,
|
|
vertical: isSmallScreen ? 12 : 16,
|
|
),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide(
|
|
color: AppColors.secondary.withOpacity(0.3),
|
|
),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide(
|
|
color: AppColors.secondary,
|
|
width: 2,
|
|
),
|
|
),
|
|
prefixIcon: Icon(
|
|
Icons.attach_money,
|
|
color: AppColors.secondary,
|
|
size: isSmallScreen ? 20 : 24,
|
|
),
|
|
suffixText: 'XAF',
|
|
suffixStyle: TextStyle(
|
|
color: AppColors.secondary,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: isSmallScreen ? 12 : 14,
|
|
),
|
|
),
|
|
autofocus: !isSmallScreen,
|
|
),
|
|
SizedBox(height: isSmallScreen ? 12 : 16),
|
|
|
|
// Champ PIN
|
|
TextField(
|
|
controller: pinController,
|
|
keyboardType: TextInputType.number,
|
|
obscureText: true,
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 14 : 16,
|
|
),
|
|
decoration: InputDecoration(
|
|
labelText: 'Code PIN',
|
|
hintText: '****',
|
|
labelStyle: TextStyle(
|
|
fontSize: isSmallScreen ? 12 : 14,
|
|
),
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: isSmallScreen ? 12 : 16,
|
|
vertical: isSmallScreen ? 12 : 16,
|
|
),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide(
|
|
color: AppColors.secondary.withOpacity(0.3),
|
|
),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide(
|
|
color: AppColors.secondary,
|
|
width: 2,
|
|
),
|
|
),
|
|
prefixIcon: Icon(
|
|
Icons.lock,
|
|
color: AppColors.secondary,
|
|
size: isSmallScreen ? 20 : 24,
|
|
),
|
|
),
|
|
),
|
|
SizedBox(height: isSmallScreen ? 12 : 16),
|
|
|
|
// Texte d'information
|
|
Text(
|
|
'Votre demande sera envoyée aux rechargeurs pour validation',
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 11 : 12,
|
|
color: Colors.grey[600],
|
|
fontStyle: FontStyle.italic,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
// Boutons fixes en bas
|
|
SizedBox(height: isSmallScreen ? 16 : 24),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: TextButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
style: TextButton.styleFrom(
|
|
padding: EdgeInsets.symmetric(
|
|
vertical: isSmallScreen ? 10 : 12,
|
|
),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
child: Text(
|
|
'Annuler',
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 14 : 16,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 12),
|
|
Expanded(
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
final amount = amountController.text.trim();
|
|
final pin = pinController.text.trim();
|
|
|
|
if (amount.isEmpty || pin.isEmpty) {
|
|
if (mounted) {
|
|
_showErrorSnackBar(
|
|
context,
|
|
'Veuillez remplir tous les champs',
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
|
|
final amountValue = double.tryParse(amount);
|
|
if (amountValue == null || amountValue <= 0) {
|
|
if (mounted) {
|
|
_showErrorSnackBar(
|
|
context,
|
|
'Montant invalide',
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
|
|
Navigator.of(context).pop();
|
|
_processRechargeRequest(context, amount, pin);
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppColors.secondary,
|
|
foregroundColor: Colors.white,
|
|
padding: EdgeInsets.symmetric(
|
|
vertical: isSmallScreen ? 10 : 12,
|
|
),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
elevation: 3,
|
|
),
|
|
child: Text(
|
|
'Envoyer',
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 14 : 16,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Dialog pour utiliser la commission
|
|
Future<void> _showCommissionRechargeDialog(
|
|
BuildContext context,
|
|
double availableCommission,
|
|
Map<String, dynamic> commissionData,
|
|
) async {
|
|
final amountController = TextEditingController();
|
|
|
|
await showDialog(
|
|
context: context,
|
|
builder:
|
|
(context) => Dialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(24),
|
|
),
|
|
elevation: 10,
|
|
child: Container(
|
|
width: MediaQuery.of(context).size.width * 0.85,
|
|
constraints: BoxConstraints(maxWidth: 400),
|
|
padding: EdgeInsets.all(24),
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// Header animé
|
|
TweenAnimationBuilder<double>(
|
|
duration: Duration(milliseconds: 600),
|
|
tween: Tween(begin: 0.0, end: 1.0),
|
|
builder: (context, value, child) {
|
|
return Transform.scale(
|
|
scale: value,
|
|
child: Container(
|
|
width: 64,
|
|
height: 64,
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [AppColors.success, Colors.green[300]!],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(20),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: AppColors.success.withOpacity(0.3),
|
|
blurRadius: 15,
|
|
offset: Offset(0, 5),
|
|
),
|
|
],
|
|
),
|
|
child: Icon(
|
|
Icons.monetization_on,
|
|
color: Colors.white,
|
|
size: 32,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
SizedBox(height: 16),
|
|
|
|
Text(
|
|
'Utiliser ma commission',
|
|
style: TextStyle(
|
|
fontSize: 22,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 8),
|
|
Text(
|
|
'Transférez vos commissions vers votre solde principal',
|
|
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
SizedBox(height: 20),
|
|
|
|
// Affichage de la commission disponible
|
|
Container(
|
|
padding: EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
AppColors.success.withOpacity(0.1),
|
|
AppColors.success.withOpacity(0.05),
|
|
],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: AppColors.success.withOpacity(0.3),
|
|
width: 1,
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.success.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Icon(
|
|
Icons.account_balance,
|
|
color: AppColors.success,
|
|
size: 18,
|
|
),
|
|
),
|
|
SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Commission disponible',
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
color: AppColors.success,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
SizedBox(height: 2),
|
|
Text(
|
|
'${availableCommission.toStringAsFixed(0)} XAF',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: AppColors.success,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(height: 20),
|
|
|
|
// Champ de saisie du montant
|
|
TextField(
|
|
controller: amountController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
labelText: 'Montant à transférer',
|
|
hintText:
|
|
'Ex: ${(availableCommission / 2).toStringAsFixed(0)}',
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide(
|
|
color: AppColors.success.withOpacity(0.3),
|
|
),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide(
|
|
color: AppColors.success,
|
|
width: 2,
|
|
),
|
|
),
|
|
prefixIcon: Icon(
|
|
Icons.account_balance_wallet,
|
|
color: AppColors.success,
|
|
),
|
|
suffixText: 'XAF',
|
|
suffixStyle: TextStyle(
|
|
color: AppColors.success,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
autofocus: true,
|
|
),
|
|
SizedBox(height: 12),
|
|
|
|
// Boutons montants rapides
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildQuickAmountButton(
|
|
context,
|
|
'25%',
|
|
(availableCommission * 0.25).toStringAsFixed(0),
|
|
amountController,
|
|
),
|
|
),
|
|
SizedBox(width: 8),
|
|
Expanded(
|
|
child: _buildQuickAmountButton(
|
|
context,
|
|
'50%',
|
|
(availableCommission * 0.5).toStringAsFixed(0),
|
|
amountController,
|
|
),
|
|
),
|
|
SizedBox(width: 8),
|
|
Expanded(
|
|
child: _buildQuickAmountButton(
|
|
context,
|
|
'Tout',
|
|
availableCommission.toStringAsFixed(0),
|
|
amountController,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 16),
|
|
|
|
Text(
|
|
'Ce montant sera instantanément ajouté à votre solde principal',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: Colors.grey[600],
|
|
fontStyle: FontStyle.italic,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
SizedBox(height: 24),
|
|
|
|
// Boutons d'action
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: TextButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
style: TextButton.styleFrom(
|
|
padding: EdgeInsets.symmetric(vertical: 12),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
child: Text(
|
|
'Annuler',
|
|
style: TextStyle(fontSize: 16),
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 12),
|
|
Expanded(
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
final amount = double.tryParse(
|
|
amountController.text,
|
|
);
|
|
if (amount != null &&
|
|
amount > 0 &&
|
|
amount <= availableCommission) {
|
|
Navigator.of(context).pop();
|
|
_processCommissionTransfer(context, amount);
|
|
} else {
|
|
_showErrorSnackBar(
|
|
context,
|
|
'Montant invalide (max: ${availableCommission.toStringAsFixed(0)} XAF)',
|
|
);
|
|
}
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppColors.success,
|
|
foregroundColor: Colors.white,
|
|
padding: EdgeInsets.symmetric(vertical: 12),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
elevation: 3,
|
|
),
|
|
child: Text(
|
|
'Transférer',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildQuickAmountButton(
|
|
BuildContext context,
|
|
String label,
|
|
String amount,
|
|
TextEditingController controller,
|
|
) {
|
|
return GestureDetector(
|
|
onTap: () {
|
|
controller.text = amount;
|
|
},
|
|
child: Container(
|
|
padding: EdgeInsets.symmetric(vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.success.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(
|
|
color: AppColors.success.withOpacity(0.3),
|
|
width: 1,
|
|
),
|
|
),
|
|
child: Text(
|
|
label,
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.success,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Dialog historique des commissions
|
|
Future<void> _showCommissionHistoryDialog(BuildContext context) async {
|
|
final authController = Provider.of<AuthController>(context, listen: false);
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder:
|
|
(context) => Dialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Container(
|
|
width: MediaQuery.of(context).size.width * 0.9,
|
|
height: MediaQuery.of(context).size.height * 0.8,
|
|
padding: EdgeInsets.all(24),
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Historique des commissions',
|
|
style: TextStyle(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
IconButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
icon: Icon(Icons.close, color: Colors.grey),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 16),
|
|
|
|
Expanded(
|
|
child: FutureBuilder<Map<String, dynamic>>(
|
|
future: _getCommissionHistory(
|
|
authController.agentId ?? '',
|
|
),
|
|
builder: (context, snapshot) {
|
|
if (snapshot.connectionState ==
|
|
ConnectionState.waiting) {
|
|
return Center(
|
|
child: CircularProgressIndicator(
|
|
valueColor: AlwaysStoppedAnimation<Color>(
|
|
AppColors.success,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (!snapshot.hasData || !snapshot.data!['success']) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.history,
|
|
size: 64,
|
|
color: Colors.grey[400],
|
|
),
|
|
SizedBox(height: 16),
|
|
Text(
|
|
'Aucun historique trouvé',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color: Colors.grey[600],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
final withdrawals =
|
|
snapshot.data!['historique']['retraits'] as List;
|
|
|
|
return ListView.builder(
|
|
itemCount: withdrawals.length,
|
|
itemBuilder: (context, index) {
|
|
final withdrawal = withdrawals[index];
|
|
final date = DateTime.parse(
|
|
withdrawal['date_creation'],
|
|
);
|
|
|
|
return Container(
|
|
margin: EdgeInsets.only(bottom: 12),
|
|
padding: EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.surface,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: AppColors.success.withOpacity(0.2),
|
|
width: 1,
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 48,
|
|
height: 48,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.success.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Icon(
|
|
Icons.trending_up,
|
|
color: AppColors.success,
|
|
size: 24,
|
|
),
|
|
),
|
|
SizedBox(width: 16),
|
|
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Transfert de commission',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 4),
|
|
Text(
|
|
withdrawal['description'] ??
|
|
'Transfert vers solde',
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
color: Colors.grey[600],
|
|
),
|
|
),
|
|
SizedBox(height: 4),
|
|
Text(
|
|
_formatDate(date),
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: Colors.grey[500],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
Text(
|
|
'+${withdrawal['montant'].toStringAsFixed(0)} XAF',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: AppColors.success,
|
|
),
|
|
),
|
|
SizedBox(height: 4),
|
|
Container(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: 8,
|
|
vertical: 4,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.success.withOpacity(
|
|
0.1,
|
|
),
|
|
borderRadius: BorderRadius.circular(
|
|
8,
|
|
),
|
|
),
|
|
child: Text(
|
|
'Terminé',
|
|
style: TextStyle(
|
|
fontSize: 11,
|
|
color: AppColors.success,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ============= TRAITEMENT DES REQUÊTES =============
|
|
Future<void> _processRechargeRequest(
|
|
BuildContext context,
|
|
String montant,
|
|
String pin,
|
|
) async {
|
|
if (montant.isEmpty || pin.isEmpty) {
|
|
_showErrorSnackBar(context, 'Veuillez remplir tous les champs');
|
|
return;
|
|
}
|
|
|
|
final amountValue = double.tryParse(montant);
|
|
if (amountValue == null || amountValue <= 0) {
|
|
_showErrorSnackBar(context, 'Montant invalide');
|
|
return;
|
|
}
|
|
|
|
if (pin.length < 4) {
|
|
_showErrorSnackBar(context, 'PIN doit contenir au moins 4 chiffres');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
final userId = await SessionManager().getUserId();
|
|
if (userId == null) {
|
|
_showErrorSnackBar(context, 'Session expirée, reconnectez-vous');
|
|
return;
|
|
}
|
|
|
|
await _demandeRecharge(userId, pin, montant, context);
|
|
} catch (e) {
|
|
print('Erreur processRechargeRequest : $e');
|
|
if (context.mounted) {
|
|
_showErrorSnackBar(context, 'Une erreur est survenue');
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _processCommissionTransfer(
|
|
BuildContext context,
|
|
double amount,
|
|
) async {
|
|
final authController = Provider.of<AuthController>(context, listen: false);
|
|
final agentId = authController.agentId;
|
|
|
|
if (agentId == null || agentId.isEmpty) {
|
|
_showErrorSnackBar(context, 'Erreur: Agent non connecté');
|
|
return;
|
|
}
|
|
|
|
// Dialog de chargement
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder:
|
|
(context) => Dialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Container(
|
|
padding: EdgeInsets.all(24),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
CircularProgressIndicator(
|
|
valueColor: AlwaysStoppedAnimation<Color>(
|
|
AppColors.success,
|
|
),
|
|
strokeWidth: 3,
|
|
),
|
|
SizedBox(height: 16),
|
|
Text(
|
|
'Transfert en cours...',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 8),
|
|
Text(
|
|
'Veuillez patienter',
|
|
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
try {
|
|
print(
|
|
'Envoi transfert commission - Agent ID: $agentId, Montant: $amount',
|
|
);
|
|
|
|
var headersList = {
|
|
'Accept': '*/*',
|
|
'User-Agent': 'FlutterApp (Wortis Agent)',
|
|
'Content-Type': 'application/json',
|
|
};
|
|
|
|
var url = Uri.parse(
|
|
'https://api.live.wortis.cg/tpe/recharge/$agentId/commission',
|
|
);
|
|
|
|
var body = {
|
|
"montant": amount,
|
|
"description": "Transfert de commission vers solde principal",
|
|
};
|
|
|
|
var req = http.Request('POST', url);
|
|
req.headers.addAll(headersList);
|
|
req.body = jsonEncode(body);
|
|
|
|
print('URL commission: $url');
|
|
print('Headers envoyés: $headersList');
|
|
print('Body envoyé: ${jsonEncode(body)}');
|
|
|
|
var res = await req.send();
|
|
final resBody = await res.stream.bytesToString();
|
|
|
|
// Fermer le dialog de chargement
|
|
if (Navigator.canPop(context)) {
|
|
Navigator.of(context).pop();
|
|
}
|
|
|
|
print('Réponse commission - Status: ${res.statusCode}');
|
|
print('Réponse commission - Body: $resBody');
|
|
|
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
final data = jsonDecode(resBody);
|
|
|
|
if (data['success'] == true) {
|
|
// Rafraîchir le solde
|
|
await authController.refreshBalance();
|
|
|
|
// Message de succès
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Row(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(4),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
child: Icon(
|
|
Icons.check_circle,
|
|
color: Colors.white,
|
|
size: 20,
|
|
),
|
|
),
|
|
SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
'Transfert réussi !',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
Text(
|
|
'+${amount.toStringAsFixed(0)} XAF ajouté à votre solde',
|
|
style: TextStyle(fontSize: 12),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
backgroundColor: AppColors.success,
|
|
behavior: SnackBarBehavior.floating,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
margin: EdgeInsets.all(16),
|
|
duration: Duration(seconds: 4),
|
|
),
|
|
);
|
|
|
|
setState(() {});
|
|
}
|
|
} else {
|
|
if (context.mounted) {
|
|
_showErrorSnackBar(
|
|
context,
|
|
data['message'] ?? 'Erreur lors du transfert',
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
try {
|
|
final data = jsonDecode(resBody);
|
|
if (context.mounted) {
|
|
if (res.statusCode == 404) {
|
|
_showErrorSnackBar(
|
|
context,
|
|
'Service de commission non disponible',
|
|
);
|
|
} else if (res.statusCode == 401) {
|
|
_showErrorSnackBar(context, 'Session expirée, reconnectez-vous');
|
|
} else {
|
|
_showErrorSnackBar(
|
|
context,
|
|
data['message'] ?? 'Erreur de serveur (${res.statusCode})',
|
|
);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (context.mounted) {
|
|
_showErrorSnackBar(
|
|
context,
|
|
'Erreur (${res.statusCode}): ${res.reasonPhrase}',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} on http.ClientException catch (e) {
|
|
if (Navigator.canPop(context)) Navigator.of(context).pop();
|
|
print('Erreur réseau commission: $e');
|
|
if (context.mounted) {
|
|
_showErrorSnackBar(context, 'Erreur de connexion réseau');
|
|
}
|
|
} on FormatException catch (e) {
|
|
if (Navigator.canPop(context)) Navigator.of(context).pop();
|
|
print('Erreur format JSON commission: $e');
|
|
if (context.mounted) {
|
|
_showErrorSnackBar(context, 'Erreur de format de réponse');
|
|
}
|
|
} catch (e) {
|
|
if (Navigator.canPop(context)) Navigator.of(context).pop();
|
|
print('Erreur transfert commission: $e');
|
|
if (context.mounted) {
|
|
_showErrorSnackBar(
|
|
context,
|
|
'Erreur lors du transfert: ${e.toString()}',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============= AUTRES MÉTHODES =============
|
|
Future<Map<String, dynamic>> _getTransactionHistory(String userId) async {
|
|
try {
|
|
final response = await http.get(
|
|
Uri.parse('$apiBaseUrl/agent/$userId/transactions'),
|
|
headers: apiHeaders,
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
return {
|
|
'success': true,
|
|
'transactions': data['transactions'] ?? [],
|
|
'total_commission': _calculateTotalCommission(
|
|
data['transactions'] ?? [],
|
|
),
|
|
};
|
|
} else {
|
|
return {'success': false, 'message': 'Erreur lors du chargement'};
|
|
}
|
|
} catch (e) {
|
|
return {'success': false, 'message': 'Erreur de connexion'};
|
|
}
|
|
}
|
|
|
|
Future<Map<String, dynamic>> _getRechargeHistory(String userId) async {
|
|
try {
|
|
final response = await http.get(
|
|
Uri.parse('$apiBaseUrl/recharge/history/agent/$userId'),
|
|
headers: apiHeaders,
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
return {'success': true, 'recharges': data['recharges'] ?? []};
|
|
} else {
|
|
return {'success': false, 'message': 'Erreur lors du chargement'};
|
|
}
|
|
} catch (e) {
|
|
return {'success': false, 'message': 'Erreur de connexion'};
|
|
}
|
|
}
|
|
|
|
double _calculateTotalCommission(List transactions) {
|
|
double totalTransaction = 0;
|
|
for (var transaction in transactions) {
|
|
if (transaction['statut'] == 'completed') {
|
|
totalTransaction += (transaction['montant'] ?? 0.0);
|
|
}
|
|
}
|
|
return totalTransaction * 0.0066;
|
|
}
|
|
|
|
Future<void> _showRechargeHistoryDialog(BuildContext context) async {
|
|
try {
|
|
final userId = await SessionManager().getUserId();
|
|
final result = await _getRechargeHistory(userId!);
|
|
|
|
if (!context.mounted) return;
|
|
|
|
if (result['success']) {
|
|
_showHistoryDialog(
|
|
context,
|
|
'Historique des recharges',
|
|
result['recharges'],
|
|
'recharge',
|
|
);
|
|
} else {
|
|
_showErrorSnackBar(context, result['message']);
|
|
}
|
|
} catch (e) {
|
|
_showErrorSnackBar(context, 'Erreur lors du chargement');
|
|
}
|
|
}
|
|
|
|
void _showHistoryDialog(
|
|
BuildContext context,
|
|
String title,
|
|
List items,
|
|
String type,
|
|
) {
|
|
showDialog(
|
|
context: context,
|
|
builder:
|
|
(context) => Dialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Container(
|
|
width: MediaQuery.of(context).size.width * 0.9,
|
|
height: MediaQuery.of(context).size.height * 0.8,
|
|
padding: const EdgeInsets.all(24),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
colors: [AppColors.surface, Colors.grey[50]!],
|
|
),
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
title,
|
|
style: const TextStyle(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
),
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey[100],
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: IconButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
icon: const Icon(Icons.close, color: Colors.grey),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
height: 2,
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [AppColors.primary, AppColors.accent],
|
|
),
|
|
borderRadius: BorderRadius.circular(1),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Expanded(
|
|
child:
|
|
items.isEmpty
|
|
? _buildEmptyState(type)
|
|
: ListView.builder(
|
|
physics: BouncingScrollPhysics(),
|
|
itemCount: items.length,
|
|
itemBuilder: (context, index) {
|
|
return AnimatedContainer(
|
|
duration: Duration(
|
|
milliseconds: 300 + (index * 100),
|
|
),
|
|
curve: Curves.easeOutBack,
|
|
child:
|
|
type == 'transaction'
|
|
? _buildEnhancedTransactionItem(
|
|
items[index],
|
|
index,
|
|
)
|
|
: _buildEnhancedRechargeHistoryItem(
|
|
items[index],
|
|
index,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildEmptyState(String type) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Container(
|
|
width: 80,
|
|
height: 80,
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey[100],
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Icon(
|
|
type == 'transaction' ? Icons.receipt_long : Icons.history,
|
|
size: 40,
|
|
color: Colors.grey[400],
|
|
),
|
|
),
|
|
SizedBox(height: 16),
|
|
Text(
|
|
'Aucun élément dans l\'historique',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color: Colors.grey[600],
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
SizedBox(height: 8),
|
|
Text(
|
|
type == 'transaction'
|
|
? 'Vos transactions apparaîtront ici'
|
|
: 'Vos recharges apparaîtront ici',
|
|
style: TextStyle(fontSize: 14, color: Colors.grey[500]),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildEnhancedTransactionItem(
|
|
Map<String, dynamic> transaction,
|
|
int index,
|
|
) {
|
|
final serviceName = transaction['service_name'] ?? 'Service inconnu';
|
|
final montant = (transaction['montant_total_debite'] ?? 0.0).toDouble();
|
|
final commission = (transaction['commission_agent'] ?? 0.0).toDouble();
|
|
final statut = transaction['status'] ?? 'unknown';
|
|
final date =
|
|
DateTime.tryParse(transaction['created_at'] ?? '') ?? DateTime.now();
|
|
|
|
return TweenAnimationBuilder<double>(
|
|
duration: Duration(milliseconds: 600 + (index * 100)),
|
|
tween: Tween(begin: 0.0, end: 1.0),
|
|
curve: Curves.easeOutBack,
|
|
builder: (context, animation, child) {
|
|
final clampedAnimation = animation.clamp(0.0, 1.0);
|
|
|
|
return Transform.translate(
|
|
offset: Offset(30 * (1 - clampedAnimation), 0),
|
|
child: Opacity(
|
|
opacity: clampedAnimation,
|
|
child: Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.surface,
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(
|
|
color: _getStatusColor(statut).withOpacity(0.2),
|
|
width: 1,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: _getStatusColor(statut).withOpacity(0.1),
|
|
blurRadius: 10,
|
|
offset: Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 50,
|
|
height: 50,
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
_getStatusColor(statut),
|
|
_getStatusColor(statut).withOpacity(0.7),
|
|
],
|
|
),
|
|
borderRadius: BorderRadius.circular(15),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: _getStatusColor(statut).withOpacity(0.3),
|
|
blurRadius: 8,
|
|
offset: Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Icon(
|
|
_getServiceIcon(serviceName),
|
|
color: Colors.white,
|
|
size: 24,
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
serviceName,
|
|
style: const TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
SizedBox(height: 4),
|
|
Row(
|
|
children: [
|
|
Text(
|
|
'${montant.toStringAsFixed(0)} XAF',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: Colors.grey[700],
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
Text(
|
|
' • ',
|
|
style: TextStyle(color: Colors.grey[500]),
|
|
),
|
|
Text(
|
|
'Commission: ${commission.toStringAsFixed(0)} XAF',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: AppColors.success,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 4),
|
|
Text(
|
|
_formatDate(date),
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: Colors.grey[500],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 10,
|
|
vertical: 6,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: _getStatusColor(statut).withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: _getStatusColor(statut).withOpacity(0.3),
|
|
),
|
|
),
|
|
child: Text(
|
|
_getStatusText(statut),
|
|
style: TextStyle(
|
|
fontSize: 11,
|
|
color: _getStatusColor(statut),
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildEnhancedRechargeHistoryItem(
|
|
Map<String, dynamic> recharge,
|
|
int index,
|
|
) {
|
|
final montant = double.tryParse(recharge['montant'].toString()) ?? 0.0;
|
|
final status = recharge['status'] ?? '';
|
|
final date =
|
|
DateTime.tryParse(recharge['created_at'] ?? '') ?? DateTime.now();
|
|
|
|
return TweenAnimationBuilder<double>(
|
|
duration: Duration(milliseconds: 600 + (index * 100)),
|
|
tween: Tween(begin: 0.0, end: 1.0),
|
|
curve: Curves.easeOutBack,
|
|
builder: (context, animation, child) {
|
|
final clampedAnimation = animation.clamp(0.0, 1.0);
|
|
|
|
return Transform.translate(
|
|
offset: Offset(30 * (1 - clampedAnimation), 0),
|
|
child: Opacity(
|
|
opacity: clampedAnimation,
|
|
child: Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.surface,
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(
|
|
color: _getStatusColor(status).withOpacity(0.2),
|
|
width: 1,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: _getStatusColor(status).withOpacity(0.1),
|
|
blurRadius: 10,
|
|
offset: Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 50,
|
|
height: 50,
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
_getStatusColor(status),
|
|
_getStatusColor(status).withOpacity(0.7),
|
|
],
|
|
),
|
|
borderRadius: BorderRadius.circular(15),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: _getStatusColor(status).withOpacity(0.3),
|
|
blurRadius: 8,
|
|
offset: Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Icon(
|
|
Icons.add_circle,
|
|
color: Colors.white,
|
|
size: 24,
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Recharge de ${montant.toStringAsFixed(0)} XAF',
|
|
style: const TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 8),
|
|
Text(
|
|
_formatDate(date),
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: Colors.grey[500],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 10,
|
|
vertical: 6,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: _getStatusColor(status).withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: _getStatusColor(status).withOpacity(0.3),
|
|
),
|
|
),
|
|
child: Text(
|
|
_getStatusText(status),
|
|
style: TextStyle(
|
|
fontSize: 11,
|
|
color: _getStatusColor(status),
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
// ============= UTILITAIRES =============
|
|
IconData _getServiceIcon(String serviceName) {
|
|
if (serviceName.toLowerCase().contains('internet')) {
|
|
return Icons.wifi;
|
|
} else if (serviceName.toLowerCase().contains('électricité') ||
|
|
serviceName.toLowerCase().contains('pelisa')) {
|
|
return Icons.electrical_services;
|
|
} else if (serviceName.toLowerCase().contains('eau')) {
|
|
return Icons.water_drop;
|
|
} else {
|
|
return Icons.receipt_long;
|
|
}
|
|
}
|
|
|
|
String _getStatusText(String status) {
|
|
switch (status.toLowerCase()) {
|
|
case 'completed':
|
|
return 'Terminé';
|
|
case 'approved':
|
|
return 'Approuvé';
|
|
case 'pending':
|
|
return 'En attente';
|
|
case 'rejected':
|
|
return 'Rejeté';
|
|
case 'cancelled':
|
|
return 'Annulé';
|
|
default:
|
|
return status.toUpperCase();
|
|
}
|
|
}
|
|
|
|
Color _getStatusColor(String status) {
|
|
switch (status.toLowerCase()) {
|
|
case 'completed':
|
|
case 'approved':
|
|
return AppColors.success;
|
|
case 'pending':
|
|
return Colors.orange;
|
|
case 'rejected':
|
|
case 'cancelled':
|
|
return Colors.red;
|
|
default:
|
|
return Colors.grey;
|
|
}
|
|
}
|
|
|
|
String _formatDate(DateTime date) {
|
|
return '${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year} à ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}';
|
|
}
|
|
|
|
Future<void> _demandeRecharge(
|
|
String? userId,
|
|
String pin,
|
|
String montant,
|
|
BuildContext context,
|
|
) async {
|
|
if (userId == null) {
|
|
_showErrorSnackBar(context, 'Erreur: Utilisateur non connecté');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
print(
|
|
'Envoi demande recharge - URL: $apiBaseUrl/recharge/create/agent/$userId',
|
|
);
|
|
print(
|
|
'Montant: $montant, PIN: ${pin.isNotEmpty ? "****(${pin.length} chars)" : "vide"}',
|
|
);
|
|
|
|
final response = await http.post(
|
|
Uri.parse('$apiBaseUrl/recharge/create/agent/$userId'),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
},
|
|
body: jsonEncode({'pin': pin, 'montant': montant}),
|
|
);
|
|
|
|
print('Réponse recharge - Status: ${response.statusCode}');
|
|
print('Réponse recharge - Body: ${response.body}');
|
|
|
|
if (response.statusCode == 201 || response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
if (data['success'] == true || data.containsKey('recharge')) {
|
|
_showSuccessDialog(
|
|
context,
|
|
'Demande de recharge envoyée avec succès',
|
|
);
|
|
} else {
|
|
_showErrorSnackBar(
|
|
context,
|
|
data['message'] ?? 'Erreur lors de la demande',
|
|
);
|
|
}
|
|
} else {
|
|
final responseBody = jsonDecode(response.body);
|
|
final errorMessage = responseBody['message'] ?? 'Erreur inconnue';
|
|
|
|
if (errorMessage.toLowerCase().contains('pin') ||
|
|
errorMessage.toLowerCase().contains('code') ||
|
|
response.statusCode == 401) {
|
|
_showErrorSnackBar(context, 'PIN incorrect, veuillez réessayer');
|
|
} else if (response.statusCode == 404) {
|
|
_showErrorSnackBar(
|
|
context,
|
|
'Service non disponible, contactez le support',
|
|
);
|
|
} else {
|
|
_showErrorSnackBar(
|
|
context,
|
|
'Erreur (${response.statusCode}): $errorMessage',
|
|
);
|
|
}
|
|
}
|
|
} on http.ClientException catch (e) {
|
|
print('Erreur réseau: $e');
|
|
_showErrorSnackBar(context, 'Erreur de connexion réseau');
|
|
} on FormatException catch (e) {
|
|
print('Erreur format JSON: $e');
|
|
_showErrorSnackBar(context, 'Erreur de format de réponse');
|
|
} catch (e) {
|
|
print('Erreur générale: $e');
|
|
_showErrorSnackBar(context, 'Une erreur inattendue s\'est produite');
|
|
}
|
|
}
|
|
|
|
void _showSupportDialog(BuildContext context) {
|
|
showDialog(
|
|
context: context,
|
|
builder:
|
|
(context) => AlertDialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
title: Row(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.purple.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Icon(Icons.headset_mic, color: AppColors.purple),
|
|
),
|
|
SizedBox(width: 12),
|
|
Text('Support Technique'),
|
|
],
|
|
),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Besoin d\'aide ? Contactez notre équipe de support :',
|
|
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
|
),
|
|
SizedBox(height: 16),
|
|
_buildSupportOption(
|
|
Icons.phone,
|
|
'Téléphone',
|
|
'50 05',
|
|
AppColors.success,
|
|
),
|
|
SizedBox(height: 12),
|
|
_buildSupportOption(
|
|
Icons.email,
|
|
'Email',
|
|
'support@wortis.cg',
|
|
AppColors.primary,
|
|
),
|
|
SizedBox(height: 12),
|
|
_buildSupportOption(
|
|
Icons.chat,
|
|
'WhatsApp',
|
|
'+242 06 755 0505',
|
|
Colors.green,
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: Text('Fermer'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSupportOption(
|
|
IconData icon,
|
|
String title,
|
|
String value,
|
|
Color color,
|
|
) {
|
|
return Container(
|
|
padding: EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.05),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: color.withOpacity(0.2)),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(icon, color: color, size: 20),
|
|
SizedBox(width: 12),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 12,
|
|
color: Colors.grey[700],
|
|
),
|
|
),
|
|
Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: color,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showSuccessDialog(BuildContext context, String message) {
|
|
if (!context.mounted) return;
|
|
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: true,
|
|
builder:
|
|
(context) => Dialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Container(
|
|
padding: EdgeInsets.all(24),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(20),
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
colors: [
|
|
AppColors.success.withOpacity(0.1),
|
|
Colors.green[50]!,
|
|
],
|
|
),
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
width: 60,
|
|
height: 60,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.success,
|
|
borderRadius: BorderRadius.circular(30),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: AppColors.success.withOpacity(0.3),
|
|
blurRadius: 15,
|
|
offset: Offset(0, 5),
|
|
),
|
|
],
|
|
),
|
|
child: Icon(Icons.check, color: Colors.white, size: 32),
|
|
),
|
|
SizedBox(height: 16),
|
|
Text(
|
|
'Succès !',
|
|
style: TextStyle(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.bold,
|
|
color: AppColors.success,
|
|
),
|
|
),
|
|
SizedBox(height: 8),
|
|
Text(
|
|
message,
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(fontSize: 16, color: Colors.grey[700]),
|
|
),
|
|
SizedBox(height: 20),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppColors.success,
|
|
foregroundColor: Colors.white,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
padding: EdgeInsets.symmetric(vertical: 12),
|
|
),
|
|
child: Text(
|
|
'Parfait !',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showErrorSnackBar(BuildContext context, String message) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Row(
|
|
children: [
|
|
const Icon(Icons.error, color: Colors.white),
|
|
const SizedBox(width: 8),
|
|
Expanded(child: Text(message)),
|
|
],
|
|
),
|
|
backgroundColor: Colors.red,
|
|
behavior: SnackBarBehavior.floating,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _logout(BuildContext context) async {
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return AlertDialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
title: Row(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.red.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Icon(Icons.logout, color: Colors.red),
|
|
),
|
|
SizedBox(width: 12),
|
|
Text('Déconnexion'),
|
|
],
|
|
),
|
|
content: Text(
|
|
'Êtes-vous sûr de vouloir vous déconnecter de votre compte agent ?',
|
|
style: TextStyle(fontSize: 16),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
child: Text('Annuler'),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () async {
|
|
Navigator.of(context).pop();
|
|
final authController = Provider.of<AuthController>(
|
|
context,
|
|
listen: false,
|
|
);
|
|
await authController.logout();
|
|
if (context.mounted) {
|
|
Navigator.of(context).pushAndRemoveUntil(
|
|
MaterialPageRoute(builder: (context) => const LoginPage()),
|
|
(route) => false,
|
|
);
|
|
}
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.red,
|
|
foregroundColor: Colors.white,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
),
|
|
child: Text('Déconnexion'),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
// ============= SECTION PARAMÈTRES =============
|
|
Widget _buildSettingsSection(
|
|
bool isSmallScreen,
|
|
bool isMediumScreen,
|
|
bool isTablet,
|
|
) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: EdgeInsets.only(left: 4, bottom: isSmallScreen ? 12 : 16),
|
|
child: Text(
|
|
'Paramètres Kiosque',
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 18 : (isMediumScreen ? 19 : 20),
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
),
|
|
|
|
Container(
|
|
padding: EdgeInsets.all(isSmallScreen ? 16 : 20),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.surface,
|
|
borderRadius: BorderRadius.circular(isSmallScreen ? 16 : 20),
|
|
border: Border.all(
|
|
color: AppColors.primary.withOpacity(0.1),
|
|
width: 1,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: isSmallScreen ? 10 : 15,
|
|
offset: Offset(0, isSmallScreen ? 3 : 5),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
children: [
|
|
_buildSettingTile(
|
|
icon: Icon(Icons.wifi_find, size: 24.0, color: Colors.blue),
|
|
title: 'Paramètres WiFi',
|
|
subtitle: 'Changer de réseau WiFi',
|
|
color: Colors.blue,
|
|
onTap: () => _openWifiSettings(),
|
|
isSmallScreen: isSmallScreen,
|
|
),
|
|
|
|
SizedBox(height: isSmallScreen ? 12 : 16),
|
|
Divider(color: Colors.grey[200], height: 1),
|
|
SizedBox(height: isSmallScreen ? 12 : 16),
|
|
|
|
_buildSettingTile(
|
|
icon: Icon(
|
|
Icons.brightness_6,
|
|
size: 24.0,
|
|
color: Colors.orange,
|
|
),
|
|
title: 'Luminosité',
|
|
subtitle: 'Ajuster la luminosité de l\'écran',
|
|
color: Colors.orange,
|
|
onTap: () => _showBrightnessDialog(context),
|
|
isSmallScreen: isSmallScreen,
|
|
),
|
|
|
|
SizedBox(height: isSmallScreen ? 12 : 16),
|
|
Divider(color: Colors.grey[200], height: 1),
|
|
SizedBox(height: isSmallScreen ? 12 : 16),
|
|
|
|
_buildSettingTile(
|
|
icon: FaIcon(
|
|
FontAwesomeIcons.whatsapp,
|
|
size: 24.0,
|
|
color: Colors.green,
|
|
),
|
|
title: 'Ouvrir WhatsApp',
|
|
subtitle: 'Ouvrir l\'application WhatsApp',
|
|
color: Colors.green,
|
|
onTap: () => _openWhatsApp(),
|
|
isSmallScreen: isSmallScreen,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildSettingTile({
|
|
required Widget icon,
|
|
required String title,
|
|
required String subtitle,
|
|
required Color color,
|
|
required VoidCallback onTap,
|
|
required bool isSmallScreen,
|
|
}) {
|
|
return GestureDetector(
|
|
onTap: onTap,
|
|
child: Container(
|
|
padding: EdgeInsets.all(isSmallScreen ? 12 : 16),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.05),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: color.withOpacity(0.2), width: 1),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: isSmallScreen ? 40 : 48,
|
|
height: isSmallScreen ? 40 : 48,
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: color.withOpacity(0.3), width: 1),
|
|
),
|
|
child: Center(child: icon),
|
|
),
|
|
SizedBox(width: isSmallScreen ? 12 : 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 14 : 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 2),
|
|
Text(
|
|
subtitle,
|
|
style: TextStyle(
|
|
fontSize: isSmallScreen ? 11 : 13,
|
|
color: Colors.grey[600],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Icon(
|
|
Icons.arrow_forward_ios,
|
|
color: color,
|
|
size: isSmallScreen ? 14 : 16,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ============= MÉTHODES D'ACTION =============
|
|
void _openWifiSettings() async {
|
|
try {
|
|
const platform = MethodChannel('com.wortis.agent/settings');
|
|
|
|
await platform.invokeMethod('exitKioskMode');
|
|
await platform.invokeMethod('openWifiSettings');
|
|
|
|
Future.delayed(Duration(seconds: 2), () async {
|
|
try {
|
|
await platform.invokeMethod('enableKioskMode');
|
|
} catch (e) {
|
|
print('Erreur réactivation kiosque: $e');
|
|
}
|
|
});
|
|
} catch (e) {
|
|
print('Erreur ouverture WiFi: $e');
|
|
_showErrorSnackBar(context, 'Impossible d\'ouvrir les paramètres WiFi');
|
|
}
|
|
}
|
|
|
|
void _showBrightnessDialog(BuildContext context) async {
|
|
double currentBrightness = 0.5;
|
|
|
|
try {
|
|
const platform = MethodChannel('com.wortis.agent/settings');
|
|
final brightness = await platform.invokeMethod('getSystemBrightness');
|
|
currentBrightness = (brightness as double).clamp(0.0, 1.0);
|
|
} catch (e) {
|
|
print('Erreur récupération luminosité: $e');
|
|
}
|
|
|
|
if (!mounted) return;
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder:
|
|
(context) => StatefulBuilder(
|
|
builder:
|
|
(context, setState) => AlertDialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
title: Row(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.orange.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Icon(Icons.brightness_6, color: Colors.orange),
|
|
),
|
|
SizedBox(width: 12),
|
|
Text('Réglage Luminosité'),
|
|
],
|
|
),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
'Ajustez la luminosité de l\'écran',
|
|
style: TextStyle(color: Colors.grey[600]),
|
|
),
|
|
SizedBox(height: 20),
|
|
Row(
|
|
children: [
|
|
Icon(Icons.brightness_low, color: Colors.grey),
|
|
Expanded(
|
|
child: Slider(
|
|
value: currentBrightness,
|
|
onChanged: (value) {
|
|
setState(() {
|
|
currentBrightness = value;
|
|
});
|
|
_setBrightness(value);
|
|
},
|
|
activeColor: Colors.orange,
|
|
inactiveColor: Colors.orange.withOpacity(0.3),
|
|
),
|
|
),
|
|
Icon(Icons.brightness_high, color: Colors.orange),
|
|
],
|
|
),
|
|
SizedBox(height: 8),
|
|
Text(
|
|
'${(currentBrightness * 100).round()}%',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.orange,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: Text('Fermer'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _setBrightness(double brightness) async {
|
|
try {
|
|
const platform = MethodChannel('com.wortis.agent/settings');
|
|
await platform.invokeMethod('setBrightness', {'brightness': brightness});
|
|
} catch (e) {
|
|
print('Erreur réglage luminosité: $e');
|
|
if (mounted) {
|
|
_showErrorSnackBar(context, 'Réglage de luminosité non disponible');
|
|
}
|
|
}
|
|
}
|
|
|
|
void _openWhatsApp() async {
|
|
try {
|
|
const platform = MethodChannel('com.wortis.agent/settings');
|
|
|
|
await platform.invokeMethod('exitKioskMode');
|
|
await platform.invokeMethod('openWhatsApp');
|
|
} catch (e) {
|
|
print('Erreur ouverture WhatsApp: $e');
|
|
|
|
try {
|
|
const platform = MethodChannel('com.wortis.agent/settings');
|
|
await platform.invokeMethod('enableKioskMode');
|
|
} catch (e2) {
|
|
print('Erreur réactivation kiosque: $e2');
|
|
}
|
|
|
|
if (mounted) {
|
|
_showErrorSnackBar(
|
|
context,
|
|
'WhatsApp non disponible ou erreur d\'ouverture',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|