import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../l10n/app_localizations.dart'; import '../utils/navigation.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(); }); } AppLocalizations get _l10n => AppLocalizations.of(context); @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, appBar: AppBar( backgroundColor: Colors.transparent, leading: IconButton( tooltip: l10n.cancel, onPressed: () => context.popOrGo('/admin'), icon: const Icon(Icons.close_rounded), ), ), 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( _l10n.connectingToServer, 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( _l10n.failedToConnect, style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 12), Text( vm.errorMessage ?? _l10n.unknownError, 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: Text(_l10n.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( _l10n.connectToRemoteServer, textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineSmall, ), const SizedBox(height: 8), Text( _l10n.selectVenueToLink, 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( _l10n.selectVenue, 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: Text(_l10n.refreshVenues), ), ], ); } 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( _l10n.enterDeviceDetails, 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: InputDecoration( labelText: _l10n.deviceName, hintText: 'e.g. Bar terminal 1', border: OutlineInputBorder(), ), validator: (v) => v == null || v.trim().isEmpty ? _l10n.deviceNameRequired : null, ), const SizedBox(height: 16), TextFormField( controller: _regCodeController, decoration: InputDecoration( labelText: _l10n.registrationCode, hintText: '6-character code from dashboard', border: OutlineInputBorder(), counterText: '', ), maxLength: 6, textCapitalization: TextCapitalization.characters, validator: (v) => v == null || v.trim().length < 6 ? _l10n.enterRegistrationCode : 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: Text(_l10n.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: Text(_l10n.register), ), ), ], ), ], ), ); } Widget _buildRegistering() { return Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 60), const CircularProgressIndicator(strokeWidth: 3), const SizedBox(height: 24), Text( _l10n.registeringDevice, 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( _l10n.deviceRegistered, 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( _l10n.openTabsWillSync, 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: Text(_l10n.done), ), ], ); } }