feat: Implement change detection and conditional enabling of save/cancel buttons for profile and notification settings.
This commit is contained in:
@@ -26,6 +26,17 @@ class _NotificationsTabState extends ConsumerState<NotificationsTab> {
|
|||||||
Map<String, Map<String, bool>> _originalPrefs = {};
|
Map<String, Map<String, bool>> _originalPrefs = {};
|
||||||
String _originalFrequency = 'instant';
|
String _originalFrequency = 'instant';
|
||||||
|
|
||||||
|
bool get _hasNotificationChanges {
|
||||||
|
if (_digestFrequency != _originalFrequency) return true;
|
||||||
|
for (final key in _notificationPrefs.keys) {
|
||||||
|
final current = _notificationPrefs[key];
|
||||||
|
final original = _originalPrefs[key];
|
||||||
|
if (current == null || original == null) return true;
|
||||||
|
if (current['email'] != original['email'] || current['inApp'] != original['inApp']) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -154,6 +165,7 @@ class _NotificationsTabState extends ConsumerState<NotificationsTab> {
|
|||||||
ProfileActionButtons(
|
ProfileActionButtons(
|
||||||
onSave: _saveNotificationSettings,
|
onSave: _saveNotificationSettings,
|
||||||
onCancel: () {
|
onCancel: () {
|
||||||
|
if (!_hasNotificationChanges) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
for (final key in _originalPrefs.keys) {
|
for (final key in _originalPrefs.keys) {
|
||||||
_notificationPrefs[key] = Map<String, bool>.from(_originalPrefs[key]!);
|
_notificationPrefs[key] = Map<String, bool>.from(_originalPrefs[key]!);
|
||||||
@@ -162,6 +174,7 @@ class _NotificationsTabState extends ConsumerState<NotificationsTab> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
isSaving: _isSavingNotifications,
|
isSaving: _isSavingNotifications,
|
||||||
|
hasChanges: _hasNotificationChanges,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -180,6 +193,12 @@ class _NotificationsTabState extends ConsumerState<NotificationsTab> {
|
|||||||
},
|
},
|
||||||
'digestFrequency': _digestFrequency,
|
'digestFrequency': _digestFrequency,
|
||||||
});
|
});
|
||||||
|
// Update saved snapshot so Cancel won't revert after successful save
|
||||||
|
_originalPrefs = {
|
||||||
|
for (final e in _notificationPrefs.entries)
|
||||||
|
e.key: Map<String, bool>.from(e.value),
|
||||||
|
};
|
||||||
|
_originalFrequency = _digestFrequency;
|
||||||
_showSnackBar('Notification settings saved');
|
_showSnackBar('Notification settings saved');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_showSnackBar('Failed to save: $e', isError: true);
|
_showSnackBar('Failed to save: $e', isError: true);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class _ProfileSettingsTabState extends ConsumerState<ProfileSettingsTab> {
|
|||||||
bool _controllersInitialized = false;
|
bool _controllersInitialized = false;
|
||||||
bool _isUploadingAvatar = false;
|
bool _isUploadingAvatar = false;
|
||||||
File? _localAvatarFile;
|
File? _localAvatarFile;
|
||||||
|
Map<String, String> _savedValues = {};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -81,6 +82,21 @@ class _ProfileSettingsTabState extends ConsumerState<ProfileSettingsTab> {
|
|||||||
final parts = [city, stateName, country].where((s) => s.isNotEmpty);
|
final parts = [city, stateName, country].where((s) => s.isNotEmpty);
|
||||||
_locationController.text = parts.join(', ');
|
_locationController.text = parts.join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_savedValues = {
|
||||||
|
'fullName': _fullNameController.text,
|
||||||
|
'title': _titleController.text,
|
||||||
|
'email': _emailController.text,
|
||||||
|
'phone': _phoneController.text,
|
||||||
|
'location': _locationController.text,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get _hasChanges {
|
||||||
|
return _fullNameController.text != (_savedValues['fullName'] ?? '') ||
|
||||||
|
_titleController.text != (_savedValues['title'] ?? '') ||
|
||||||
|
_phoneController.text != (_savedValues['phone'] ?? '') ||
|
||||||
|
_locationController.text != (_savedValues['location'] ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -262,6 +278,7 @@ class _ProfileSettingsTabState extends ConsumerState<ProfileSettingsTab> {
|
|||||||
onSave: _saveProfileSettings,
|
onSave: _saveProfileSettings,
|
||||||
onCancel: _resetForm,
|
onCancel: _resetForm,
|
||||||
isSaving: profileState.isSaving,
|
isSaving: profileState.isSaving,
|
||||||
|
hasChanges: _hasChanges,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -338,16 +355,16 @@ class _ProfileSettingsTabState extends ConsumerState<ProfileSettingsTab> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _resetForm() {
|
void _resetForm() {
|
||||||
_controllersInitialized = false;
|
if (!_hasChanges) return;
|
||||||
_localAvatarFile = null;
|
// Revert to last saved values
|
||||||
final profileState = ref.read(profileProvider);
|
_fullNameController.text = _savedValues['fullName'] ?? '';
|
||||||
if (profileState.profile.isNotEmpty) {
|
_titleController.text = _savedValues['title'] ?? '';
|
||||||
_initControllersFromProfile(profileState);
|
_phoneController.text = _savedValues['phone'] ?? '';
|
||||||
}
|
_locationController.text = _savedValues['location'] ?? '';
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _saveProfileSettings() {
|
Future<void> _saveProfileSettings() async {
|
||||||
final profileState = ref.read(profileProvider);
|
final profileState = ref.read(profileProvider);
|
||||||
final isAgent = profileState.isAgent;
|
final isAgent = profileState.isAgent;
|
||||||
|
|
||||||
@@ -366,7 +383,6 @@ class _ProfileSettingsTabState extends ConsumerState<ProfileSettingsTab> {
|
|||||||
final location = _locationController.text.trim();
|
final location = _locationController.text.trim();
|
||||||
if (location.isNotEmpty) data['serviceAreas'] = [location];
|
if (location.isNotEmpty) data['serviceAreas'] = [location];
|
||||||
} else {
|
} else {
|
||||||
// Note: headline is not a field on UserProfile — don't send it
|
|
||||||
final locationParts = _locationController.text
|
final locationParts = _locationController.text
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((s) => s.trim())
|
.map((s) => s.trim())
|
||||||
@@ -377,7 +393,20 @@ class _ProfileSettingsTabState extends ConsumerState<ProfileSettingsTab> {
|
|||||||
if (locationParts.length > 2) data['country'] = locationParts[2];
|
if (locationParts.length > 2) data['country'] = locationParts[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
ref.read(profileProvider.notifier).updateProfile(data);
|
await ref.read(profileProvider.notifier).updateProfile(data);
|
||||||
|
|
||||||
|
// Update saved values so Cancel won't revert after successful save
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_savedValues = {
|
||||||
|
'fullName': _fullNameController.text,
|
||||||
|
'title': _titleController.text,
|
||||||
|
'email': _emailController.text,
|
||||||
|
'phone': _phoneController.text,
|
||||||
|
'location': _locationController.text,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showSnackBar(String message, {bool isError = false}) {
|
void _showSnackBar(String message, {bool isError = false}) {
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ class ProfileActionButtons extends StatelessWidget {
|
|||||||
final VoidCallback onSave;
|
final VoidCallback onSave;
|
||||||
final VoidCallback onCancel;
|
final VoidCallback onCancel;
|
||||||
final bool isSaving;
|
final bool isSaving;
|
||||||
|
final bool hasChanges;
|
||||||
final String saveLabel;
|
final String saveLabel;
|
||||||
|
|
||||||
const ProfileActionButtons({
|
const ProfileActionButtons({
|
||||||
@@ -94,6 +95,7 @@ class ProfileActionButtons extends StatelessWidget {
|
|||||||
required this.onSave,
|
required this.onSave,
|
||||||
required this.onCancel,
|
required this.onCancel,
|
||||||
this.isSaving = false,
|
this.isSaving = false,
|
||||||
|
this.hasChanges = true,
|
||||||
this.saveLabel = 'Save Changes',
|
this.saveLabel = 'Save Changes',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -114,7 +116,9 @@ class ProfileActionButtons extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: onCancel,
|
onTap: hasChanges ? onCancel : null,
|
||||||
|
child: Opacity(
|
||||||
|
opacity: hasChanges ? 1.0 : 0.5,
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 31,
|
height: 31,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -137,6 +141,7 @@ class ProfileActionButtons extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 3,
|
flex: 3,
|
||||||
|
|||||||
Reference in New Issue
Block a user