import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../l10n/app_localizations.dart'; import '../viewmodels/registration_view_model.dart'; import '../viewmodels/remote_sync_view_model.dart'; class RegistrationView extends StatefulWidget { const RegistrationView({super.key}); @override State createState() => _RegistrationViewState(); } class _RegistrationViewState extends State { final _deviceNameController = TextEditingController(text: 'Bar terminal 1'); final _regCodeController = TextEditingController(); final _formKey = GlobalKey(); @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { context.read().fetchVenues(); }); } @override void dispose() { _deviceNameController.dispose(); _regCodeController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final vm = context.watch(); final l10n = AppLocalizations.of(context); return Scaffold( backgroundColor: Theme.of(context).colorScheme.surface, body: SafeArea( child: Center( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 24), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 480), child: _buildStep(vm, l10n), ), ), ), ), ); } Widget _buildStep(RegistrationViewModel vm, AppLocalizations l10n) { switch (vm.step) { case RegistrationStep.loadingVenues: return _buildLoading(); case RegistrationStep.error: return _buildError(vm); case RegistrationStep.venuesLoaded: if (vm.selectedVenue == null) { return _buildVenueSelection(vm, l10n); } return _buildDeviceDetails(vm, l10n); case RegistrationStep.registering: return _buildRegistering(); case RegistrationStep.registered: return _buildSuccess(vm, l10n); case RegistrationStep.initial: return _buildLoading(); } } Widget _buildLoading() { return Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 60), Icon( Icons.cloud_outlined, size: 64, color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.5), ), const SizedBox(height: 24), const CircularProgressIndicator(strokeWidth: 3), const SizedBox(height: 16), Text( 'Connecting to server…', style: Theme.of(context).textTheme.titleMedium, ), ], ); } Widget _buildError(RegistrationViewModel vm) { return Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 60), Icon( Icons.cloud_off_rounded, size: 64, color: Theme.of(context).colorScheme.error, ), const SizedBox(height: 24), Text( 'Failed to connect', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 12), Text( vm.errorMessage ?? 'Unknown error', textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of( context, ).colorScheme.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(height: 24), FilledButton.icon( onPressed: () => vm.retry(), icon: const Icon(Icons.refresh), label: const Text('Retry'), ), ], ); } Widget _buildVenueSelection(RegistrationViewModel vm, AppLocalizations l10n) { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 40), Icon( Icons.cloud_outlined, size: 48, color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.5), ), const SizedBox(height: 16), Text( 'Connect to remote server', textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineSmall, ), const SizedBox(height: 8), Text( 'Select your venue to link this device', textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of( context, ).colorScheme.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(height: 32), Text( 'Select venue', style: Theme.of(context).textTheme.labelLarge?.copyWith( color: Theme.of( context, ).colorScheme.onSurface.withValues(alpha: 0.7), ), ), const SizedBox(height: 8), Container( decoration: BoxDecoration( border: Border.all( color: Theme.of( context, ).colorScheme.onSurface.withValues(alpha: 0.15), ), borderRadius: BorderRadius.circular(12), ), child: Column( children: [ for (final venue in vm.venues) Material( color: Colors.transparent, child: InkWell( onTap: () => vm.selectVenue(venue), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), child: Row( children: [ Icon( Icons.store_outlined, color: Theme.of(context).colorScheme.primary, ), const SizedBox(width: 12), Expanded( child: Text( venue.name, style: Theme.of(context).textTheme.bodyLarge, ), ), Icon( Icons.chevron_right_rounded, color: Theme.of( context, ).colorScheme.onSurface.withValues(alpha: 0.3), ), ], ), ), ), ), ], ), ), const SizedBox(height: 24), TextButton( onPressed: () => vm.retry(), child: const Text('Refresh venues'), ), ], ); } Widget _buildDeviceDetails(RegistrationViewModel vm, AppLocalizations l10n) { return Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 40), Icon( Icons.store_outlined, size: 48, color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.5), ), const SizedBox(height: 16), Text( vm.selectedVenue!.name, textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineSmall, ), const SizedBox(height: 8), Text( 'Enter device details to complete pairing', textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of( context, ).colorScheme.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(height: 32), TextFormField( controller: _deviceNameController, decoration: const InputDecoration( labelText: 'Device name', hintText: 'e.g. Bar terminal 1', border: OutlineInputBorder(), ), validator: (v) => v == null || v.trim().isEmpty ? 'Device name is required' : null, ), const SizedBox(height: 16), TextFormField( controller: _regCodeController, decoration: const InputDecoration( labelText: 'Registration code', hintText: '6-character code from dashboard', border: OutlineInputBorder(), counterText: '', ), maxLength: 6, textCapitalization: TextCapitalization.characters, validator: (v) => v == null || v.trim().length < 6 ? 'Enter the 6-character code' : null, ), const SizedBox(height: 8), if (vm.errorMessage != null) Padding( padding: const EdgeInsets.only(bottom: 8), child: Text( vm.errorMessage!, style: TextStyle( color: Theme.of(context).colorScheme.error, fontSize: 13, ), ), ), const SizedBox(height: 16), Row( children: [ TextButton( onPressed: () { vm.reset(); }, child: const Text('Back'), ), const SizedBox(width: 12), Expanded( child: FilledButton( onPressed: () async { if (!_formKey.currentState!.validate()) return; await vm.registerDevice( deviceName: _deviceNameController.text.trim(), registrationCode: _regCodeController.text .trim() .toUpperCase(), ); }, child: const Text('Register'), ), ), ], ), ], ), ); } Widget _buildRegistering() { return Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 60), const CircularProgressIndicator(strokeWidth: 3), const SizedBox(height: 24), Text( 'Registering device…', style: Theme.of(context).textTheme.titleMedium, ), ], ); } Widget _buildSuccess(RegistrationViewModel vm, AppLocalizations l10n) { return Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 40), Icon( Icons.check_circle_rounded, size: 80, color: Theme.of(context).colorScheme.primary, ), const SizedBox(height: 24), Text( 'Device registered!', textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineSmall, ), const SizedBox(height: 12), Text( '${vm.selectedVenue?.name ?? "Venue"} is now linked.', textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of( context, ).colorScheme.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(height: 8), Text( 'Open tabs will sync to the dashboard.', textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of( context, ).colorScheme.onSurface.withValues(alpha: 0.5), ), ), const SizedBox(height: 32), FilledButton.icon( // Once configured, the router redirects to /bar by itself. onPressed: () => context.read().activate(), icon: const Icon(Icons.arrow_forward), label: const Text('Start using KoolTap'), ), ], ); } }