import 'package:sysfarma_mobile/src/core/storage/tenant_session_store.dart'; import 'package:sysfarma_mobile/src/core/theme/app_tokens.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:url_launcher/url_launcher.dart'; const _privacyPolicyUrl = 'https://sysfarma.pe/politica-de-privacidad'; class SettingsScreen extends ConsumerStatefulWidget { const SettingsScreen({super.key}); @override ConsumerState createState() => _SettingsScreenState(); } class _SettingsScreenState extends ConsumerState { @override Widget build(BuildContext context) { final sessionAsync = ref.watch(currentSessionProvider); final session = sessionAsync.maybeWhen( data: (s) => s, orElse: () => null, ); bool hasModule(String value) => session?.hasModule(value) ?? false; final options = <_OptionEntry>[ _OptionEntry( icon: Icons.person_rounded, title: 'Mi perfil', accent: AppTokens.primary, onTap: () => context.go('/profile'), ), _OptionEntry( icon: Icons.sync_problem_rounded, title: 'Pendientes sync', accent: AppTokens.warning, onTap: () => context.go('/pending-sync'), ), if (hasModule('items')) ...[ _OptionEntry( icon: Icons.inventory_2_rounded, title: 'Productos', accent: AppTokens.primary, onTap: () => context.go('/catalog'), ), _OptionEntry( icon: Icons.design_services_rounded, title: 'Servicios', accent: AppTokens.cta, onTap: () => context.go('/services'), ), ], if (hasModule('persons')) _OptionEntry( icon: Icons.people_alt_rounded, title: 'Clientes', accent: AppTokens.success, onTap: () => context.go('/customers'), ), // Proveedores cuelga de Compras en la web, no de Clientes (ver // ModuleAccess.routeModules). if (hasModule('purchases')) ...[ _OptionEntry( icon: Icons.local_shipping_rounded, title: 'Proveedores', accent: AppTokens.cta, onTap: () => context.go('/suppliers'), ), _OptionEntry( icon: Icons.shopping_bag_rounded, title: 'Compras', accent: AppTokens.warning, onTap: () => context.go('/purchases'), ), ], if (hasModule('documents')) ...[ _OptionEntry( icon: Icons.receipt_long_rounded, title: 'Ventas emitidas', accent: AppTokens.primary, onTap: () => context.go('/sales-history'), ), _OptionEntry( icon: Icons.hourglass_top_rounded, title: 'Por anular', accent: AppTokens.warning, onTap: () => context.go('/pending-voided'), ), _OptionEntry( icon: Icons.cloud_sync_rounded, title: 'Consulta SUNAT', accent: AppTokens.secondary, onTap: () => context.go('/quick'), ), ], // Bandeja de pagos Yape/Plin (addon superadmin). La escucha solo para // el usuario designado como capturador. if (session?.yapeInboxEnabled ?? false) ...[ _OptionEntry( icon: Icons.account_balance_wallet_rounded, title: 'Pagos Yape', accent: const Color(0xFF742384), onTap: () => context.go('/yape-inbox'), ), if (session?.yapeListenerEnabled ?? false) _OptionEntry( icon: Icons.hearing_rounded, title: 'Escuchar pagos en este celular', accent: AppTokens.success, onTap: () => context.push('/yape-listener'), ), ], // Al final: no es una funcion operativa, es informativa. _OptionEntry( icon: Icons.privacy_tip_rounded, title: 'Política de privacidad', accent: AppTokens.secondary, onTap: () => launchUrl( Uri.parse(_privacyPolicyUrl), mode: LaunchMode.externalApplication, ), ), ]; return SafeArea( child: ListView( padding: AppTokens.pagePadding, children: [ GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: options.length, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 12, mainAxisSpacing: 12, childAspectRatio: 1.15, ), itemBuilder: (context, index) { final option = options[index]; return _OptionCard(entry: option); }, ), ], ), ); } } class _OptionEntry { const _OptionEntry({ required this.icon, required this.title, required this.accent, required this.onTap, }); final IconData icon; final String title; final Color accent; final VoidCallback onTap; } class _OptionCard extends StatelessWidget { const _OptionCard({required this.entry}); final _OptionEntry entry; @override Widget build(BuildContext context) { return Material( color: AppTokens.surface, borderRadius: BorderRadius.circular(AppTokens.radiusMedium), child: InkWell( onTap: entry.onTap, borderRadius: BorderRadius.circular(AppTokens.radiusMedium), child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(AppTokens.radiusMedium), border: Border.all(color: AppTokens.border), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Container( width: 48, height: 48, decoration: BoxDecoration( color: entry.accent.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(14), ), child: Icon(entry.icon, color: entry.accent, size: 26), ), const SizedBox(height: 12), // Flexible (no solo maxLines/ellipsis): con titulos largos como // "Política de privacidad" + texto de sistema mas grande // (accesibilidad), el Column pedia mas alto que la celda fija // del grid y tiraba "RenderFlex overflowed" - Flexible deja que // el texto se comprima en vez de reventar el layout. Flexible( child: Text( entry.title, maxLines: 2, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w700, ), ), ), ], ), ), ), ); } }