feat: Implement change detection and conditional enabling of save/cancel buttons for profile and notification settings.

This commit is contained in:
pradeepkumar
2026-03-20 13:29:28 +05:30
parent f3e48925b2
commit ea7c003e6a
3 changed files with 80 additions and 27 deletions

View File

@@ -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);

View File

@@ -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}) {

View File

@@ -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,