// 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'; import '../../core/theme/app_colors.dart'; import '../../core/theme/app_decorations.dart'; import '../../core/theme/app_text_styles.dart'; import '../../shared/widgets/animations/animations.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. Cek URL backend di Connect Server dan pastikan server 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' ? AppColors.softBlueBg : 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 BounceTap( onTap: onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 180), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: selected ? AppColors.softBlueBg : Colors.white, borderRadius: BorderRadius.circular(20), border: Border.all( color: selected ? AppColors.primaryBlue : AppColors.border, width: selected ? 2 : 1, ), boxShadow: selected ? AppDecorations.cardShadow : null, ), child: Row( children: [ Container( width: 48, height: 48, decoration: BoxDecoration( color: selected ? AppColors.primaryBlue : const Color(0xFFF1F5F9), borderRadius: BorderRadius.circular(50), ), child: Icon(icon, color: selected ? Colors.white : const Color(0xFF64748B)), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: AppTextStyles.subheading.copyWith(fontSize: 16)), Text(subtitle, style: const TextStyle( color: AppColors.muted, fontSize: 13)), ], ), ), if (selected) const Icon(Icons.check_circle_rounded, color: AppColors.primaryBlue), ], ), ), ); } } // --------------------------------------------------------------------------- // 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: AppColors.softBlueBg, body: LayoutBuilder( builder: (context, constraints) { final compact = constraints.maxWidth < 480 || constraints.maxHeight < 720; return DecoratedBox( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ AppColors.softBlueBg, Colors.white, AppColors.softPinkBg, ], ), ), child: SafeArea( child: Center( child: SingleChildScrollView( keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, padding: EdgeInsets.fromLTRB( compact ? 14 : 24, compact ? 12 : 24, compact ? 14 : 24, 20 + MediaQuery.of(context).viewInsets.bottom, ), child: ConstrainedBox( constraints: BoxConstraints(maxWidth: compact ? 380 : 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: AppColors.cardWhite, borderRadius: BorderRadius.circular(compact ? 22 : 28), boxShadow: AppDecorations.cardShadow, ), child: Padding( padding: EdgeInsets.fromLTRB( compact ? 18 : 24, compact ? 18 : 26, compact ? 18 : 24, compact ? 18 : 24, ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( children: [ Container( width: compact ? 44 : 56, height: compact ? 44 : 56, decoration: BoxDecoration( gradient: AppDecorations.blueGradient, borderRadius: BorderRadius.circular(16), boxShadow: const [ BoxShadow( color: Color(0x334A90D9), blurRadius: 18, offset: Offset(0, 8), ), ], ), child: Icon(Icons.navigation_rounded, color: Colors.white, size: compact ? 26 : 30), ), const SizedBox(width: 12), const Expanded( child: Text( 'WalkGuide', maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w800, color: AppColors.textDark, ), ), ), ], ), SizedBox(height: compact ? 14 : 22), Text( title, maxLines: 2, overflow: TextOverflow.ellipsis, style: AppTextStyles.heading.copyWith( fontSize: compact ? 26 : null, fontWeight: FontWeight.w800, ), ), const SizedBox(height: 6), Text( subtitle, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.muted, height: 1.35, ), ), SizedBox(height: compact ? 18 : 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))); } }