fix(contacts): validate duplicate phone numbers before adding

This commit is contained in:
2026-04-26 05:12:23 +02:00
parent c7fefe2a8b
commit 51901cc639
5 changed files with 37 additions and 8 deletions

View File

@@ -112,7 +112,7 @@ class ContactsScreen extends ConsumerWidget {
return;
}
if (!context.mounted) return;
showDialog<void>(
showLegacyDialog<void>(
context: context,
builder: (_) => Dialog(
backgroundColor: Colors.transparent,
@@ -145,7 +145,7 @@ class ContactsScreen extends ConsumerWidget {
required String contactId,
required int currentCount,
}) async {
final confirmed = await showDialog<bool>(
final confirmed = await showLegacyDialog<bool>(
context: context,
builder: (dialogContext) => AlertDialog(
title: Text(context.translate(I18n.deleteContactMessage)),

View File

@@ -1,5 +1,6 @@
import 'package:design_system/design_system.dart';
import 'package:device_management/src/features/contacts/presentation/providers/contacts_controller.dart';
import 'package:device_management/src/features/contacts/presentation/providers/contacts_provider.dart';
import 'package:device_management/src/features/contacts/presentation/providers/new_contact_form_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -99,6 +100,16 @@ class _NewContactDialogState extends ConsumerState<NewContactDialog> {
return;
}
final existingContacts =
ref.read(contactsProvider(widget.userId)).value ?? [];
final isDuplicate = existingContacts.any(
(c) => c.phone?.e164 == parsed.e164 || c.rawPhone == parsed.e164,
);
if (isDuplicate) {
formNotifier.setLocalError(I18n.errorContactDuplicate);
return;
}
formNotifier.clearError();
Navigator.of(context).pop();
ref.read(contactsControllerProvider.notifier).createContact(

View File

@@ -162,6 +162,16 @@ class _ContactFormSheetWrapperState
return;
}
if (!_isEdit) {
final isDuplicate = widget.currentContacts.any(
(c) => c.phone == parsed.e164,
);
if (isDuplicate) {
formNotifier.setPhoneError(I18n.errorContactDuplicate);
return;
}
}
final contact = ContactListContactEntity(
name: _nameController.text.trim(),
phone: parsed.e164,

View File

@@ -1,3 +1,4 @@
import 'package:legacy_ui/legacy_ui.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -21,8 +22,8 @@ class SosContactsScreen extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final device = ref.watch(selectedDeviceProvider).value;
final primaryColor = context.sfColors.legacyPrimary;
final maxContacts = device?.capabilities?.contacts
?.maxForType('emergency', fallback: 3) ??
final maxContacts =
device?.capabilities?.contacts?.maxForType('emergency', fallback: 3) ??
3;
ref.listen(sosContactsControllerProvider, (prev, next) async {
@@ -128,7 +129,7 @@ class SosContactsScreen extends ConsumerWidget {
),
),
title: Text(
context.translate(I18n.sosContacts).toUpperCase(),
context.translate(I18n.sosContacts),
style: TextStyle(
fontSize: SizeUtils.getByScreen(small: 20, big: 19),
fontWeight: FontWeight.w500,
@@ -163,7 +164,7 @@ class SosContactsScreen extends ConsumerWidget {
Future<bool?> _confirmDelete(BuildContext context, String name) {
final theme = Theme.of(context);
return showDialog<bool>(
return showLegacyDialog<bool>(
context: context,
builder: (dialogContext) => AlertDialog(
icon: Icon(
@@ -268,8 +269,7 @@ class _ContactList extends StatelessWidget {
context.translate(I18n.sosDescription),
style: TextStyle(
fontSize: SizeUtils.getByScreen(small: 14, big: 15),
color:
Theme.of(context).colorScheme.onSurface.withAlpha(178),
color: Theme.of(context).colorScheme.onSurface.withAlpha(178),
),
),
),

View File

@@ -120,6 +120,14 @@ class _AddSosContactSheetState extends ConsumerState<_AddSosContactSheet> {
return;
}
final isDuplicate = widget.currentContacts.any(
(c) => c.phone == parsed.e164,
);
if (isDuplicate) {
formNotifier.setPhoneError(I18n.errorContactDuplicate);
return;
}
await ref.read(sosContactsControllerProvider.notifier).addContact(
deviceId: widget.deviceId,
userId: widget.userId,