feat(block-phone): add edit contact functionality

This commit is contained in:
2026-04-21 17:59:16 +02:00
parent 09897b7f69
commit 982dee6c7a
4 changed files with 164 additions and 0 deletions

View File

@@ -6,11 +6,13 @@ import '../../domain/entities/contact_list_contact_entity.dart';
class ContactListContactCard extends StatelessWidget {
final ContactListContactEntity contact;
final VoidCallback? onEdit;
final VoidCallback onDelete;
const ContactListContactCard({
super.key,
required this.contact,
this.onEdit,
required this.onDelete,
});
@@ -61,6 +63,21 @@ class ContactListContactCard extends StatelessWidget {
],
),
),
if (onEdit != null)
InkWell(
onTap: onEdit,
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: EdgeInsets.all(
SizeUtils.getByScreen(small: 6, big: 8),
),
child: Icon(
Icons.edit_outlined,
color: context.sfColors.legacyPrimary,
size: SizeUtils.getByScreen(small: 22, big: 24),
),
),
),
InkWell(
onTap: onDelete,
borderRadius: BorderRadius.circular(8),

View File

@@ -192,6 +192,11 @@ class _ContactList extends ConsumerWidget {
final contact = contacts[index];
return ContactListContactCard(
contact: contact,
onEdit: () => showEditContactSheet(
context,
index: index,
contact: contact,
),
onDelete: () =>
_confirmDelete(context, ref, index, contact.name),
);

View File

@@ -80,6 +80,37 @@ class BlockPhoneViewModel extends Notifier<BlockPhoneViewState> {
}
}
Future<void> updateContact(int index, ContactListContactEntity contact) async {
state = state.copyWith(isSaving: true, errorMessage: '');
try {
final device = ref.read(selectedDeviceProvider).value;
if (device == null) return;
final updatedContacts = [...state.contacts];
updatedContacts[index] = contact;
await _repository.upsertWhitelist(
userId: device.userId ?? '',
deviceId: device.id,
contacts: updatedContacts,
);
if (!ref.mounted) return;
state = state.copyWith(
contacts: updatedContacts,
isSaving: false,
successMessage: I18n.numberUpdated,
);
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(
isSaving: false,
errorMessage: formatErrorMessage(e),
);
}
}
Future<void> removeContact(int index) async {
state = state.copyWith(isSaving: true, errorMessage: '');

View File

@@ -3,8 +3,11 @@ import 'package:legacy_theme/legacy_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:sf_shared/sf_shared.dart';
import 'package:settings/src/core/domain/entities/contact_list_contact_entity.dart';
import 'package:settings/src/core/presentation/widgets/contact_form_sheet.dart';
import 'package:settings/src/features/block_phone/presentation/state/block_phone_view_model.dart';
import 'package:settings/src/features/block_phone/presentation/state/new_block_phone_contact_view_model.dart';
void showAddContactSheet(BuildContext context) {
@@ -16,6 +19,19 @@ void showAddContactSheet(BuildContext context) {
);
}
void showEditContactSheet(
BuildContext context, {
required int index,
required ContactListContactEntity contact,
}) {
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (_) => _EditContactSheet(index: index, contact: contact),
);
}
class _AddContactSheet extends ConsumerWidget {
const _AddContactSheet();
@@ -55,3 +71,98 @@ class _AddContactSheet extends ConsumerWidget {
);
}
}
class _EditContactSheet extends ConsumerStatefulWidget {
final int index;
final ContactListContactEntity contact;
const _EditContactSheet({required this.index, required this.contact});
@override
ConsumerState<_EditContactSheet> createState() => _EditContactSheetState();
}
class _EditContactSheetState extends ConsumerState<_EditContactSheet> {
late final TextEditingController _nameController;
late final TextEditingController _phoneController;
String _isoCode = SfPhoneNumber.defaultIsoCode;
bool _canSave = true;
bool _isSubmitting = false;
String? _phoneError;
@override
void initState() {
super.initState();
_nameController = TextEditingController(text: widget.contact.name);
final parsed = SfPhoneNumber.tryParse(widget.contact.phone);
_phoneController = TextEditingController(
text: parsed?.nationalNumber ?? widget.contact.phone,
);
if (parsed != null) _isoCode = parsed.isoCode;
_nameController.addListener(_refreshCanSave);
_phoneController.addListener(_refreshCanSave);
}
@override
void dispose() {
_nameController.dispose();
_phoneController.dispose();
super.dispose();
}
void _refreshCanSave() {
final canSave =
_nameController.text.trim().isNotEmpty &&
_phoneController.text.trim().isNotEmpty;
if (canSave != _canSave) setState(() => _canSave = canSave);
if (_phoneError != null) setState(() => _phoneError = null);
}
Future<void> _submit() async {
if (_isSubmitting || !_canSave) return;
final parsed = SfPhoneNumber.tryParse(
_phoneController.text,
defaultIsoCode: _isoCode,
);
if (parsed == null) {
setState(() => _phoneError = I18n.errorMessagePhoneIsInvalid);
return;
}
setState(() => _isSubmitting = true);
await ref
.read(blockPhoneViewModelProvider.notifier)
.updateContact(
widget.index,
ContactListContactEntity(
name: _nameController.text.trim(),
phone: parsed.e164,
),
);
if (!mounted) return;
setState(() => _isSubmitting = false);
Navigator.pop(context);
}
@override
Widget build(BuildContext context) {
return ContactFormSheet(
title: context.translate(I18n.editAllowedNumber),
primaryColor: context.sfColors.legacyPrimary,
nameController: _nameController,
phoneController: _phoneController,
isoCode: _isoCode,
canSave: _canSave,
isSubmitting: _isSubmitting,
phoneError: _phoneError,
onCountryChanged: (isoCode) => setState(() => _isoCode = isoCode),
onPickContact: () async {},
onSubmit: _submit,
);
}
}