import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:airhub_app/theme/design_tokens.dart'; import 'package:airhub_app/widgets/animated_gradient_background.dart'; import 'package:airhub_app/widgets/ios_toast.dart'; import 'package:airhub_app/features/user/presentation/controllers/user_controller.dart'; import 'package:image_picker/image_picker.dart'; import 'dart:typed_data'; class ProfileInfoPage extends ConsumerStatefulWidget { const ProfileInfoPage({super.key}); @override ConsumerState createState() => _ProfileInfoPageState(); } class _ProfileInfoPageState extends ConsumerState { String _gender = ''; String _birthday = ''; Uint8List? _avatarBytes; String? _avatarUrl; late final TextEditingController _nicknameController; bool _initialized = false; @override void initState() { super.initState(); _nicknameController = TextEditingController(); } @override void dispose() { _nicknameController.dispose(); super.dispose(); } void _initFromUser() { if (_initialized) return; final userAsync = ref.read(userControllerProvider); final user = userAsync.value; if (user != null) { _nicknameController.text = user.nickname ?? ''; _gender = user.gender ?? ''; _birthday = user.birthday ?? ''; _avatarUrl = user.avatar; _initialized = true; } } @override Widget build(BuildContext context) { final userAsync = ref.watch(userControllerProvider); // 首次从用户数据初始化表单 userAsync.whenData((_) => _initFromUser()); return Scaffold( backgroundColor: Colors.transparent, body: Stack( children: [ const AnimatedGradientBackground(), Column( children: [ _buildHeader(context), Expanded( child: SingleChildScrollView( padding: EdgeInsets.only( top: 20, left: AppSpacing.lg, right: AppSpacing.lg, bottom: 40 + MediaQuery.of(context).padding.bottom, ), child: Column( children: [ const SizedBox(height: 20), _buildAvatarSection(), const SizedBox(height: 32), _buildFormCard(), ], ), ), ), ], ), ], ), ); } Widget _buildHeader(BuildContext context) { return Container( padding: EdgeInsets.only( top: MediaQuery.of(context).padding.top + 20, left: AppSpacing.lg, right: AppSpacing.lg, bottom: AppSpacing.md, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _buildBackButton(context), Text('个人信息', style: AppTextStyles.title), _buildSaveButton(), ], ), ); } Widget _buildBackButton(BuildContext context) { return GestureDetector( onTap: () => Navigator.pop(context), child: Container( width: 40, height: 40, decoration: BoxDecoration( color: AppColors.iconBtnBg, borderRadius: BorderRadius.circular(AppRadius.button), ), child: const Icon( Icons.arrow_back_ios_new, color: AppColors.textPrimary, size: 18, ), ), ); } Widget _buildSaveButton() { return GestureDetector( onTap: _saveProfile, child: Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), decoration: BoxDecoration( gradient: const LinearGradient( colors: AppColors.saveBtnGradient, begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(20), ), child: const Text( '保存', style: TextStyle( color: Colors.white, fontWeight: FontWeight.w600, fontSize: 14, ), ), ), ); } Future _saveProfile() async { // 转换性别为后端格式 String? genderCode; if (_gender == '男') { genderCode = 'M'; } else if (_gender == '女') { genderCode = 'F'; } final success = await ref.read(userControllerProvider.notifier).updateProfile( nickname: _nicknameController.text.trim(), gender: genderCode, birthday: _birthday.isNotEmpty ? _birthday : null, ); if (mounted) { if (success) { AppToast.show(context, '保存成功'); Navigator.pop(context); } else { AppToast.show(context, '保存失败,请重试', isError: true); } } } Widget _buildAvatarSection() { return Stack( children: [ Container( width: 100, height: 100, decoration: const BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( colors: AppColors.avatarGradient, begin: Alignment.topLeft, end: Alignment.bottomRight, ), boxShadow: [ BoxShadow( color: Color(0x338B5E3C), blurRadius: 24, offset: Offset(0, 8), ), ], ), child: ClipOval( child: _avatarBytes != null ? Image.memory(_avatarBytes!, fit: BoxFit.cover) : (_avatarUrl != null && _avatarUrl!.isNotEmpty) ? Image.network( _avatarUrl!, fit: BoxFit.cover, errorBuilder: (ctx, err, stack) => Image.asset('assets/www/Capybara.png', fit: BoxFit.cover), ) : Image.asset( 'assets/www/Capybara.png', fit: BoxFit.cover, errorBuilder: (ctx, err, stack) => const Icon(Icons.person, color: Colors.white, size: 40), ), ), ), Positioned( bottom: 0, right: 0, child: GestureDetector( onTap: _pickImage, child: Container( width: 32, height: 32, decoration: BoxDecoration( gradient: const LinearGradient( colors: AppColors.saveBtnGradient, ), shape: BoxShape.circle, border: Border.all(color: Colors.white, width: 3), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.15), offset: const Offset(0, 2), blurRadius: 8, ), ], ), child: const Icon( Icons.camera_alt, color: Colors.white, size: 14, ), ), ), ), ], ); } Future _pickImage() async { try { final ImagePicker picker = ImagePicker(); final XFile? image = await picker.pickImage(source: ImageSource.gallery); if (image != null) { final bytes = await image.readAsBytes(); setState(() { _avatarBytes = bytes; }); // 上传头像到服务器 final success = await ref.read(userControllerProvider.notifier).changeAvatar(image.path); if (mounted && !success) { AppToast.show(context, '头像上传失败', isError: true); } } } catch (e) { if (mounted) { AppToast.show(context, '选择图片失败', isError: true); } } } Widget _buildFormCard() { return Container( decoration: BoxDecoration( color: AppColors.cardSurface, borderRadius: BorderRadius.circular(AppRadius.card), boxShadow: const [AppShadows.card], ), child: Column( children: [ _buildInputItem('昵称', _nicknameController), _buildSelectionItem( '性别', _gender.isEmpty ? '未设置' : _gender, onTap: _showGenderModal, ), _buildSelectionItem( '生日', _birthday.isEmpty ? '未设置' : _birthday, showDivider: false, onTap: _showBirthdayInput, ), ], ), ); } Widget _buildInputItem(String label, TextEditingController controller) { return Container( padding: const EdgeInsets.symmetric( horizontal: AppSpacing.lg, vertical: 18, ), decoration: const BoxDecoration( border: Border(bottom: BorderSide(color: AppColors.divider)), ), child: Row( children: [ SizedBox( width: 80, child: Text( label, style: const TextStyle(color: AppColors.formLabel, fontSize: 15), ), ), Expanded( child: TextField( controller: controller, textAlign: TextAlign.right, decoration: const InputDecoration.collapsed( hintText: '请输入', hintStyle: TextStyle(color: AppColors.textHint), ), style: const TextStyle( color: AppColors.textPrimary, fontSize: 15, ), ), ), ], ), ); } Widget _buildSelectionItem( String label, String value, { bool showDivider = true, VoidCallback? onTap, }) { return InkWell( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric( horizontal: AppSpacing.lg, vertical: 18, ), decoration: showDivider ? const BoxDecoration( border: Border(bottom: BorderSide(color: AppColors.divider)), ) : null, child: Row( children: [ SizedBox( width: 80, child: Text( label, style: const TextStyle( color: AppColors.formLabel, fontSize: 15, ), ), ), Expanded( child: Text( value, textAlign: TextAlign.right, style: const TextStyle( color: AppColors.textPrimary, fontSize: 15, ), ), ), const SizedBox(width: 8), const Icon( Icons.chevron_right, color: AppColors.textHint, size: 18, ), ], ), ), ); } void _showGenderModal() { showModalBottomSheet( context: context, backgroundColor: Colors.transparent, builder: (context) => Container( padding: const EdgeInsets.fromLTRB(20, 24, 20, 20), decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color(0xFFFFFDF9), Color(0xFFFFF8F0), ], ), borderRadius: BorderRadius.vertical(top: Radius.circular(24)), boxShadow: [ BoxShadow( color: Color(0x148B5E3C), blurRadius: 20, offset: Offset(0, -4), ), ], ), child: SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 36, height: 4, decoration: BoxDecoration( color: const Color(0xFFE8C9A8), borderRadius: BorderRadius.circular(2), ), ), const SizedBox(height: 20), Text('选择性别', style: AppTextStyles.title), const SizedBox(height: 24), _buildGenderOption('男'), const SizedBox(height: 12), _buildGenderOption('女'), const SizedBox(height: 16), GestureDetector( onTap: () => Navigator.pop(context), child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 14), decoration: BoxDecoration( color: const Color(0xFFF5F0EB), borderRadius: BorderRadius.circular(16), ), alignment: Alignment.center, child: const Text( '取消', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, color: Color(0xFF6B7280), ), ), ), ), ], ), ), ), ); } Widget _buildGenderOption(String label) { final isSelected = _gender == label; return GestureDetector( onTap: () { setState(() => _gender = label); Navigator.pop(context); }, child: AnimatedContainer( duration: const Duration(milliseconds: 200), width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 16), decoration: BoxDecoration( color: isSelected ? const Color(0xFFFFF5EB) : Colors.white.withOpacity(0.8), borderRadius: BorderRadius.circular(16), border: Border.all( color: isSelected ? const Color(0xFFECCFA8) : const Color(0xFFE5E7EB), width: isSelected ? 1.5 : 1, ), boxShadow: isSelected ? [ BoxShadow( color: const Color(0xFFECCFA8).withOpacity(0.25), blurRadius: 12, offset: const Offset(0, 4), ), ] : null, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( label, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: isSelected ? const Color(0xFFB07D5A) : const Color(0xFF374151), ), ), ], ), ), ); } void _showBirthdayInput() { DateTime tempDate = DateTime.tryParse(_birthday) ?? DateTime(1994, 12, 9); showModalBottomSheet( context: context, backgroundColor: Colors.transparent, builder: (context) => Container( height: 340, decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color(0xFFFFFDF9), Color(0xFFFFF8F0), ], ), borderRadius: BorderRadius.vertical(top: Radius.circular(24)), boxShadow: [ BoxShadow( color: Color(0x148B5E3C), blurRadius: 20, offset: Offset(0, -4), ), ], ), child: Column( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), decoration: const BoxDecoration( border: Border( bottom: BorderSide(color: Color(0x0D000000)), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ GestureDetector( onTap: () => Navigator.pop(context), child: const Text( '取消', style: TextStyle( fontSize: 16, color: Color(0xFF6B7280), ), ), ), Text( '选择生日', style: AppTextStyles.title.copyWith(fontSize: 17), ), GestureDetector( onTap: () { setState(() { _birthday = "${tempDate.year}-${tempDate.month.toString().padLeft(2, '0')}-${tempDate.day.toString().padLeft(2, '0')}"; }); Navigator.pop(context); }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( gradient: const LinearGradient( colors: AppColors.saveBtnGradient, ), borderRadius: BorderRadius.circular(16), ), child: const Text( '确定', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.white, ), ), ), ), ], ), ), Expanded( child: CupertinoTheme( data: const CupertinoThemeData( textTheme: CupertinoTextThemeData( dateTimePickerTextStyle: TextStyle( fontSize: 20, color: Color(0xFF374151), ), ), ), child: CupertinoDatePicker( mode: CupertinoDatePickerMode.date, initialDateTime: tempDate, minimumDate: DateTime(1900), maximumDate: DateTime.now(), onDateTimeChanged: (DateTime date) { tempDate = date; }, ), ), ), ], ), ), ); } }