wesal/frontend/lib/screens/edit_profile_screen.dart
2025-08-03 10:06:34 +03:00

428 lines
17 KiB
Dart

import 'package:flutter/material.dart';
import '../services/user_service.dart';
import '../services/app_lifecycle_service.dart';
import 'notification_permission_screen.dart';
class EditProfileScreen extends StatefulWidget {
final Map<String, dynamic>? userData;
final bool isOnboarding;
const EditProfileScreen({
Key? key,
this.userData,
this.isOnboarding = false,
}) : super(key: key);
@override
_EditProfileScreenState createState() => _EditProfileScreenState();
}
class _EditProfileScreenState extends State<EditProfileScreen> {
final _formKey = GlobalKey<FormState>();
final TextEditingController _displayNameController = TextEditingController();
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _emailController = TextEditingController();
bool _isLoading = false;
@override
void initState() {
super.initState();
_initializeFields();
}
void _initializeFields() {
if (widget.userData != null) {
_displayNameController.text = widget.userData!['displayName'] ?? '';
_emailController.text = widget.userData!['email'] ?? '';
if (!widget.isOnboarding) {
_usernameController.text = widget.userData!['username'] ?? '';
}
}
}
@override
void dispose() {
_displayNameController.dispose();
_usernameController.dispose();
_emailController.dispose();
super.dispose();
}
String? _validateDisplayName(String? value) {
if (value == null || value.trim().isEmpty) {
return 'Display name is required';
}
if (value.trim().length < 2) {
return 'Display name must be at least 2 characters';
}
if (value.trim().length > 50) {
return 'Display name cannot exceed 50 characters';
}
return null;
}
String? _validateUsername(String? value) {
if (widget.isOnboarding) {
if (value == null || value.trim().isEmpty) {
return 'Username is required';
}
}
if (value != null && value.trim().isNotEmpty) {
if (value.trim().length < 3) {
return 'Username must be at least 3 characters';
}
if (value.trim().length > 30) {
return 'Username cannot exceed 30 characters';
}
// Basic username validation
if (!RegExp(r'^[a-zA-Z0-9_]+$').hasMatch(value.trim())) {
return 'Username can only contain letters, numbers, and underscores';
}
}
return null;
}
Future<void> _saveProfile() async {
if (!_formKey.currentState!.validate()) {
return;
}
setState(() {
_isLoading = true;
});
try {
final Map<String, dynamic> updateData = {
'displayName': _displayNameController.text.trim(),
};
if (widget.isOnboarding || _usernameController.text.trim().isNotEmpty) {
updateData['username'] = _usernameController.text.trim();
}
final result = await UserService.updateUser(
displayName: updateData['displayName'],
username: updateData['username'],
);
if (mounted) {
setState(() {
_isLoading = false;
});
if (result['success'] == true) {
_showSuccessMessage(result['message'] ?? 'Profile updated successfully');
if (widget.isOnboarding) {
// Start polling services now that user has completed onboarding
AppLifecycleService.startAllPolling();
// Navigate to notification permission screen after successful onboarding
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => NotificationPermissionScreen()),
);
} else {
// Go back to profile page
Navigator.of(context).pop(true); // Return true to indicate update
}
} else {
_showErrorMessage(result['message'] ?? 'Failed to update profile');
}
}
} catch (e) {
if (mounted) {
setState(() {
_isLoading = false;
});
_showErrorMessage('An error occurred. Please try again.');
}
}
}
void _showSuccessMessage(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Color(0xFF6A4C93),
behavior: SnackBarBehavior.floating,
),
);
}
void _showErrorMessage(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
behavior: SnackBarBehavior.floating,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.isOnboarding ? 'Setup Profile' : 'Edit Profile',
style: TextStyle(fontWeight: FontWeight.w600),
),
backgroundColor: Colors.white,
foregroundColor: Color(0xFF6A4C93),
elevation: 0,
bottom: PreferredSize(
preferredSize: Size.fromHeight(1),
child: Container(height: 1, color: Colors.grey[200]),
),
automaticallyImplyLeading: !widget.isOnboarding,
),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFF32B0A5),
Color(0xFF4600B9),
],
),
),
child: SafeArea(
child: Padding(
padding: EdgeInsets.all(24),
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.isOnboarding) ...[
SizedBox(height: 20),
Center(
child: Column(
children: [
Text(
'وصال',
style: TextStyle(
fontFamily: 'Blaka',
fontSize: 48,
fontWeight: FontWeight.w200,
color: Colors.white,
),
),
SizedBox(height: 8),
Text(
'Complete your profile setup',
style: TextStyle(
fontSize: 18,
color: Colors.white.withOpacity(0.9),
),
),
],
),
),
SizedBox(height: 40),
] else ...[
SizedBox(height: 20),
],
Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
spreadRadius: 0,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Profile Information',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Color(0xFF6A4C93),
),
),
SizedBox(height: 24),
// Display Name Field
Text(
'Display Name',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
SizedBox(height: 8),
TextFormField(
controller: _displayNameController,
validator: _validateDisplayName,
decoration: InputDecoration(
hintText: 'Enter your display name',
prefixIcon: Icon(Icons.person, color: Color(0xFF6A4C93)),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey[300]!),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Color(0xFF6A4C93), width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.red, width: 2),
),
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
),
),
SizedBox(height: 20),
// Email Field (Read-only)
Text(
'Email',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
SizedBox(height: 8),
TextFormField(
controller: _emailController,
readOnly: true,
decoration: InputDecoration(
hintText: 'Email address',
prefixIcon: Icon(Icons.email, color: Colors.grey[400]),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey[300]!),
),
disabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey[300]!),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey[300]!),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey[300]!),
),
filled: true,
fillColor: Colors.grey[50],
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
),
style: TextStyle(color: Colors.grey[600]),
),
SizedBox(height: 8),
Text(
'Email cannot be changed',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
SizedBox(height: 20),
// Username Field
Text(
'Username${widget.isOnboarding ? ' *' : ''}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
SizedBox(height: 8),
TextFormField(
controller: _usernameController,
validator: _validateUsername,
decoration: InputDecoration(
hintText: widget.isOnboarding
? 'Choose a username'
: 'Enter your username (optional)',
prefixIcon: Icon(Icons.alternate_email, color: Color(0xFF6A4C93)),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey[300]!),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Color(0xFF6A4C93), width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.red, width: 2),
),
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
),
),
SizedBox(height: 8),
Text(
'Username can contain letters, numbers, and underscores only',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
),
],
),
),
),
),
SizedBox(height: 24),
Container(
width: double.infinity,
child: ElevatedButton(
onPressed: _isLoading ? null : _saveProfile,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Color(0xFF6A4C93),
padding: EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 2,
),
child: _isLoading
? SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF6A4C93)),
),
)
: Text(
widget.isOnboarding ? 'Complete Setup' : 'Save Changes',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
),
),
);
}
}