// ignore_for_file: use_build_context_synchronously import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../app/injection_container.dart'; import '../../core/errors/friendly_error.dart'; import '../../core/network/api_client.dart'; // --------------------------------------------------------------------------- // RegisterScreen // --------------------------------------------------------------------------- // // Step 1: Pilih role (Guardian / User) dengan UI card besar. // • Guardian: icon perisai, "I will guide someone" // • User: icon mata, "I need navigation assistance" // Step 2: Form email, password, displayName. // Submit → POST /auth/register → simpan pending email → dialog sukses → /login. // Jika User: dialog menampilkan uniqueUserId untuk dibagikan ke Guardian. // --------------------------------------------------------------------------- class RegisterScreen extends StatefulWidget { const RegisterScreen({super.key}); @override State createState() => _RegisterScreenState(); } class _RegisterScreenState extends State { final _name = TextEditingController(); final _email = TextEditingController(); final _password = TextEditingController(); bool _showPassword = false; String _role = 'USER'; bool _loading = false; int _step = 0; // 0 = role selection, 1 = form @override void dispose() { _name.dispose(); _email.dispose(); _password.dispose(); super.dispose(); } Future _register() async { if (_name.text.trim().isEmpty || _email.text.trim().isEmpty || _password.text.isEmpty) { _snack(context, 'Isi semua field terlebih dahulu.'); return; } setState(() => _loading = true); await runFriendlyAction( () async { final res = await sl().dio.post('/auth/register', data: { 'displayName': _name.text.trim(), 'email': _email.text.trim(), 'password': _password.text, 'role': _role, }); final data = Map.from(res.data['data'] as Map); if (!mounted) return; final prefs = await SharedPreferences.getInstance(); await prefs.setString('pending_login_email', _email.text.trim()); await _showRegisterSuccess(context, data); if (mounted) context.go('/login'); }, onError: (message) => _snack(context, message), fallback: 'Registrasi gagal. Periksa data akun kamu.', connectionHint: 'Tidak bisa ke server. Pakai URL backend publik/aktif.', ); if (mounted) setState(() => _loading = false); } @override Widget build(BuildContext context) { return _AuthFrame( title: _step == 0 ? 'Create Account' : 'Fill Your Details', subtitle: _step == 0 ? 'Who are you in the WalkGuide system?' : _role == 'USER' ? 'User bisa membuat Pairing Code sementara setelah login.' : 'Guardian dapat monitor dan konfigurasi User.', child: _step == 0 ? _buildRoleStep(context) : _buildFormStep(context), ); } Widget _buildRoleStep(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _RoleCard( selected: _role == 'USER', icon: Icons.accessibility_new, title: 'User', subtitle: 'I need navigation assistance', onTap: () => setState(() => _role = 'USER'), ), const SizedBox(height: 12), _RoleCard( selected: _role == 'GUARDIAN', icon: Icons.shield_outlined, title: 'Guardian', subtitle: 'I will guide someone', onTap: () => setState(() => _role = 'GUARDIAN'), ), const SizedBox(height: 20), FilledButton.icon( onPressed: () => setState(() => _step = 1), icon: const Icon(Icons.arrow_forward), label: Text('Continue as $_role'), ), TextButton( onPressed: () => context.go('/login'), child: const Text('Sudah punya akun')), ], ); } Widget _buildFormStep(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Role indicator chip Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: _role == 'USER' ? const Color(0xFFEFF6FF) : const Color(0xFFF0FDF4), borderRadius: BorderRadius.circular(999), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( _role == 'USER' ? Icons.accessibility_new : Icons.shield_outlined, size: 16, color: _role == 'USER' ? const Color(0xFF1A56DB) : const Color(0xFF16A34A), ), const SizedBox(width: 6), Text( 'Daftar sebagai $_role', style: TextStyle( fontWeight: FontWeight.w700, color: _role == 'USER' ? const Color(0xFF1A56DB) : const Color(0xFF16A34A), ), ), const SizedBox(width: 6), GestureDetector( onTap: () => setState(() => _step = 0), child: const Icon(Icons.edit_outlined, size: 14), ), ], ), ), const SizedBox(height: 16), TextField( controller: _name, textInputAction: TextInputAction.next, decoration: const InputDecoration( labelText: 'Display name', prefixIcon: Icon(Icons.person_outline), )), const SizedBox(height: 12), TextField( controller: _email, keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.next, decoration: const InputDecoration( labelText: 'Email', prefixIcon: Icon(Icons.email_outlined), )), const SizedBox(height: 12), TextField( controller: _password, obscureText: !_showPassword, textInputAction: TextInputAction.done, onSubmitted: (_) => _register(), decoration: InputDecoration( labelText: 'Password', prefixIcon: const Icon(Icons.lock_outline), suffixIcon: IconButton( icon: Icon( _showPassword ? Icons.visibility_off : Icons.visibility), onPressed: () => setState(() => _showPassword = !_showPassword), ), )), const SizedBox(height: 18), FilledButton.icon( onPressed: _loading ? null : _register, icon: _loading ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2)) : const Icon(Icons.person_add_alt_1), label: const Text('Register'), ), TextButton( onPressed: () => context.go('/login'), child: const Text('Sudah punya akun')), ], ); } } // --------------------------------------------------------------------------- // _RoleCard // --------------------------------------------------------------------------- class _RoleCard extends StatelessWidget { final bool selected; final IconData icon; final String title; final String subtitle; final VoidCallback onTap; const _RoleCard({ required this.selected, required this.icon, required this.title, required this.subtitle, required this.onTap, }); @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 180), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: selected ? const Color(0xFFEFF6FF) : Colors.white, borderRadius: BorderRadius.circular(14), border: Border.all( color: selected ? const Color(0xFF1A56DB) : const Color(0xFFE2E8F0), width: selected ? 2 : 1, ), ), child: Row( children: [ Container( width: 48, height: 48, decoration: BoxDecoration( color: selected ? const Color(0xFF1A56DB) : const Color(0xFFF1F5F9), borderRadius: BorderRadius.circular(12), ), child: Icon(icon, color: selected ? Colors.white : const Color(0xFF64748B)), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: const TextStyle( fontWeight: FontWeight.w800, fontSize: 16)), Text(subtitle, style: const TextStyle( color: Color(0xFF64748B), fontSize: 13)), ], ), ), if (selected) const Icon(Icons.check_circle_rounded, color: Color(0xFF1A56DB)), ], ), ), ); } } // --------------------------------------------------------------------------- // Shared private widgets // --------------------------------------------------------------------------- class _AuthFrame extends StatelessWidget { final String title; final String subtitle; final Widget child; const _AuthFrame( {required this.title, required this.subtitle, required this.child}); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFEAF4FF), body: Stack( children: [ const Positioned.fill( child: DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFFD9EEFF), Color(0xFFF7FAFC)], ), ), ), ), Positioned( top: -90, right: -60, child: TweenAnimationBuilder( tween: Tween(begin: 0.85, end: 1), duration: const Duration(milliseconds: 900), curve: Curves.easeOutCubic, builder: (_, value, child) => Transform.scale( scale: value, child: child, ), child: Container( width: 260, height: 260, decoration: BoxDecoration( color: const Color(0xFF2563EB).withValues(alpha: 0.14), shape: BoxShape.circle, ), ), ), ), Center( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 430), child: TweenAnimationBuilder( tween: Tween(begin: 18, end: 0), duration: const Duration(milliseconds: 520), curve: Curves.easeOutCubic, builder: (_, offset, child) => Opacity( opacity: (1 - offset / 18).clamp(0.0, 1.0), child: Transform.translate( offset: Offset(0, offset), child: child, ), ), child: RepaintBoundary( child: Container( decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.96), borderRadius: BorderRadius.circular(30), border: Border.all( color: Colors.white.withValues(alpha: 0.8)), boxShadow: [ BoxShadow( color: const Color(0xFF1E3A8A).withValues(alpha: 0.14), blurRadius: 40, offset: const Offset(0, 20), ), ], ), child: Padding( padding: const EdgeInsets.fromLTRB(24, 26, 24, 24), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( children: [ Container( width: 56, height: 56, decoration: BoxDecoration( color: const Color(0xFF1D4ED8), borderRadius: BorderRadius.circular(18), ), child: const Icon(Icons.navigation_rounded, color: Colors.white, size: 30), ), const SizedBox(width: 12), const Expanded( child: Text( 'WalkGuide', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w900, color: Color(0xFF0F172A), ), ), ), ], ), const SizedBox(height: 22), Text( title, style: Theme.of(context) .textTheme .headlineMedium ?.copyWith( fontWeight: FontWeight.w900, color: const Color(0xFF0F172A), ), ), const SizedBox(height: 6), Text( subtitle, style: const TextStyle( color: Color(0xFF64748B), height: 1.35, ), ), const SizedBox(height: 26), child, ], ), ), ), ), ), ), ), ), ], ), ); } } // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- Future _showRegisterSuccess( BuildContext context, Map data) async { final uniqueId = data['uniqueUserId']?.toString(); final message = uniqueId == null || uniqueId.isEmpty ? 'Registrasi berhasil. Silakan login.' : 'Registrasi berhasil.\n\nSilakan login, lalu buka menu Pairing Code untuk membuat kode sementara yang bisa dibagikan ke Guardian.'; _snack( context, uniqueId == null ? 'Registrasi berhasil.' : 'Registrasi berhasil. Buat Pairing Code setelah login.'); await showDialog( context: context, builder: (dialogContext) => AlertDialog( title: const Text('Register Successful'), content: SelectableText(message), actions: [ FilledButton( onPressed: () => Navigator.of(dialogContext).pop(), child: const Text('Login sekarang'), ), ], ), ); } void _snack(BuildContext context, String message) { if (context.mounted) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(message))); } }