import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../../../core/logging/logger.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../../shared/theme/app_color_palette.dart'; import '../../../../shared/theme/design_tokens.dart'; import '../../../../shared/widgets/toast/toast.dart'; import '../../../../shared/widgets/toast/toast_type.dart'; import '../../data/repositories/invite_repository.dart'; class InviteScreen extends StatefulWidget { const InviteScreen({super.key, required this.inviteRepository}); final InviteRepository inviteRepository; @override State createState() => _InviteScreenState(); } class _InviteScreenState extends State { final Logger _logger = getLogger('features.settings.invite_screen'); final _bindCodeController = TextEditingController(); final _formKey = GlobalKey(); bool _isBinding = false; bool _isGenerating = false; bool _isLoading = true; bool _hasError = false; String? _myInviteCode; int _invitedCount = 0; final bool _hasInviter = false; @override void initState() { super.initState(); _loadInviteCode(); } Future _loadInviteCode() async { setState(() { _isLoading = true; _hasError = false; }); try { final result = await widget.inviteRepository.getMyInviteCode(); if (!mounted) return; setState(() { _myInviteCode = result.code; _invitedCount = result.usedCount; _isLoading = false; }); } catch (error, stackTrace) { _logger.error( message: 'Failed to load invite code', error: error, stackTrace: stackTrace, ); if (!mounted) return; setState(() { _hasError = true; _isLoading = false; }); } } @override void dispose() { _bindCodeController.dispose(); super.dispose(); } bool get _hasMyInviteCode => _myInviteCode != null && _myInviteCode!.isNotEmpty; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final colors = Theme.of(context).colorScheme; if (_isLoading) { return Scaffold( backgroundColor: colors.surfaceContainerLow, appBar: AppBar( title: Text(l10n.settingsInviteTitle), centerTitle: true, backgroundColor: colors.surfaceContainerLow, surfaceTintColor: colors.surfaceContainerLow, ), body: const Center(child: CircularProgressIndicator()), ); } if (_hasError) { return Scaffold( backgroundColor: colors.surfaceContainerLow, appBar: AppBar( title: Text(l10n.settingsInviteTitle), centerTitle: true, backgroundColor: colors.surfaceContainerLow, surfaceTintColor: colors.surfaceContainerLow, ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( l10n.settingsInviteEmptyTitle, style: Theme.of(context).textTheme.bodyLarge, ), const SizedBox(height: AppSpacing.md), FilledButton( onPressed: _loadInviteCode, child: const Text('Retry'), ), ], ), ), ); } return Scaffold( backgroundColor: colors.surfaceContainerLow, appBar: AppBar( title: Text(l10n.settingsInviteTitle), centerTitle: true, backgroundColor: colors.surfaceContainerLow, surfaceTintColor: colors.surfaceContainerLow, ), body: ListView( padding: const EdgeInsets.all(AppSpacing.lg), children: [ if (_hasMyInviteCode) ...[ _InviteCodeCard( inviteCode: _myInviteCode!, onCopy: _copyInviteCode, ), const SizedBox(height: AppSpacing.lg), _InviteStatsCard(count: _invitedCount), const SizedBox(height: AppSpacing.xl), _BindCodeSection( controller: _bindCodeController, formKey: _formKey, isBinding: _isBinding, onBind: _bindInviteCode, ), ] else ...[ _EmptyStateCard( controller: _bindCodeController, formKey: _formKey, isBinding: _isBinding, isGenerating: _isGenerating, hasInviter: _hasInviter, onBind: _bindInviteCode, onGenerate: _generateInviteCode, ), ], ], ), ); } void _copyInviteCode() { final l10n = AppLocalizations.of(context)!; Clipboard.setData(ClipboardData(text: _myInviteCode!)); Toast.show( context, l10n.settingsInviteCopySuccess, type: ToastType.success, ); } Future _bindInviteCode() async { if (!_formKey.currentState!.validate()) return; setState(() => _isBinding = true); // Simulate API call await Future.delayed(const Duration(seconds: 1)); if (!mounted) return; final l10n = AppLocalizations.of(context)!; Toast.show( context, l10n.settingsInviteBindSuccess, type: ToastType.success, ); setState(() => _isBinding = false); } Future _generateInviteCode() async { setState(() => _isGenerating = true); // Simulate API call await Future.delayed(const Duration(seconds: 1)); if (!mounted) return; final l10n = AppLocalizations.of(context)!; Toast.show( context, l10n.settingsInviteGenerateSuccess, type: ToastType.success, ); setState(() => _isGenerating = false); } } class _InviteCodeCard extends StatelessWidget { const _InviteCodeCard({required this.inviteCode, required this.onCopy}); final String inviteCode; final VoidCallback onCopy; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final colors = Theme.of(context).colorScheme; final palette = Theme.of(context).extension()!; return Stack( children: [ Container( width: double.infinity, padding: const EdgeInsets.all(AppSpacing.xl), decoration: BoxDecoration( borderRadius: BorderRadius.circular(AppRadius.xl), gradient: LinearGradient( colors: [colors.primary, palette.accentPurple], ), ), child: Column( children: [ Icon( Icons.card_giftcard_rounded, color: colors.onPrimary, size: 40, ), const SizedBox(height: AppSpacing.md), Text( l10n.settingsInviteMyCode, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: colors.onPrimary.withValues(alpha: 0.88), ), ), const SizedBox(height: AppSpacing.md), Text( inviteCode, style: Theme.of(context).textTheme.headlineLarge?.copyWith( color: colors.onPrimary, fontWeight: FontWeight.w700, letterSpacing: 6, ), ), ], ), ), Positioned( top: AppSpacing.sm, right: AppSpacing.sm, child: Material( color: colors.onPrimary.withValues(alpha: 0.18), borderRadius: BorderRadius.circular(AppRadius.full), child: InkWell( onTap: onCopy, borderRadius: BorderRadius.circular(AppRadius.full), child: Container( padding: const EdgeInsets.all(AppSpacing.sm), child: Icon( Icons.copy_rounded, color: colors.onPrimary, size: 20, ), ), ), ), ), ], ); } } class _InviteStatsCard extends StatelessWidget { const _InviteStatsCard({required this.count}); final int count; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final colors = Theme.of(context).colorScheme; return Card( margin: EdgeInsets.zero, elevation: 0, color: colors.surface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.xl), ), child: Padding( padding: const EdgeInsets.all(AppSpacing.lg), child: Row( children: [ Container( width: 48, height: 48, decoration: BoxDecoration( color: colors.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(AppRadius.md), ), child: Icon(Icons.people_outline_rounded, color: colors.primary), ), const SizedBox(width: AppSpacing.md), Expanded( child: Text( l10n.settingsInviteStats(count), style: Theme.of(context).textTheme.titleMedium, ), ), ], ), ), ); } } class _BindCodeSection extends StatelessWidget { const _BindCodeSection({ required this.controller, required this.formKey, required this.isBinding, required this.onBind, }); final TextEditingController controller; final GlobalKey formKey; final bool isBinding; final Future Function() onBind; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final colors = Theme.of(context).colorScheme; return Card( margin: EdgeInsets.zero, elevation: 0, color: colors.surface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.xl), ), child: Padding( padding: const EdgeInsets.all(AppSpacing.lg), child: Form( key: formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.link_rounded, color: colors.primary, size: 20), const SizedBox(width: AppSpacing.sm), Text( l10n.settingsInviteBindCode, style: Theme.of(context).textTheme.titleMedium, ), ], ), const SizedBox(height: AppSpacing.md), TextFormField( controller: controller, textCapitalization: TextCapitalization.characters, decoration: InputDecoration( hintText: l10n.settingsInviteBindPlaceholder, filled: true, fillColor: colors.surfaceContainerHighest, border: OutlineInputBorder( borderRadius: BorderRadius.circular(AppRadius.md), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric( horizontal: AppSpacing.md, vertical: AppSpacing.md, ), ), inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9]')), LengthLimitingTextInputFormatter(6), ], validator: (value) { if (value == null || value.isEmpty) { return null; // Optional field } if (value.length != 6) { return l10n.settingsInviteInvalidCode; } return null; }, ), const SizedBox(height: AppSpacing.md), SizedBox( width: double.infinity, child: FilledButton( onPressed: null, style: FilledButton.styleFrom( elevation: 0, padding: const EdgeInsets.symmetric( vertical: AppSpacing.md, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.full), ), ), child: Text(l10n.settingsComingSoon), ), ), ], ), ), ), ); } } class _EmptyStateCard extends StatelessWidget { const _EmptyStateCard({ required this.controller, required this.formKey, required this.isBinding, required this.isGenerating, required this.hasInviter, required this.onBind, required this.onGenerate, }); final TextEditingController controller; final GlobalKey formKey; final bool isBinding; final bool isGenerating; final bool hasInviter; final Future Function() onBind; final Future Function() onGenerate; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final colors = Theme.of(context).colorScheme; final palette = Theme.of(context).extension()!; return Column( children: [ Container( padding: const EdgeInsets.all(AppSpacing.xxl), decoration: BoxDecoration( color: colors.surface, borderRadius: BorderRadius.circular(AppRadius.xl), ), child: Column( children: [ Container( width: 80, height: 80, decoration: BoxDecoration( gradient: LinearGradient( colors: [ colors.primary.withValues(alpha: 0.1), palette.accentPurple.withValues(alpha: 0.1), ], ), borderRadius: BorderRadius.circular(AppRadius.xl), ), child: Icon( Icons.celebration_rounded, color: colors.primary, size: 40, ), ), const SizedBox(height: AppSpacing.lg), Text( l10n.settingsInviteEmptyTitle, style: Theme.of( context, ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700), textAlign: TextAlign.center, ), const SizedBox(height: AppSpacing.sm), Text( l10n.settingsInviteEmptyDescription, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: colors.onSurfaceVariant, ), textAlign: TextAlign.center, ), ], ), ), const SizedBox(height: AppSpacing.lg), if (!hasInviter) ...[ Card( margin: EdgeInsets.zero, elevation: 0, color: colors.surface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.xl), ), child: Padding( padding: const EdgeInsets.all(AppSpacing.lg), child: Form( key: formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( l10n.settingsInviteInputLabel, style: Theme.of(context).textTheme.titleSmall?.copyWith( color: colors.onSurfaceVariant, ), ), const SizedBox(height: AppSpacing.sm), TextFormField( controller: controller, textCapitalization: TextCapitalization.characters, decoration: InputDecoration( hintText: l10n.settingsInviteInputHint, filled: true, fillColor: colors.surfaceContainerHighest, border: OutlineInputBorder( borderRadius: BorderRadius.circular(AppRadius.md), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric( horizontal: AppSpacing.md, vertical: AppSpacing.md, ), ), inputFormatters: [ FilteringTextInputFormatter.allow( RegExp(r'[A-Za-z0-9]'), ), LengthLimitingTextInputFormatter(6), ], validator: (value) { if (value == null || value.isEmpty) { return null; } if (value.length != 6) { return l10n.settingsInviteInvalidCode; } return null; }, ), ], ), ), ), ), const SizedBox(height: AppSpacing.md), ], SizedBox( width: double.infinity, child: FilledButton( onPressed: isGenerating ? null : onGenerate, style: FilledButton.styleFrom( elevation: 0, padding: const EdgeInsets.symmetric(vertical: AppSpacing.md), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.full), ), ), child: isGenerating ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.add_rounded, size: 20), const SizedBox(width: AppSpacing.sm), Text(l10n.settingsInviteGenerateButton), ], ), ), ), ], ); } }