refactor(settings): migrate block_phone to Riverpod CRUD pattern
This commit is contained in:
@@ -1,16 +1,17 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:legacy_theme/legacy_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:legacy_device_state/legacy_device_state.dart';
|
||||
import 'package:legacy_theme/legacy_theme.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
import 'package:settings/src/core/domain/entities/contact_list_contact_entity.dart';
|
||||
import 'package:settings/src/core/presentation/widgets/contact_list_contact_card.dart';
|
||||
import 'package:settings/src/features/block_phone/presentation/providers/block_phone_contacts_provider.dart';
|
||||
import 'package:settings/src/features/block_phone/presentation/providers/block_phone_controller.dart';
|
||||
import 'package:settings/src/features/block_phone/presentation/widgets/add_contact_sheet.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
import 'package:utils/utils.dart';
|
||||
|
||||
import 'state/block_phone_view_model.dart';
|
||||
import '../../../core/presentation/widgets/contact_list_contact_card.dart';
|
||||
import 'widgets/add_contact_sheet.dart';
|
||||
|
||||
class BlockPhoneScreen extends ConsumerWidget {
|
||||
final NavigationContract navigationContract;
|
||||
|
||||
@@ -18,62 +19,133 @@ class BlockPhoneScreen extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final state = ref.watch(blockPhoneViewModelProvider);
|
||||
final device = ref.watch(selectedDeviceProvider).value;
|
||||
final primaryColor = context.sfColors.legacyPrimary;
|
||||
|
||||
ref.listen(blockPhoneViewModelProvider.select((s) => s.errorMessage), (
|
||||
_,
|
||||
errorMessage,
|
||||
) {
|
||||
if (errorMessage.isNotEmpty) {
|
||||
showTopSnackbar(
|
||||
context,
|
||||
message: errorMessage,
|
||||
type: MessageType.error,
|
||||
);
|
||||
ref.listen(blockPhoneControllerProvider, (prev, next) async {
|
||||
if (prev == null || !prev.isLoading || next.isLoading) return;
|
||||
if (next.hasError) {
|
||||
final error = next.error;
|
||||
if (error is BlockPhoneMinimumOneContactException) {
|
||||
await showErrorDialog(context, I18n.whitelistMinimumOneContact);
|
||||
} else {
|
||||
await next.showErrorOn(context);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ref.listen(blockPhoneViewModelProvider.select((s) => s.successMessage), (
|
||||
_,
|
||||
successMessage,
|
||||
) {
|
||||
if (successMessage.isNotEmpty) {
|
||||
showTopSnackbar(
|
||||
context,
|
||||
message: context.translate(successMessage),
|
||||
type: MessageType.success,
|
||||
);
|
||||
ref.read(blockPhoneViewModelProvider.notifier).clearSuccess();
|
||||
}
|
||||
});
|
||||
if (device == null) {
|
||||
return Scaffold(
|
||||
appBar: _appBar(context, navigationContract, primaryColor),
|
||||
body: const SizedBox.shrink(),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
final contactsAsync = ref.watch(blockPhoneContactsProvider(device.id));
|
||||
|
||||
return contactsAsync.when(
|
||||
loading: () => Scaffold(
|
||||
appBar: _appBar(context, navigationContract, primaryColor),
|
||||
body: const Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
error: (_, __) => Scaffold(
|
||||
appBar: _appBar(context, navigationContract, primaryColor),
|
||||
body: Center(child: Text(context.translate(I18n.errorGeneric))),
|
||||
),
|
||||
data: (contacts) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
appBar: _appBar(
|
||||
context,
|
||||
navigationContract,
|
||||
primaryColor,
|
||||
onAdd: () async {
|
||||
if (!await guardDeviceCommand(context, ref)) return;
|
||||
if (!context.mounted) return;
|
||||
showAddContactSheet(
|
||||
context,
|
||||
deviceId: device.id,
|
||||
userId: device.userId ?? '',
|
||||
currentContacts: contacts,
|
||||
);
|
||||
},
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: contacts.isEmpty
|
||||
? _EmptyState(primaryColor: primaryColor)
|
||||
: _ContactList(
|
||||
contacts: contacts,
|
||||
primaryColor: primaryColor,
|
||||
onEdit: (index, contact) async {
|
||||
if (!await guardDeviceCommand(context, ref)) return;
|
||||
if (!context.mounted) return;
|
||||
showEditContactSheet(
|
||||
context,
|
||||
deviceId: device.id,
|
||||
userId: device.userId ?? '',
|
||||
currentContacts: contacts,
|
||||
index: index,
|
||||
contact: contact,
|
||||
);
|
||||
},
|
||||
onDelete: (index, name) async {
|
||||
if (!await guardDeviceCommand(context, ref)) return;
|
||||
if (!context.mounted) return;
|
||||
final confirmed = await _confirmDelete(context, name);
|
||||
if (confirmed != true) return;
|
||||
if (!context.mounted) return;
|
||||
await ref
|
||||
.read(blockPhoneControllerProvider.notifier)
|
||||
.removeContact(
|
||||
deviceId: device.id,
|
||||
userId: device.userId ?? '',
|
||||
current: contacts,
|
||||
index: index,
|
||||
);
|
||||
if (!context.mounted) return;
|
||||
final state = ref.read(blockPhoneControllerProvider);
|
||||
if (state.hasError) return;
|
||||
await showSuccessDialog(context, I18n.numberRemoved);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
PreferredSizeWidget _appBar(
|
||||
BuildContext context,
|
||||
NavigationContract nav,
|
||||
Color primaryColor, {
|
||||
VoidCallback? onAdd,
|
||||
}) {
|
||||
return AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
automaticallyImplyLeading: false,
|
||||
leading: IconButton(
|
||||
onPressed: () => navigationContract.goBack(),
|
||||
icon: Icon(
|
||||
Icons.adaptive.arrow_back,
|
||||
color: primaryColor,
|
||||
size: SizeUtils.getByScreen(small: 32, big: 28),
|
||||
),
|
||||
surfaceTintColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
automaticallyImplyLeading: false,
|
||||
leading: IconButton(
|
||||
onPressed: () => nav.goBack(),
|
||||
icon: Icon(
|
||||
Icons.adaptive.arrow_back,
|
||||
color: primaryColor,
|
||||
size: SizeUtils.getByScreen(small: 32, big: 28),
|
||||
),
|
||||
title: Text(
|
||||
context.translate(I18n.blockPhone).toUpperCase(),
|
||||
style: TextStyle(
|
||||
fontSize: SizeUtils.getByScreen(small: 20, big: 19),
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
context.translate(I18n.blockPhone).toUpperCase(),
|
||||
style: TextStyle(
|
||||
fontSize: SizeUtils.getByScreen(small: 20, big: 19),
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0,
|
||||
color: primaryColor,
|
||||
),
|
||||
actions: [
|
||||
),
|
||||
actions: [
|
||||
if (onAdd != null)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
right: SizeUtils.getByScreen(small: 16, big: 14),
|
||||
@@ -84,11 +156,7 @@ class BlockPhoneScreen extends ConsumerWidget {
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: IconButton(
|
||||
onPressed: () async {
|
||||
if (!await guardDeviceCommand(context, ref)) return;
|
||||
if (!context.mounted) return;
|
||||
showAddContactSheet(context);
|
||||
},
|
||||
onPressed: onAdd,
|
||||
icon: Icon(
|
||||
Icons.add,
|
||||
color: Colors.white,
|
||||
@@ -97,16 +165,41 @@ class BlockPhoneScreen extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool?> _confirmDelete(BuildContext context, String name) {
|
||||
final theme = Theme.of(context);
|
||||
return showDialog<bool>(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
icon: Icon(
|
||||
Icons.warning_amber_rounded,
|
||||
color: theme.colorScheme.error,
|
||||
size: 40,
|
||||
),
|
||||
title: Text(context.translate(I18n.removeAllowedNumber)),
|
||||
content: Text(
|
||||
context.translate(
|
||||
I18n.removeAllowedNumberConfirm,
|
||||
args: {'name': name},
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(dialogContext).pop(false),
|
||||
child: Text(context.translate(I18n.cancel)),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(dialogContext).pop(true),
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.error,
|
||||
),
|
||||
child: Text(context.translate(I18n.delete)),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: state.isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: state.contacts.isEmpty
|
||||
? _EmptyState(primaryColor: primaryColor)
|
||||
: _ContactList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -156,21 +249,26 @@ class _EmptyState extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _ContactList extends ConsumerWidget {
|
||||
const _ContactList();
|
||||
class _ContactList extends StatelessWidget {
|
||||
final List<ContactListContactEntity> contacts;
|
||||
final Color primaryColor;
|
||||
final void Function(int index, ContactListContactEntity contact) onEdit;
|
||||
final void Function(int index, String name) onDelete;
|
||||
|
||||
const _ContactList({
|
||||
required this.contacts,
|
||||
required this.primaryColor,
|
||||
required this.onEdit,
|
||||
required this.onDelete,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final contacts = ref.watch(
|
||||
blockPhoneViewModelProvider.select((s) => s.contacts),
|
||||
);
|
||||
final primaryColor = context.sfColors.legacyPrimary;
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: SizeUtils.getByScreen(
|
||||
small: EdgeInsets.symmetric(horizontal: 22, vertical: 10),
|
||||
big: EdgeInsets.symmetric(horizontal: 21, vertical: 8),
|
||||
small: const EdgeInsets.symmetric(horizontal: 22, vertical: 10),
|
||||
big: const EdgeInsets.symmetric(horizontal: 21, vertical: 8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -183,8 +281,8 @@ class _ContactList extends ConsumerWidget {
|
||||
context.translate(I18n.whitelistDescription),
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -192,13 +290,8 @@ 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),
|
||||
onEdit: () => onEdit(index, contact),
|
||||
onDelete: () => onDelete(index, contact.name),
|
||||
);
|
||||
}),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 12, big: 10)),
|
||||
@@ -218,49 +311,4 @@ class _ContactList extends ConsumerWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _confirmDelete(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
int index,
|
||||
String name,
|
||||
) async {
|
||||
if (!await guardDeviceCommand(context, ref)) return;
|
||||
if (!context.mounted) return;
|
||||
final primaryColor = context.sfColors.legacyPrimary;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(context.translate(I18n.removeAllowedNumber)),
|
||||
content: Text(
|
||||
context.translate(
|
||||
I18n.removeAllowedNumberConfirm,
|
||||
args: {'name': name},
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(
|
||||
context.translate(I18n.cancel),
|
||||
style: TextStyle(color: primaryColor),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(blockPhoneViewModelProvider.notifier)
|
||||
.removeContact(index);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
context.translate(I18n.delete),
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:settings/src/core/domain/entities/contact_list_contact_entity.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
|
||||
part 'block_phone_contact_form_provider.g.dart';
|
||||
|
||||
class BlockPhoneContactFormState {
|
||||
const BlockPhoneContactFormState({
|
||||
required this.isoCode,
|
||||
this.canSave = false,
|
||||
this.permissionBlocked = false,
|
||||
this.phoneError,
|
||||
});
|
||||
|
||||
final String isoCode;
|
||||
final bool canSave;
|
||||
final bool permissionBlocked;
|
||||
final String? phoneError;
|
||||
|
||||
BlockPhoneContactFormState copyWith({
|
||||
String? isoCode,
|
||||
bool? canSave,
|
||||
bool? permissionBlocked,
|
||||
String? phoneError,
|
||||
bool clearPhoneError = false,
|
||||
}) {
|
||||
return BlockPhoneContactFormState(
|
||||
isoCode: isoCode ?? this.isoCode,
|
||||
canSave: canSave ?? this.canSave,
|
||||
permissionBlocked: permissionBlocked ?? this.permissionBlocked,
|
||||
phoneError: clearPhoneError ? null : (phoneError ?? this.phoneError),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class BlockPhoneContactForm extends _$BlockPhoneContactForm {
|
||||
@override
|
||||
BlockPhoneContactFormState build(ContactListContactEntity? initial) {
|
||||
if (initial == null) {
|
||||
return const BlockPhoneContactFormState(isoCode: 'ES');
|
||||
}
|
||||
final parsed = SfPhoneNumber.tryParse(initial.phone);
|
||||
return BlockPhoneContactFormState(
|
||||
isoCode: parsed?.isoCode ?? SfPhoneNumber.defaultIsoCode,
|
||||
canSave: true,
|
||||
);
|
||||
}
|
||||
|
||||
void setCanSave(bool value) {
|
||||
if (value == state.canSave) return;
|
||||
state = state.copyWith(canSave: value);
|
||||
}
|
||||
|
||||
void setIsoCode(String code) {
|
||||
if (code == state.isoCode) return;
|
||||
state = state.copyWith(isoCode: code, clearPhoneError: true);
|
||||
}
|
||||
|
||||
void setPhoneError(String? error) {
|
||||
state = state.copyWith(phoneError: error);
|
||||
}
|
||||
|
||||
void clearPhoneError() {
|
||||
if (state.phoneError == null) return;
|
||||
state = state.copyWith(clearPhoneError: true);
|
||||
}
|
||||
|
||||
void setPermissionBlocked(bool value) {
|
||||
state = state.copyWith(permissionBlocked: value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'block_phone_contact_form_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(BlockPhoneContactForm)
|
||||
const blockPhoneContactFormProvider = BlockPhoneContactFormFamily._();
|
||||
|
||||
final class BlockPhoneContactFormProvider
|
||||
extends
|
||||
$NotifierProvider<BlockPhoneContactForm, BlockPhoneContactFormState> {
|
||||
const BlockPhoneContactFormProvider._({
|
||||
required BlockPhoneContactFormFamily super.from,
|
||||
required ContactListContactEntity? super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'blockPhoneContactFormProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$blockPhoneContactFormHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'blockPhoneContactFormProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
BlockPhoneContactForm create() => BlockPhoneContactForm();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(BlockPhoneContactFormState value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<BlockPhoneContactFormState>(value),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is BlockPhoneContactFormProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$blockPhoneContactFormHash() =>
|
||||
r'6282fe5b19436963fa1e8febe8d05daf68ecee40';
|
||||
|
||||
final class BlockPhoneContactFormFamily extends $Family
|
||||
with
|
||||
$ClassFamilyOverride<
|
||||
BlockPhoneContactForm,
|
||||
BlockPhoneContactFormState,
|
||||
BlockPhoneContactFormState,
|
||||
BlockPhoneContactFormState,
|
||||
ContactListContactEntity?
|
||||
> {
|
||||
const BlockPhoneContactFormFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'blockPhoneContactFormProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
BlockPhoneContactFormProvider call(ContactListContactEntity? initial) =>
|
||||
BlockPhoneContactFormProvider._(argument: initial, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'blockPhoneContactFormProvider';
|
||||
}
|
||||
|
||||
abstract class _$BlockPhoneContactForm
|
||||
extends $Notifier<BlockPhoneContactFormState> {
|
||||
late final _$args = ref.$arg as ContactListContactEntity?;
|
||||
ContactListContactEntity? get initial => _$args;
|
||||
|
||||
BlockPhoneContactFormState build(ContactListContactEntity? initial);
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(_$args);
|
||||
final ref =
|
||||
this.ref
|
||||
as $Ref<BlockPhoneContactFormState, BlockPhoneContactFormState>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<
|
||||
BlockPhoneContactFormState,
|
||||
BlockPhoneContactFormState
|
||||
>,
|
||||
BlockPhoneContactFormState,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:settings/src/core/domain/entities/contact_list_contact_entity.dart';
|
||||
import 'package:settings/src/core/providers/block_phone_repository_provider.dart';
|
||||
|
||||
part 'block_phone_contacts_provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<List<ContactListContactEntity>> blockPhoneContacts(
|
||||
Ref ref,
|
||||
String deviceId,
|
||||
) async {
|
||||
return ref.watch(blockPhoneRepositoryProvider).getWhitelist(deviceId: deviceId);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'block_phone_contacts_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(blockPhoneContacts)
|
||||
const blockPhoneContactsProvider = BlockPhoneContactsFamily._();
|
||||
|
||||
final class BlockPhoneContactsProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<List<ContactListContactEntity>>,
|
||||
List<ContactListContactEntity>,
|
||||
FutureOr<List<ContactListContactEntity>>
|
||||
>
|
||||
with
|
||||
$FutureModifier<List<ContactListContactEntity>>,
|
||||
$FutureProvider<List<ContactListContactEntity>> {
|
||||
const BlockPhoneContactsProvider._({
|
||||
required BlockPhoneContactsFamily super.from,
|
||||
required String super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'blockPhoneContactsProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$blockPhoneContactsHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'blockPhoneContactsProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<List<ContactListContactEntity>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<List<ContactListContactEntity>> create(Ref ref) {
|
||||
final argument = this.argument as String;
|
||||
return blockPhoneContacts(ref, argument);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is BlockPhoneContactsProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$blockPhoneContactsHash() =>
|
||||
r'936920ff6848e41a463c91821af2618f40f0f68e';
|
||||
|
||||
final class BlockPhoneContactsFamily extends $Family
|
||||
with
|
||||
$FunctionalFamilyOverride<
|
||||
FutureOr<List<ContactListContactEntity>>,
|
||||
String
|
||||
> {
|
||||
const BlockPhoneContactsFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'blockPhoneContactsProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
BlockPhoneContactsProvider call(String deviceId) =>
|
||||
BlockPhoneContactsProvider._(argument: deviceId, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'blockPhoneContactsProvider';
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:settings/src/core/domain/entities/contact_list_contact_entity.dart';
|
||||
import 'package:settings/src/core/providers/block_phone_repository_provider.dart';
|
||||
import 'package:settings/src/features/block_phone/presentation/providers/block_phone_contacts_provider.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
|
||||
part 'block_phone_controller.g.dart';
|
||||
|
||||
class BlockPhoneMinimumOneContactException implements Exception {
|
||||
const BlockPhoneMinimumOneContactException();
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class BlockPhoneController extends _$BlockPhoneController {
|
||||
@override
|
||||
FutureOr<void> build() {}
|
||||
|
||||
Future<void> addContact({
|
||||
required String deviceId,
|
||||
required String userId,
|
||||
required List<ContactListContactEntity> current,
|
||||
required ContactListContactEntity contact,
|
||||
}) async {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
final updated = [...current, contact];
|
||||
await ref.read(blockPhoneRepositoryProvider).upsertWhitelist(
|
||||
userId: userId,
|
||||
deviceId: deviceId,
|
||||
contacts: updated,
|
||||
);
|
||||
ref.invalidate(blockPhoneContactsProvider(deviceId));
|
||||
unawaited(
|
||||
ref
|
||||
.read(sfTrackingProvider)
|
||||
.legacySettingsBlockPhoneContactAdded(totalCount: updated.length),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> updateContact({
|
||||
required String deviceId,
|
||||
required String userId,
|
||||
required List<ContactListContactEntity> current,
|
||||
required int index,
|
||||
required ContactListContactEntity contact,
|
||||
}) async {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
final updated = [...current];
|
||||
updated[index] = contact;
|
||||
await ref.read(blockPhoneRepositoryProvider).upsertWhitelist(
|
||||
userId: userId,
|
||||
deviceId: deviceId,
|
||||
contacts: updated,
|
||||
);
|
||||
ref.invalidate(blockPhoneContactsProvider(deviceId));
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> removeContact({
|
||||
required String deviceId,
|
||||
required String userId,
|
||||
required List<ContactListContactEntity> current,
|
||||
required int index,
|
||||
}) async {
|
||||
state = const AsyncLoading();
|
||||
if (current.length <= 1) {
|
||||
state = AsyncError(
|
||||
const BlockPhoneMinimumOneContactException(),
|
||||
StackTrace.current,
|
||||
);
|
||||
return;
|
||||
}
|
||||
state = await AsyncValue.guard(() async {
|
||||
final updated = [...current]..removeAt(index);
|
||||
await ref.read(blockPhoneRepositoryProvider).upsertWhitelist(
|
||||
userId: userId,
|
||||
deviceId: deviceId,
|
||||
contacts: updated,
|
||||
);
|
||||
ref.invalidate(blockPhoneContactsProvider(deviceId));
|
||||
unawaited(
|
||||
ref
|
||||
.read(sfTrackingProvider)
|
||||
.legacySettingsBlockPhoneContactRemoved(totalCount: updated.length),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'block_phone_controller.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(BlockPhoneController)
|
||||
const blockPhoneControllerProvider = BlockPhoneControllerProvider._();
|
||||
|
||||
final class BlockPhoneControllerProvider
|
||||
extends $AsyncNotifierProvider<BlockPhoneController, void> {
|
||||
const BlockPhoneControllerProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'blockPhoneControllerProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$blockPhoneControllerHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
BlockPhoneController create() => BlockPhoneController();
|
||||
}
|
||||
|
||||
String _$blockPhoneControllerHash() =>
|
||||
r'9992feb7188fa92f3b6fdb569bef89e750a81b0e';
|
||||
|
||||
abstract class _$BlockPhoneController extends $AsyncNotifier<void> {
|
||||
FutureOr<void> build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
build();
|
||||
final ref = this.ref as $Ref<AsyncValue<void>, void>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<AsyncValue<void>, void>,
|
||||
AsyncValue<void>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, null);
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.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/domain/repositories/block_phone_repository.dart';
|
||||
import 'package:settings/src/core/providers/block_phone_repository_provider.dart';
|
||||
import 'package:settings/src/features/block_phone/presentation/state/block_phone_view_state.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
|
||||
final blockPhoneViewModelProvider =
|
||||
NotifierProvider.autoDispose<BlockPhoneViewModel, BlockPhoneViewState>(
|
||||
BlockPhoneViewModel.new,
|
||||
);
|
||||
|
||||
class BlockPhoneViewModel extends Notifier<BlockPhoneViewState> {
|
||||
late final BlockPhoneRepository _repository;
|
||||
late final SfTrackingRepository _tracking;
|
||||
|
||||
@override
|
||||
BlockPhoneViewState build() {
|
||||
_repository = ref.read(blockPhoneRepositoryProvider);
|
||||
_tracking = ref.read(sfTrackingProvider);
|
||||
Future.microtask(_load);
|
||||
return const BlockPhoneViewState();
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
try {
|
||||
final device = ref.read(selectedDeviceProvider).value;
|
||||
if (device == null) return;
|
||||
|
||||
final contacts = await _repository.getWhitelist(deviceId: device.id);
|
||||
if (!ref.mounted) return;
|
||||
state = state.copyWith(contacts: contacts, isLoading: false);
|
||||
} catch (e) {
|
||||
if (!ref.mounted) return;
|
||||
state = state.copyWith(
|
||||
isLoading: false,
|
||||
errorMessage: formatErrorMessage(e),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addContact(ContactListContactEntity contact) async {
|
||||
state = state.copyWith(isSaving: true, errorMessage: '');
|
||||
|
||||
try {
|
||||
final device = ref.read(selectedDeviceProvider).value;
|
||||
if (device == null) return;
|
||||
|
||||
final updatedContacts = [...state.contacts, contact];
|
||||
|
||||
await _repository.upsertWhitelist(
|
||||
userId: device.userId ?? '',
|
||||
deviceId: device.id,
|
||||
contacts: updatedContacts,
|
||||
);
|
||||
if (!ref.mounted) return;
|
||||
|
||||
unawaited(
|
||||
_tracking.legacySettingsBlockPhoneContactAdded(
|
||||
totalCount: updatedContacts.length,
|
||||
),
|
||||
);
|
||||
|
||||
state = state.copyWith(
|
||||
contacts: updatedContacts,
|
||||
isSaving: false,
|
||||
successMessage: I18n.numberAdded,
|
||||
);
|
||||
} catch (e) {
|
||||
if (!ref.mounted) return;
|
||||
state = state.copyWith(
|
||||
isSaving: false,
|
||||
errorMessage: formatErrorMessage(e),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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: '');
|
||||
|
||||
try {
|
||||
final device = ref.read(selectedDeviceProvider).value;
|
||||
if (device == null) return;
|
||||
|
||||
final updatedContacts = [...state.contacts]..removeAt(index);
|
||||
|
||||
await _repository.upsertWhitelist(
|
||||
userId: device.userId ?? '',
|
||||
deviceId: device.id,
|
||||
contacts: updatedContacts,
|
||||
);
|
||||
if (!ref.mounted) return;
|
||||
|
||||
unawaited(
|
||||
_tracking.legacySettingsBlockPhoneContactRemoved(
|
||||
totalCount: updatedContacts.length,
|
||||
),
|
||||
);
|
||||
|
||||
state = state.copyWith(
|
||||
contacts: updatedContacts,
|
||||
isSaving: false,
|
||||
successMessage: I18n.numberRemoved,
|
||||
);
|
||||
} catch (e) {
|
||||
if (!ref.mounted) return;
|
||||
state = state.copyWith(
|
||||
isSaving: false,
|
||||
errorMessage: formatErrorMessage(e),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void clearSuccess() {
|
||||
state = state.copyWith(successMessage: '');
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:settings/src/core/domain/entities/contact_list_contact_entity.dart';
|
||||
|
||||
part 'block_phone_view_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class BlockPhoneViewState with _$BlockPhoneViewState {
|
||||
const factory BlockPhoneViewState({
|
||||
@Default([]) List<ContactListContactEntity> contacts,
|
||||
@Default(true) bool isLoading,
|
||||
@Default(false) bool isSaving,
|
||||
@Default('') String successMessage,
|
||||
@Default('') String errorMessage,
|
||||
}) = _BlockPhoneViewState;
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'block_phone_view_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$BlockPhoneViewState {
|
||||
|
||||
List<ContactListContactEntity> get contacts; bool get isLoading; bool get isSaving; String get successMessage; String get errorMessage;
|
||||
/// Create a copy of BlockPhoneViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$BlockPhoneViewStateCopyWith<BlockPhoneViewState> get copyWith => _$BlockPhoneViewStateCopyWithImpl<BlockPhoneViewState>(this as BlockPhoneViewState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is BlockPhoneViewState&&const DeepCollectionEquality().equals(other.contacts, contacts)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isSaving, isSaving) || other.isSaving == isSaving)&&(identical(other.successMessage, successMessage) || other.successMessage == successMessage)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(contacts),isLoading,isSaving,successMessage,errorMessage);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BlockPhoneViewState(contacts: $contacts, isLoading: $isLoading, isSaving: $isSaving, successMessage: $successMessage, errorMessage: $errorMessage)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $BlockPhoneViewStateCopyWith<$Res> {
|
||||
factory $BlockPhoneViewStateCopyWith(BlockPhoneViewState value, $Res Function(BlockPhoneViewState) _then) = _$BlockPhoneViewStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
List<ContactListContactEntity> contacts, bool isLoading, bool isSaving, String successMessage, String errorMessage
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$BlockPhoneViewStateCopyWithImpl<$Res>
|
||||
implements $BlockPhoneViewStateCopyWith<$Res> {
|
||||
_$BlockPhoneViewStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final BlockPhoneViewState _self;
|
||||
final $Res Function(BlockPhoneViewState) _then;
|
||||
|
||||
/// Create a copy of BlockPhoneViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? contacts = null,Object? isLoading = null,Object? isSaving = null,Object? successMessage = null,Object? errorMessage = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
contacts: null == contacts ? _self.contacts : contacts // ignore: cast_nullable_to_non_nullable
|
||||
as List<ContactListContactEntity>,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isSaving: null == isSaving ? _self.isSaving : isSaving // ignore: cast_nullable_to_non_nullable
|
||||
as bool,successMessage: null == successMessage ? _self.successMessage : successMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [BlockPhoneViewState].
|
||||
extension BlockPhoneViewStatePatterns on BlockPhoneViewState {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _BlockPhoneViewState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _BlockPhoneViewState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _BlockPhoneViewState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _BlockPhoneViewState():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _BlockPhoneViewState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _BlockPhoneViewState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<ContactListContactEntity> contacts, bool isLoading, bool isSaving, String successMessage, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _BlockPhoneViewState() when $default != null:
|
||||
return $default(_that.contacts,_that.isLoading,_that.isSaving,_that.successMessage,_that.errorMessage);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<ContactListContactEntity> contacts, bool isLoading, bool isSaving, String successMessage, String errorMessage) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _BlockPhoneViewState():
|
||||
return $default(_that.contacts,_that.isLoading,_that.isSaving,_that.successMessage,_that.errorMessage);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<ContactListContactEntity> contacts, bool isLoading, bool isSaving, String successMessage, String errorMessage)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _BlockPhoneViewState() when $default != null:
|
||||
return $default(_that.contacts,_that.isLoading,_that.isSaving,_that.successMessage,_that.errorMessage);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _BlockPhoneViewState implements BlockPhoneViewState {
|
||||
const _BlockPhoneViewState({final List<ContactListContactEntity> contacts = const [], this.isLoading = true, this.isSaving = false, this.successMessage = '', this.errorMessage = ''}): _contacts = contacts;
|
||||
|
||||
|
||||
final List<ContactListContactEntity> _contacts;
|
||||
@override@JsonKey() List<ContactListContactEntity> get contacts {
|
||||
if (_contacts is EqualUnmodifiableListView) return _contacts;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_contacts);
|
||||
}
|
||||
|
||||
@override@JsonKey() final bool isLoading;
|
||||
@override@JsonKey() final bool isSaving;
|
||||
@override@JsonKey() final String successMessage;
|
||||
@override@JsonKey() final String errorMessage;
|
||||
|
||||
/// Create a copy of BlockPhoneViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$BlockPhoneViewStateCopyWith<_BlockPhoneViewState> get copyWith => __$BlockPhoneViewStateCopyWithImpl<_BlockPhoneViewState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _BlockPhoneViewState&&const DeepCollectionEquality().equals(other._contacts, _contacts)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isSaving, isSaving) || other.isSaving == isSaving)&&(identical(other.successMessage, successMessage) || other.successMessage == successMessage)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_contacts),isLoading,isSaving,successMessage,errorMessage);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BlockPhoneViewState(contacts: $contacts, isLoading: $isLoading, isSaving: $isSaving, successMessage: $successMessage, errorMessage: $errorMessage)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$BlockPhoneViewStateCopyWith<$Res> implements $BlockPhoneViewStateCopyWith<$Res> {
|
||||
factory _$BlockPhoneViewStateCopyWith(_BlockPhoneViewState value, $Res Function(_BlockPhoneViewState) _then) = __$BlockPhoneViewStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
List<ContactListContactEntity> contacts, bool isLoading, bool isSaving, String successMessage, String errorMessage
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$BlockPhoneViewStateCopyWithImpl<$Res>
|
||||
implements _$BlockPhoneViewStateCopyWith<$Res> {
|
||||
__$BlockPhoneViewStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _BlockPhoneViewState _self;
|
||||
final $Res Function(_BlockPhoneViewState) _then;
|
||||
|
||||
/// Create a copy of BlockPhoneViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? contacts = null,Object? isLoading = null,Object? isSaving = null,Object? successMessage = null,Object? errorMessage = null,}) {
|
||||
return _then(_BlockPhoneViewState(
|
||||
contacts: null == contacts ? _self._contacts : contacts // ignore: cast_nullable_to_non_nullable
|
||||
as List<ContactListContactEntity>,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isSaving: null == isSaving ? _self.isSaving : isSaving // ignore: cast_nullable_to_non_nullable
|
||||
as bool,successMessage: null == successMessage ? _self.successMessage : successMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -1,121 +0,0 @@
|
||||
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/features/block_phone/presentation/state/block_phone_view_model.dart';
|
||||
import 'package:settings/src/features/block_phone/presentation/state/new_block_phone_contact_view_state.dart';
|
||||
|
||||
final newBlockPhoneContactViewModelProvider =
|
||||
NotifierProvider.autoDispose<
|
||||
NewBlockPhoneContactViewModel,
|
||||
NewBlockPhoneContactViewState
|
||||
>(NewBlockPhoneContactViewModel.new);
|
||||
|
||||
class NewBlockPhoneContactViewModel
|
||||
extends Notifier<NewBlockPhoneContactViewState> {
|
||||
late final TextEditingController nameController;
|
||||
late final TextEditingController phoneController;
|
||||
|
||||
@override
|
||||
NewBlockPhoneContactViewState build() {
|
||||
nameController = TextEditingController();
|
||||
phoneController = TextEditingController();
|
||||
|
||||
nameController.addListener(_refreshCanSave);
|
||||
phoneController.addListener(_onPhoneChanged);
|
||||
|
||||
ref.onDispose(() {
|
||||
nameController.removeListener(_refreshCanSave);
|
||||
phoneController.removeListener(_onPhoneChanged);
|
||||
nameController.dispose();
|
||||
phoneController.dispose();
|
||||
});
|
||||
|
||||
return const NewBlockPhoneContactViewState();
|
||||
}
|
||||
|
||||
void _refreshCanSave() {
|
||||
final canSave =
|
||||
nameController.text.trim().isNotEmpty &&
|
||||
phoneController.text.trim().isNotEmpty;
|
||||
if (canSave == state.canSave) return;
|
||||
state = state.copyWith(canSave: canSave);
|
||||
}
|
||||
|
||||
void _onPhoneChanged() {
|
||||
_refreshCanSave();
|
||||
if (state.phoneError != null) state = state.copyWith(phoneError: null);
|
||||
}
|
||||
|
||||
void updateCountry(String isoCode) {
|
||||
if (isoCode == state.isoCode) return;
|
||||
state = state.copyWith(isoCode: isoCode, phoneError: null);
|
||||
}
|
||||
|
||||
void clearPermissionBlocked() {
|
||||
if (state.permissionBlocked) {
|
||||
state = state.copyWith(permissionBlocked: false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> openSystemSettings() =>
|
||||
ref.read(deviceContactPickerProvider).openSystemSettings();
|
||||
|
||||
Future<void> pickContactFromDevice() async {
|
||||
final response = await ref
|
||||
.read(deviceContactPickerProvider)
|
||||
.pick(hintIsoCode: state.isoCode);
|
||||
|
||||
if (response.outcome ==
|
||||
DeviceContactPickOutcome.permissionPermanentlyDenied) {
|
||||
state = state.copyWith(permissionBlocked: true);
|
||||
return;
|
||||
}
|
||||
|
||||
final data = response.data;
|
||||
if (data == null) return;
|
||||
|
||||
final parsed = data.parsedPhone;
|
||||
if (parsed != null) {
|
||||
state = state.copyWith(isoCode: parsed.isoCode, phoneError: null);
|
||||
phoneController.text = parsed.nationalNumber;
|
||||
} else {
|
||||
phoneController.text = data.rawNumber;
|
||||
state = state.copyWith(phoneError: null);
|
||||
}
|
||||
|
||||
if (nameController.text.trim().isEmpty) {
|
||||
nameController.text = data.displayName;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> submit() async {
|
||||
if (state.isSubmitting || !state.canSave) return false;
|
||||
|
||||
final parsed = SfPhoneNumber.tryParse(
|
||||
phoneController.text,
|
||||
defaultIsoCode: state.isoCode,
|
||||
);
|
||||
if (parsed == null) {
|
||||
state = state.copyWith(phoneError: I18n.errorMessagePhoneIsInvalid);
|
||||
return false;
|
||||
}
|
||||
|
||||
state = state.copyWith(isSubmitting: true);
|
||||
|
||||
await ref
|
||||
.read(blockPhoneViewModelProvider.notifier)
|
||||
.addContact(
|
||||
ContactListContactEntity(
|
||||
name: nameController.text.trim(),
|
||||
phone: parsed.e164,
|
||||
),
|
||||
);
|
||||
|
||||
if (!ref.mounted) return false;
|
||||
state = state.copyWith(isSubmitting: false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'new_block_phone_contact_view_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class NewBlockPhoneContactViewState
|
||||
with _$NewBlockPhoneContactViewState {
|
||||
const factory NewBlockPhoneContactViewState({
|
||||
@Default('ES') String isoCode,
|
||||
@Default(false) bool canSave,
|
||||
@Default(false) bool isSubmitting,
|
||||
@Default(false) bool permissionBlocked,
|
||||
String? phoneError,
|
||||
}) = _NewBlockPhoneContactViewState;
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'new_block_phone_contact_view_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$NewBlockPhoneContactViewState {
|
||||
|
||||
String get isoCode; bool get canSave; bool get isSubmitting; bool get permissionBlocked; String? get phoneError;
|
||||
/// Create a copy of NewBlockPhoneContactViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$NewBlockPhoneContactViewStateCopyWith<NewBlockPhoneContactViewState> get copyWith => _$NewBlockPhoneContactViewStateCopyWithImpl<NewBlockPhoneContactViewState>(this as NewBlockPhoneContactViewState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is NewBlockPhoneContactViewState&&(identical(other.isoCode, isoCode) || other.isoCode == isoCode)&&(identical(other.canSave, canSave) || other.canSave == canSave)&&(identical(other.isSubmitting, isSubmitting) || other.isSubmitting == isSubmitting)&&(identical(other.permissionBlocked, permissionBlocked) || other.permissionBlocked == permissionBlocked)&&(identical(other.phoneError, phoneError) || other.phoneError == phoneError));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,isoCode,canSave,isSubmitting,permissionBlocked,phoneError);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'NewBlockPhoneContactViewState(isoCode: $isoCode, canSave: $canSave, isSubmitting: $isSubmitting, permissionBlocked: $permissionBlocked, phoneError: $phoneError)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $NewBlockPhoneContactViewStateCopyWith<$Res> {
|
||||
factory $NewBlockPhoneContactViewStateCopyWith(NewBlockPhoneContactViewState value, $Res Function(NewBlockPhoneContactViewState) _then) = _$NewBlockPhoneContactViewStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String isoCode, bool canSave, bool isSubmitting, bool permissionBlocked, String? phoneError
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$NewBlockPhoneContactViewStateCopyWithImpl<$Res>
|
||||
implements $NewBlockPhoneContactViewStateCopyWith<$Res> {
|
||||
_$NewBlockPhoneContactViewStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final NewBlockPhoneContactViewState _self;
|
||||
final $Res Function(NewBlockPhoneContactViewState) _then;
|
||||
|
||||
/// Create a copy of NewBlockPhoneContactViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? isoCode = null,Object? canSave = null,Object? isSubmitting = null,Object? permissionBlocked = null,Object? phoneError = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
isoCode: null == isoCode ? _self.isoCode : isoCode // ignore: cast_nullable_to_non_nullable
|
||||
as String,canSave: null == canSave ? _self.canSave : canSave // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isSubmitting: null == isSubmitting ? _self.isSubmitting : isSubmitting // ignore: cast_nullable_to_non_nullable
|
||||
as bool,permissionBlocked: null == permissionBlocked ? _self.permissionBlocked : permissionBlocked // ignore: cast_nullable_to_non_nullable
|
||||
as bool,phoneError: freezed == phoneError ? _self.phoneError : phoneError // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [NewBlockPhoneContactViewState].
|
||||
extension NewBlockPhoneContactViewStatePatterns on NewBlockPhoneContactViewState {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _NewBlockPhoneContactViewState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _NewBlockPhoneContactViewState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _NewBlockPhoneContactViewState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _NewBlockPhoneContactViewState():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _NewBlockPhoneContactViewState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _NewBlockPhoneContactViewState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String isoCode, bool canSave, bool isSubmitting, bool permissionBlocked, String? phoneError)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _NewBlockPhoneContactViewState() when $default != null:
|
||||
return $default(_that.isoCode,_that.canSave,_that.isSubmitting,_that.permissionBlocked,_that.phoneError);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String isoCode, bool canSave, bool isSubmitting, bool permissionBlocked, String? phoneError) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _NewBlockPhoneContactViewState():
|
||||
return $default(_that.isoCode,_that.canSave,_that.isSubmitting,_that.permissionBlocked,_that.phoneError);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String isoCode, bool canSave, bool isSubmitting, bool permissionBlocked, String? phoneError)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _NewBlockPhoneContactViewState() when $default != null:
|
||||
return $default(_that.isoCode,_that.canSave,_that.isSubmitting,_that.permissionBlocked,_that.phoneError);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _NewBlockPhoneContactViewState implements NewBlockPhoneContactViewState {
|
||||
const _NewBlockPhoneContactViewState({this.isoCode = 'ES', this.canSave = false, this.isSubmitting = false, this.permissionBlocked = false, this.phoneError});
|
||||
|
||||
|
||||
@override@JsonKey() final String isoCode;
|
||||
@override@JsonKey() final bool canSave;
|
||||
@override@JsonKey() final bool isSubmitting;
|
||||
@override@JsonKey() final bool permissionBlocked;
|
||||
@override final String? phoneError;
|
||||
|
||||
/// Create a copy of NewBlockPhoneContactViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$NewBlockPhoneContactViewStateCopyWith<_NewBlockPhoneContactViewState> get copyWith => __$NewBlockPhoneContactViewStateCopyWithImpl<_NewBlockPhoneContactViewState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _NewBlockPhoneContactViewState&&(identical(other.isoCode, isoCode) || other.isoCode == isoCode)&&(identical(other.canSave, canSave) || other.canSave == canSave)&&(identical(other.isSubmitting, isSubmitting) || other.isSubmitting == isSubmitting)&&(identical(other.permissionBlocked, permissionBlocked) || other.permissionBlocked == permissionBlocked)&&(identical(other.phoneError, phoneError) || other.phoneError == phoneError));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,isoCode,canSave,isSubmitting,permissionBlocked,phoneError);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'NewBlockPhoneContactViewState(isoCode: $isoCode, canSave: $canSave, isSubmitting: $isSubmitting, permissionBlocked: $permissionBlocked, phoneError: $phoneError)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$NewBlockPhoneContactViewStateCopyWith<$Res> implements $NewBlockPhoneContactViewStateCopyWith<$Res> {
|
||||
factory _$NewBlockPhoneContactViewStateCopyWith(_NewBlockPhoneContactViewState value, $Res Function(_NewBlockPhoneContactViewState) _then) = __$NewBlockPhoneContactViewStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String isoCode, bool canSave, bool isSubmitting, bool permissionBlocked, String? phoneError
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$NewBlockPhoneContactViewStateCopyWithImpl<$Res>
|
||||
implements _$NewBlockPhoneContactViewStateCopyWith<$Res> {
|
||||
__$NewBlockPhoneContactViewStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _NewBlockPhoneContactViewState _self;
|
||||
final $Res Function(_NewBlockPhoneContactViewState) _then;
|
||||
|
||||
/// Create a copy of NewBlockPhoneContactViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? isoCode = null,Object? canSave = null,Object? isSubmitting = null,Object? permissionBlocked = null,Object? phoneError = freezed,}) {
|
||||
return _then(_NewBlockPhoneContactViewState(
|
||||
isoCode: null == isoCode ? _self.isoCode : isoCode // ignore: cast_nullable_to_non_nullable
|
||||
as String,canSave: null == canSave ? _self.canSave : canSave // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isSubmitting: null == isSubmitting ? _self.isSubmitting : isSubmitting // ignore: cast_nullable_to_non_nullable
|
||||
as bool,permissionBlocked: null == permissionBlocked ? _self.permissionBlocked : permissionBlocked // ignore: cast_nullable_to_non_nullable
|
||||
as bool,phoneError: freezed == phoneError ? _self.phoneError : phoneError // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -1,26 +1,37 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:legacy_theme/legacy_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:legacy_theme/legacy_theme.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/providers/block_phone_contact_form_provider.dart';
|
||||
import 'package:settings/src/features/block_phone/presentation/providers/block_phone_controller.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) {
|
||||
void showAddContactSheet(
|
||||
BuildContext context, {
|
||||
required String deviceId,
|
||||
required String userId,
|
||||
required List<ContactListContactEntity> currentContacts,
|
||||
}) {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (_) => const _AddContactSheet(),
|
||||
builder: (_) => _ContactFormSheetWrapper(
|
||||
deviceId: deviceId,
|
||||
userId: userId,
|
||||
currentContacts: currentContacts,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void showEditContactSheet(
|
||||
BuildContext context, {
|
||||
required String deviceId,
|
||||
required String userId,
|
||||
required List<ContactListContactEntity> currentContacts,
|
||||
required int index,
|
||||
required ContactListContactEntity contact,
|
||||
}) {
|
||||
@@ -28,140 +39,206 @@ void showEditContactSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (_) => _EditContactSheet(index: index, contact: contact),
|
||||
builder: (_) => _ContactFormSheetWrapper(
|
||||
deviceId: deviceId,
|
||||
userId: userId,
|
||||
currentContacts: currentContacts,
|
||||
index: index,
|
||||
initialContact: contact,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _AddContactSheet extends ConsumerWidget {
|
||||
const _AddContactSheet();
|
||||
class _ContactFormSheetWrapper extends ConsumerStatefulWidget {
|
||||
final String deviceId;
|
||||
final String userId;
|
||||
final List<ContactListContactEntity> currentContacts;
|
||||
final int? index;
|
||||
final ContactListContactEntity? initialContact;
|
||||
|
||||
const _ContactFormSheetWrapper({
|
||||
required this.deviceId,
|
||||
required this.userId,
|
||||
required this.currentContacts,
|
||||
this.index,
|
||||
this.initialContact,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final vm = ref.read(newBlockPhoneContactViewModelProvider.notifier);
|
||||
final state = ref.watch(newBlockPhoneContactViewModelProvider);
|
||||
|
||||
ref.listen(
|
||||
newBlockPhoneContactViewModelProvider.select((s) => s.permissionBlocked),
|
||||
(_, blocked) {
|
||||
if (blocked == true) {
|
||||
showContactsPermissionDialog(
|
||||
context,
|
||||
onOpenSettings: vm.openSystemSettings,
|
||||
);
|
||||
vm.clearPermissionBlocked();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return ContactFormSheet(
|
||||
title: context.translate(I18n.addAllowedNumber),
|
||||
primaryColor: context.sfColors.legacyPrimary,
|
||||
nameController: vm.nameController,
|
||||
phoneController: vm.phoneController,
|
||||
isoCode: state.isoCode,
|
||||
canSave: state.canSave,
|
||||
isSubmitting: state.isSubmitting,
|
||||
phoneError: state.phoneError,
|
||||
onCountryChanged: vm.updateCountry,
|
||||
onPickContact: vm.pickContactFromDevice,
|
||||
onSubmit: () async {
|
||||
final success = await vm.submit();
|
||||
if (success && context.mounted) Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
ConsumerState<_ContactFormSheetWrapper> createState() =>
|
||||
_ContactFormSheetWrapperState();
|
||||
}
|
||||
|
||||
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> {
|
||||
class _ContactFormSheetWrapperState
|
||||
extends ConsumerState<_ContactFormSheetWrapper> {
|
||||
late final TextEditingController _nameController;
|
||||
late final TextEditingController _phoneController;
|
||||
String _isoCode = SfPhoneNumber.defaultIsoCode;
|
||||
bool _canSave = true;
|
||||
bool _isSubmitting = false;
|
||||
String? _phoneError;
|
||||
|
||||
bool get _isEdit => widget.initialContact != null;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_nameController = TextEditingController(text: widget.contact.name);
|
||||
|
||||
final parsed = SfPhoneNumber.tryParse(widget.contact.phone);
|
||||
final initial = widget.initialContact;
|
||||
_nameController = TextEditingController(text: initial?.name ?? '');
|
||||
final parsed =
|
||||
initial != null ? SfPhoneNumber.tryParse(initial.phone) : null;
|
||||
_phoneController = TextEditingController(
|
||||
text: parsed?.nationalNumber ?? widget.contact.phone,
|
||||
text: parsed?.nationalNumber ?? initial?.phone ?? '',
|
||||
);
|
||||
if (parsed != null) _isoCode = parsed.isoCode;
|
||||
|
||||
_nameController.addListener(_refreshCanSave);
|
||||
_phoneController.addListener(_refreshCanSave);
|
||||
_phoneController.addListener(_onPhoneChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.removeListener(_refreshCanSave);
|
||||
_phoneController.removeListener(_onPhoneChanged);
|
||||
_nameController.dispose();
|
||||
_phoneController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _refreshCanSave() {
|
||||
final canSave =
|
||||
_nameController.text.trim().isNotEmpty &&
|
||||
final canSave = _nameController.text.trim().isNotEmpty &&
|
||||
_phoneController.text.trim().isNotEmpty;
|
||||
if (canSave != _canSave) setState(() => _canSave = canSave);
|
||||
if (_phoneError != null) setState(() => _phoneError = null);
|
||||
ref
|
||||
.read(blockPhoneContactFormProvider(widget.initialContact).notifier)
|
||||
.setCanSave(canSave);
|
||||
}
|
||||
|
||||
Future<void> _submit() async {
|
||||
if (_isSubmitting || !_canSave) return;
|
||||
void _onPhoneChanged() {
|
||||
_refreshCanSave();
|
||||
ref
|
||||
.read(blockPhoneContactFormProvider(widget.initialContact).notifier)
|
||||
.clearPhoneError();
|
||||
}
|
||||
|
||||
final parsed = SfPhoneNumber.tryParse(
|
||||
_phoneController.text,
|
||||
defaultIsoCode: _isoCode,
|
||||
);
|
||||
if (parsed == null) {
|
||||
setState(() => _phoneError = I18n.errorMessagePhoneIsInvalid);
|
||||
Future<void> _pickContactFromDevice() async {
|
||||
final formNotifier = ref
|
||||
.read(blockPhoneContactFormProvider(widget.initialContact).notifier);
|
||||
final isoCode =
|
||||
ref.read(blockPhoneContactFormProvider(widget.initialContact)).isoCode;
|
||||
final response = await ref
|
||||
.read(deviceContactPickerProvider)
|
||||
.pick(hintIsoCode: isoCode);
|
||||
|
||||
if (response.outcome ==
|
||||
DeviceContactPickOutcome.permissionPermanentlyDenied) {
|
||||
formNotifier.setPermissionBlocked(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _isSubmitting = true);
|
||||
final data = response.data;
|
||||
if (data == null) return;
|
||||
|
||||
await ref
|
||||
.read(blockPhoneViewModelProvider.notifier)
|
||||
.updateContact(
|
||||
widget.index,
|
||||
ContactListContactEntity(
|
||||
name: _nameController.text.trim(),
|
||||
phone: parsed.e164,
|
||||
),
|
||||
);
|
||||
final parsed = data.parsedPhone;
|
||||
if (parsed != null) {
|
||||
formNotifier.setIsoCode(parsed.isoCode);
|
||||
_phoneController.text = parsed.nationalNumber;
|
||||
} else {
|
||||
_phoneController.text = data.rawNumber;
|
||||
formNotifier.clearPhoneError();
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() => _isSubmitting = false);
|
||||
Navigator.pop(context);
|
||||
if (_nameController.text.trim().isEmpty) {
|
||||
_nameController.text = data.displayName;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _submit() async {
|
||||
final formNotifier = ref
|
||||
.read(blockPhoneContactFormProvider(widget.initialContact).notifier);
|
||||
final formState =
|
||||
ref.read(blockPhoneContactFormProvider(widget.initialContact));
|
||||
if (!formState.canSave) return;
|
||||
|
||||
final parsed = SfPhoneNumber.tryParse(
|
||||
_phoneController.text,
|
||||
defaultIsoCode: formState.isoCode,
|
||||
);
|
||||
if (parsed == null) {
|
||||
formNotifier.setPhoneError(I18n.errorMessagePhoneIsInvalid);
|
||||
return;
|
||||
}
|
||||
|
||||
final contact = ContactListContactEntity(
|
||||
name: _nameController.text.trim(),
|
||||
phone: parsed.e164,
|
||||
);
|
||||
|
||||
final controller = ref.read(blockPhoneControllerProvider.notifier);
|
||||
if (_isEdit) {
|
||||
await controller.updateContact(
|
||||
deviceId: widget.deviceId,
|
||||
userId: widget.userId,
|
||||
current: widget.currentContacts,
|
||||
index: widget.index!,
|
||||
contact: contact,
|
||||
);
|
||||
} else {
|
||||
await controller.addContact(
|
||||
deviceId: widget.deviceId,
|
||||
userId: widget.userId,
|
||||
current: widget.currentContacts,
|
||||
contact: contact,
|
||||
);
|
||||
}
|
||||
|
||||
if (!context.mounted) return;
|
||||
final state = ref.read(blockPhoneControllerProvider);
|
||||
if (state.hasError) return;
|
||||
Navigator.of(context).pop();
|
||||
await showSuccessDialog(
|
||||
context,
|
||||
_isEdit ? I18n.numberUpdated : I18n.numberAdded,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ref.listen(
|
||||
blockPhoneContactFormProvider(
|
||||
widget.initialContact,
|
||||
).select((s) => s.permissionBlocked),
|
||||
(_, blocked) {
|
||||
if (blocked == true) {
|
||||
showContactsPermissionDialog(
|
||||
context,
|
||||
onOpenSettings: ref
|
||||
.read(deviceContactPickerProvider)
|
||||
.openSystemSettings,
|
||||
);
|
||||
ref
|
||||
.read(
|
||||
blockPhoneContactFormProvider(widget.initialContact).notifier,
|
||||
)
|
||||
.setPermissionBlocked(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
final formState =
|
||||
ref.watch(blockPhoneContactFormProvider(widget.initialContact));
|
||||
final isSubmitting = ref.watch(
|
||||
blockPhoneControllerProvider.select((s) => s.isLoading),
|
||||
);
|
||||
|
||||
return ContactFormSheet(
|
||||
title: context.translate(I18n.editAllowedNumber),
|
||||
title: context.translate(
|
||||
_isEdit ? I18n.editAllowedNumber : I18n.addAllowedNumber,
|
||||
),
|
||||
primaryColor: context.sfColors.legacyPrimary,
|
||||
nameController: _nameController,
|
||||
phoneController: _phoneController,
|
||||
isoCode: _isoCode,
|
||||
canSave: _canSave,
|
||||
isSubmitting: _isSubmitting,
|
||||
phoneError: _phoneError,
|
||||
onCountryChanged: (isoCode) => setState(() => _isoCode = isoCode),
|
||||
onPickContact: () async {},
|
||||
isoCode: formState.isoCode,
|
||||
canSave: formState.canSave,
|
||||
isSubmitting: isSubmitting,
|
||||
phoneError: formState.phoneError,
|
||||
onCountryChanged: ref
|
||||
.read(blockPhoneContactFormProvider(widget.initialContact).notifier)
|
||||
.setIsoCode,
|
||||
onPickContact: _pickContactFromDevice,
|
||||
onSubmit: _submit,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ class SosContactsController extends _$SosContactsController {
|
||||
required List<ContactListContactEntity> current,
|
||||
required int index,
|
||||
}) async {
|
||||
state = const AsyncLoading();
|
||||
if (current.length <= 1) {
|
||||
state = AsyncError(
|
||||
const SosMinimumOneContactException(),
|
||||
@@ -53,7 +54,6 @@ class SosContactsController extends _$SosContactsController {
|
||||
);
|
||||
return;
|
||||
}
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
final updated = [...current]..removeAt(index);
|
||||
await ref.read(sosContactsRepositoryProvider).upsertEmergencyContacts(
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:settings/src/core/domain/entities/contact_list_contact_entity.dart';
|
||||
import 'package:settings/src/core/domain/repositories/block_phone_repository.dart';
|
||||
import 'package:settings/src/core/providers/block_phone_repository_provider.dart';
|
||||
import 'package:settings/src/features/block_phone/presentation/providers/block_phone_controller.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
import 'package:sf_shared/testing.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
|
||||
class MockBlockPhoneRepository extends Mock implements BlockPhoneRepository {}
|
||||
|
||||
const _a = ContactListContactEntity(name: 'Alice', phone: '+34600000001');
|
||||
const _b = ContactListContactEntity(name: 'Bob', phone: '+34600000002');
|
||||
const _c = ContactListContactEntity(name: 'Carol', phone: '+34600000003');
|
||||
|
||||
void main() {
|
||||
setUpAll(() {
|
||||
registerFallbackValue(const <ContactListContactEntity>[]);
|
||||
});
|
||||
|
||||
ProviderContainer buildContainer(BlockPhoneRepository repo) {
|
||||
return makeContainer(
|
||||
overrides: [
|
||||
blockPhoneRepositoryProvider.overrideWithValue(repo),
|
||||
sfTrackingProvider.overrideWithValue(
|
||||
SfTrackingRepository(clients: const []),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
group('BlockPhoneController.addContact', () {
|
||||
test('appends contact and persists via repository', () async {
|
||||
final repo = MockBlockPhoneRepository();
|
||||
when(
|
||||
() => repo.upsertWhitelist(
|
||||
userId: any(named: 'userId'),
|
||||
deviceId: any(named: 'deviceId'),
|
||||
contacts: any(named: 'contacts'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
final container = buildContainer(repo);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container.read(blockPhoneControllerProvider.notifier).addContact(
|
||||
deviceId: 'd',
|
||||
userId: 'u',
|
||||
current: const [_a],
|
||||
contact: _b,
|
||||
);
|
||||
|
||||
expect(
|
||||
container.read(blockPhoneControllerProvider),
|
||||
isA<AsyncData<void>>(),
|
||||
);
|
||||
verify(
|
||||
() => repo.upsertWhitelist(
|
||||
userId: 'u',
|
||||
deviceId: 'd',
|
||||
contacts: [_a, _b],
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('exposes AsyncError when repository fails', () async {
|
||||
final repo = MockBlockPhoneRepository();
|
||||
when(
|
||||
() => repo.upsertWhitelist(
|
||||
userId: any(named: 'userId'),
|
||||
deviceId: any(named: 'deviceId'),
|
||||
contacts: any(named: 'contacts'),
|
||||
),
|
||||
).thenThrow(const ApiException(message: 'boom', isNetworkError: true));
|
||||
|
||||
final container = buildContainer(repo);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container.read(blockPhoneControllerProvider.notifier).addContact(
|
||||
deviceId: 'd',
|
||||
userId: 'u',
|
||||
current: const [],
|
||||
contact: _a,
|
||||
);
|
||||
|
||||
expect(
|
||||
container.read(blockPhoneControllerProvider),
|
||||
isA<AsyncError<void>>(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('BlockPhoneController.updateContact', () {
|
||||
test('replaces contact at index and persists via repository', () async {
|
||||
final repo = MockBlockPhoneRepository();
|
||||
when(
|
||||
() => repo.upsertWhitelist(
|
||||
userId: any(named: 'userId'),
|
||||
deviceId: any(named: 'deviceId'),
|
||||
contacts: any(named: 'contacts'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
final container = buildContainer(repo);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container
|
||||
.read(blockPhoneControllerProvider.notifier)
|
||||
.updateContact(
|
||||
deviceId: 'd',
|
||||
userId: 'u',
|
||||
current: const [_a, _b],
|
||||
index: 1,
|
||||
contact: _c,
|
||||
);
|
||||
|
||||
expect(
|
||||
container.read(blockPhoneControllerProvider),
|
||||
isA<AsyncData<void>>(),
|
||||
);
|
||||
verify(
|
||||
() => repo.upsertWhitelist(
|
||||
userId: 'u',
|
||||
deviceId: 'd',
|
||||
contacts: [_a, _c],
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('exposes AsyncError when repository fails', () async {
|
||||
final repo = MockBlockPhoneRepository();
|
||||
when(
|
||||
() => repo.upsertWhitelist(
|
||||
userId: any(named: 'userId'),
|
||||
deviceId: any(named: 'deviceId'),
|
||||
contacts: any(named: 'contacts'),
|
||||
),
|
||||
).thenThrow(const ApiException(message: 'boom', isNetworkError: true));
|
||||
|
||||
final container = buildContainer(repo);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container
|
||||
.read(blockPhoneControllerProvider.notifier)
|
||||
.updateContact(
|
||||
deviceId: 'd',
|
||||
userId: 'u',
|
||||
current: const [_a],
|
||||
index: 0,
|
||||
contact: _b,
|
||||
);
|
||||
|
||||
expect(
|
||||
container.read(blockPhoneControllerProvider),
|
||||
isA<AsyncError<void>>(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('BlockPhoneController.removeContact', () {
|
||||
test('removes contact at index and persists via repository', () async {
|
||||
final repo = MockBlockPhoneRepository();
|
||||
when(
|
||||
() => repo.upsertWhitelist(
|
||||
userId: any(named: 'userId'),
|
||||
deviceId: any(named: 'deviceId'),
|
||||
contacts: any(named: 'contacts'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
final container = buildContainer(repo);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container
|
||||
.read(blockPhoneControllerProvider.notifier)
|
||||
.removeContact(
|
||||
deviceId: 'd',
|
||||
userId: 'u',
|
||||
current: const [_a, _b],
|
||||
index: 0,
|
||||
);
|
||||
|
||||
expect(
|
||||
container.read(blockPhoneControllerProvider),
|
||||
isA<AsyncData<void>>(),
|
||||
);
|
||||
verify(
|
||||
() => repo.upsertWhitelist(
|
||||
userId: 'u',
|
||||
deviceId: 'd',
|
||||
contacts: [_b],
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('blocks removal when only one contact remains', () async {
|
||||
final repo = MockBlockPhoneRepository();
|
||||
final container = buildContainer(repo);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container
|
||||
.read(blockPhoneControllerProvider.notifier)
|
||||
.removeContact(
|
||||
deviceId: 'd',
|
||||
userId: 'u',
|
||||
current: const [_a],
|
||||
index: 0,
|
||||
);
|
||||
|
||||
final state = container.read(blockPhoneControllerProvider);
|
||||
expect(state, isA<AsyncError<void>>());
|
||||
expect(state.error, isA<BlockPhoneMinimumOneContactException>());
|
||||
verifyNever(
|
||||
() => repo.upsertWhitelist(
|
||||
userId: any(named: 'userId'),
|
||||
deviceId: any(named: 'deviceId'),
|
||||
contacts: any(named: 'contacts'),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('exposes AsyncError when repository fails', () async {
|
||||
final repo = MockBlockPhoneRepository();
|
||||
when(
|
||||
() => repo.upsertWhitelist(
|
||||
userId: any(named: 'userId'),
|
||||
deviceId: any(named: 'deviceId'),
|
||||
contacts: any(named: 'contacts'),
|
||||
),
|
||||
).thenThrow(const ApiException(message: 'boom', isNetworkError: true));
|
||||
|
||||
final container = buildContainer(repo);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container
|
||||
.read(blockPhoneControllerProvider.notifier)
|
||||
.removeContact(
|
||||
deviceId: 'd',
|
||||
userId: 'u',
|
||||
current: const [_a, _b],
|
||||
index: 0,
|
||||
);
|
||||
|
||||
expect(
|
||||
container.read(blockPhoneControllerProvider),
|
||||
isA<AsyncError<void>>(),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -579,6 +579,7 @@
|
||||
"noBlockedNumbers": "Keine Nummern konfiguriert",
|
||||
"noBlockedNumbersDescription": "Fügen Sie Nummern hinzu, um einzuschränken, wer mit dem Gerät kommunizieren kann",
|
||||
"whitelistDescription": "Nur diese Nummern können mit dem Gerät kommunizieren",
|
||||
"whitelistMinimumOneContact": "Es muss mindestens 1 erlaubte Nummer vorhanden sein",
|
||||
"allowedNumbersCount": "{count} erlaubte Nummern",
|
||||
"removeAllowedNumber": "Erlaubte Nummer entfernen",
|
||||
"removeAllowedNumberConfirm": "\"{name}\" aus der Liste der erlaubten Nummern entfernen? Diese Nummer kann nicht mehr mit dem Gerät kommunizieren",
|
||||
|
||||
@@ -753,6 +753,7 @@
|
||||
"noBlockedNumbers": "No numbers configured",
|
||||
"noBlockedNumbersDescription": "Add numbers to restrict who can communicate with the device",
|
||||
"whitelistDescription": "Only these numbers can communicate with the device",
|
||||
"whitelistMinimumOneContact": "There must be at least 1 allowed number",
|
||||
"allowedNumbersCount": "{count} allowed numbers",
|
||||
"removeAllowedNumber": "Remove allowed number",
|
||||
"removeAllowedNumberConfirm": "Remove \"{name}\" from the allowed numbers list? This number will no longer be able to communicate with the device",
|
||||
|
||||
@@ -754,6 +754,7 @@
|
||||
"noBlockedNumbers": "No hay números configurados",
|
||||
"noBlockedNumbersDescription": "Añade números para restringir quién puede comunicarse con el dispositivo",
|
||||
"whitelistDescription": "Solo estos números pueden comunicarse con el dispositivo",
|
||||
"whitelistMinimumOneContact": "Debe haber al menos 1 número permitido",
|
||||
"allowedNumbersCount": "{count} números permitidos",
|
||||
"removeAllowedNumber": "Eliminar número permitido",
|
||||
"removeAllowedNumberConfirm": "¿Eliminar \"{name}\" de la lista de números permitidos? Este número ya no podrá comunicarse con el dispositivo",
|
||||
|
||||
@@ -579,6 +579,7 @@
|
||||
"noBlockedNumbers": "Aucun numéro configuré",
|
||||
"noBlockedNumbersDescription": "Ajoutez des numéros pour restreindre qui peut communiquer avec l'appareil",
|
||||
"whitelistDescription": "Seuls ces numéros peuvent communiquer avec l'appareil",
|
||||
"whitelistMinimumOneContact": "Il doit y avoir au moins 1 numéro autorisé",
|
||||
"allowedNumbersCount": "{count} numéros autorisés",
|
||||
"removeAllowedNumber": "Supprimer le numéro autorisé",
|
||||
"removeAllowedNumberConfirm": "Supprimer \"{name}\" de la liste des numéros autorisés ? Ce numéro ne pourra plus communiquer avec l'appareil",
|
||||
|
||||
@@ -579,6 +579,7 @@
|
||||
"noBlockedNumbers": "Nessun numero configurato",
|
||||
"noBlockedNumbersDescription": "Aggiungi numeri per limitare chi può comunicare con il dispositivo",
|
||||
"whitelistDescription": "Solo questi numeri possono comunicare con il dispositivo",
|
||||
"whitelistMinimumOneContact": "Deve esserci almeno 1 numero consentito",
|
||||
"allowedNumbersCount": "{count} numeri consentiti",
|
||||
"removeAllowedNumber": "Rimuovi numero consentito",
|
||||
"removeAllowedNumberConfirm": "Rimuovere \"{name}\" dall'elenco dei numeri consentiti? Questo numero non potrà più comunicare con il dispositivo",
|
||||
|
||||
@@ -579,6 +579,7 @@
|
||||
"noBlockedNumbers": "Nenhum número configurado",
|
||||
"noBlockedNumbersDescription": "Adicione números para restringir quem pode comunicar com o dispositivo",
|
||||
"whitelistDescription": "Apenas estes números podem comunicar com o dispositivo",
|
||||
"whitelistMinimumOneContact": "Deve haver pelo menos 1 número permitido",
|
||||
"allowedNumbersCount": "{count} números permitidos",
|
||||
"removeAllowedNumber": "Remover número permitido",
|
||||
"removeAllowedNumberConfirm": "Remover \"{name}\" da lista de números permitidos? Este número não poderá mais comunicar com o dispositivo",
|
||||
|
||||
@@ -975,6 +975,7 @@ class I18n {
|
||||
static const String weekdayWedShort = 'weekdayWedShort';
|
||||
static const String welcome = 'welcome';
|
||||
static const String whitelistDescription = 'whitelistDescription';
|
||||
static const String whitelistMinimumOneContact = 'whitelistMinimumOneContact';
|
||||
static const String wifiAvailableNetworks = 'wifiAvailableNetworks';
|
||||
static const String wifiBssid = 'wifiBssid';
|
||||
static const String wifiBssidHint = 'wifiBssidHint';
|
||||
|
||||
Reference in New Issue
Block a user