Initial commit: unionflow-mobile-apps
Application Flutter complète (sans build artifacts). Signed-off-by: lions dev Team
This commit is contained in:
522
lib/features/about/presentation/pages/about_page.dart
Normal file
522
lib/features/about/presentation/pages/about_page.dart
Normal file
@@ -0,0 +1,522 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart' show defaultTargetPlatform, TargetPlatform, kIsWeb;
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../../../../shared/design_system/unionflow_design_system.dart';
|
||||
import '../../../../shared/widgets/core_card.dart';
|
||||
import '../../../../shared/widgets/info_badge.dart';
|
||||
|
||||
|
||||
/// Page À propos - UnionFlow Mobile
|
||||
///
|
||||
/// Page d'informations sur l'application, version, équipe de développement,
|
||||
/// liens utiles et fonctionnalités de support.
|
||||
class AboutPage extends StatefulWidget {
|
||||
const AboutPage({super.key});
|
||||
|
||||
@override
|
||||
State<AboutPage> createState() => _AboutPageState();
|
||||
}
|
||||
|
||||
class _AboutPageState extends State<AboutPage> {
|
||||
PackageInfo? _packageInfo;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadPackageInfo();
|
||||
}
|
||||
|
||||
Future<void> _loadPackageInfo() async {
|
||||
final info = await PackageInfo.fromPlatform();
|
||||
setState(() {
|
||||
_packageInfo = info;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
appBar: UFAppBar(
|
||||
title: 'À PROPOS',
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.share_outlined, size: 20),
|
||||
onPressed: _shareApp,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header harmonisé
|
||||
_buildHeader(),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Informations de l'application
|
||||
_buildAppInfoSection(),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Équipe de développement
|
||||
_buildTeamSection(),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Fonctionnalités
|
||||
_buildFeaturesSection(),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Liens utiles
|
||||
_buildLinksSection(),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Support et contact
|
||||
_buildSupportSection(),
|
||||
const SizedBox(height: 80),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Header épuré
|
||||
Widget _buildHeader() {
|
||||
return Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primaryGreen.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.account_balance,
|
||||
color: AppColors.primaryGreen,
|
||||
size: 48,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'UNIONFLOW MOBILE',
|
||||
style: AppTypography.headerSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.2),
|
||||
),
|
||||
Text(
|
||||
'Gestion d\'associations et syndicats',
|
||||
style: AppTypography.subtitleSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (_packageInfo != null)
|
||||
InfoBadge(
|
||||
text: 'VERSION ${_packageInfo!.version}',
|
||||
backgroundColor: AppColors.lightSurface,
|
||||
textColor: AppColors.textSecondaryLight,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Section informations de l'application
|
||||
Widget _buildAppInfoSection() {
|
||||
return CoreCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'INFORMATIONS',
|
||||
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildInfoRow('Construction', _packageInfo?.buildNumber ?? '...'),
|
||||
_buildInfoRow('Package', _packageInfo?.packageName ?? '...'),
|
||||
_buildInfoRow('Plateforme', 'Android / iOS'),
|
||||
_buildInfoRow('Framework', 'Flutter 3.x'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Ligne d'information
|
||||
Widget _buildInfoRow(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: AppTypography.bodyTextSmall.copyWith(color: AppColors.textSecondaryLight),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
value,
|
||||
style: AppTypography.actionText.copyWith(fontSize: 12),
|
||||
textAlign: TextAlign.end,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Section équipe de développement
|
||||
Widget _buildTeamSection() {
|
||||
return CoreCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'ÉQUIPE',
|
||||
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildTeamMember(
|
||||
'UnionFlow Team',
|
||||
'Architecture & Dev',
|
||||
Icons.code,
|
||||
AppColors.primaryGreen,
|
||||
),
|
||||
_buildTeamMember(
|
||||
'Design System',
|
||||
'UI / UX Focus',
|
||||
Icons.design_services,
|
||||
AppColors.info,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Membre de l'équipe
|
||||
Widget _buildTeamMember(String name, String role, IconData icon, Color color) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(icon, color: color, size: 16),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(name, style: AppTypography.actionText.copyWith(fontSize: 12)),
|
||||
Text(role, style: AppTypography.subtitleSmall.copyWith(fontSize: 10)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Section fonctionnalités
|
||||
Widget _buildFeaturesSection() {
|
||||
return CoreCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'FONCTIONNALITÉS',
|
||||
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildFeatureItem('Membres', 'Administration complète', Icons.people, AppColors.primaryGreen),
|
||||
_buildFeatureItem('Organisations', 'Syndicats & Fédérations', Icons.business, AppColors.info),
|
||||
_buildFeatureItem('Événements', 'Planification & Suivi', Icons.event, AppColors.success),
|
||||
_buildFeatureItem('Sécurité', 'Auth Keycloak OIDC', Icons.security, AppColors.warning),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Élément de fonctionnalité
|
||||
Widget _buildFeatureItem(String title, String description, IconData icon, Color color) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, color: color, size: 16),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: AppTypography.actionText.copyWith(fontSize: 12)),
|
||||
Text(description, style: AppTypography.subtitleSmall.copyWith(fontSize: 10)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Section liens utiles
|
||||
Widget _buildLinksSection() {
|
||||
return CoreCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'LIENS UTILES',
|
||||
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildLinkItem('Site Web', 'https://unionflow.com', Icons.web, () => _launchUrl('https://unionflow.com')),
|
||||
_buildLinkItem('Documentation', 'Guide d\'utilisation', Icons.book, () => _launchUrl('https://docs.unionflow.com')),
|
||||
_buildLinkItem('Confidentialité', 'Protection des données', Icons.privacy_tip, () => _launchUrl('https://unionflow.com/privacy')),
|
||||
_buildLinkItem('Évaluer l\'app', 'Noter sur le store', Icons.star, _showRatingDialog),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Élément de lien
|
||||
Widget _buildLinkItem(String title, String subtitle, IconData icon, VoidCallback onTap) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, color: AppColors.primaryGreen, size: 16),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: AppTypography.actionText.copyWith(fontSize: 12)),
|
||||
Text(subtitle, style: AppTypography.subtitleSmall.copyWith(fontSize: 10)),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Icon(Icons.chevron_right, color: AppColors.textSecondaryLight, size: 14),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Section support
|
||||
Widget _buildSupportSection() {
|
||||
return CoreCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'SUPPORT',
|
||||
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildSupportItem('Email', 'support@unionflow.com', Icons.email, () => _launchUrl('mailto:support@unionflow.com')),
|
||||
_buildSupportItem('Bug', 'Signaler un problème', Icons.bug_report, () => _showBugReportDialog()),
|
||||
const SizedBox(height: 24),
|
||||
const Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Text('© 2024 UNIONFLOW', style: AppTypography.badgeText),
|
||||
Text('Fait avec ❤️ pour les syndicats', style: AppTypography.subtitleSmall),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Élément de support
|
||||
Widget _buildSupportItem(String title, String subtitle, IconData icon, VoidCallback onTap) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, color: AppColors.error, size: 16),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: AppTypography.actionText.copyWith(fontSize: 12)),
|
||||
Text(subtitle, style: AppTypography.subtitleSmall.copyWith(fontSize: 10)),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Icon(Icons.chevron_right, color: AppColors.textSecondaryLight, size: 14),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Lancer une URL
|
||||
Future<void> _launchUrl(String url) async {
|
||||
try {
|
||||
final uri = Uri.parse(url);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
} else {
|
||||
_showErrorSnackBar('Impossible d\'ouvrir le lien');
|
||||
}
|
||||
} catch (e) {
|
||||
_showErrorSnackBar('Erreur lors de l\'ouverture du lien');
|
||||
}
|
||||
}
|
||||
|
||||
/// Afficher le dialogue de rapport de bug
|
||||
void _showBugReportDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Signaler un bug'),
|
||||
content: const Text(
|
||||
'Pour signaler un bug, veuillez envoyer un email à support@unionflow.com '
|
||||
'en décrivant le problème rencontré et les étapes pour le reproduire.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Fermer'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
_launchUrl('mailto:support@unionflow.com?subject=Rapport de bug - UnionFlow Mobile');
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: ColorTokens.primary,
|
||||
foregroundColor: ColorTokens.onPrimary,
|
||||
),
|
||||
child: const Text('Envoyer un email'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Afficher le dialogue de demande de fonctionnalité
|
||||
void _showFeatureRequestDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Suggérer une amélioration'),
|
||||
content: const Text(
|
||||
'Nous sommes toujours à l\'écoute de vos suggestions ! '
|
||||
'Envoyez-nous vos idées d\'amélioration par email.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Fermer'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
_launchUrl('mailto:support@unionflow.com?subject=Suggestion d\'amélioration - UnionFlow Mobile');
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: ColorTokens.primary,
|
||||
foregroundColor: ColorTokens.onPrimary,
|
||||
),
|
||||
child: const Text('Envoyer une suggestion'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Afficher le dialogue d'évaluation
|
||||
void _showRatingDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Évaluer l\'application'),
|
||||
content: const Text(
|
||||
'Votre avis nous aide à améliorer UnionFlow ! '
|
||||
'Prenez quelques secondes pour évaluer l\'application sur votre store.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Plus tard'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
_launchStoreForRating();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: ColorTokens.primary,
|
||||
foregroundColor: ColorTokens.onPrimary,
|
||||
),
|
||||
child: const Text('Évaluer maintenant'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Partager les infos de l'app (titre, description, lien)
|
||||
Future<void> _shareApp() async {
|
||||
final version = _packageInfo != null
|
||||
? '${_packageInfo!.version}+${_packageInfo!.buildNumber}'
|
||||
: '';
|
||||
await Share.share(
|
||||
'Découvrez UnionFlow - Mouvement d\'entraide et de solidarité.\n'
|
||||
'Version $version\n'
|
||||
'https://unionflow.com',
|
||||
subject: 'UnionFlow - Application mobile',
|
||||
);
|
||||
}
|
||||
|
||||
/// Ouvrir le store (Play Store / App Store) pour noter l'app
|
||||
Future<void> _launchStoreForRating() async {
|
||||
try {
|
||||
final packageName = _packageInfo?.packageName ?? 'dev.lions.unionflow';
|
||||
String storeUrl;
|
||||
if (kIsWeb) {
|
||||
storeUrl = 'https://unionflow.com';
|
||||
} else if (defaultTargetPlatform == TargetPlatform.android) {
|
||||
storeUrl = 'https://play.google.com/store/apps/details?id=$packageName';
|
||||
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
|
||||
// Remplacer par l'ID App Store réel une fois l'app publiée
|
||||
storeUrl = 'https://apps.apple.com/app/id0000000000';
|
||||
} else {
|
||||
storeUrl = 'https://unionflow.com';
|
||||
}
|
||||
final uri = Uri.parse(storeUrl);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
} else {
|
||||
_showErrorSnackBar('Impossible d\'ouvrir le store');
|
||||
}
|
||||
} catch (e) {
|
||||
_showErrorSnackBar('Erreur lors de l\'ouverture du store');
|
||||
}
|
||||
}
|
||||
|
||||
/// Afficher un message d'erreur
|
||||
void _showErrorSnackBar(String message) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message),
|
||||
backgroundColor: const Color(0xFFE74C3C),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user