refactor(settings): unify SOS and block_phone sheets with shared ContactFormSheet
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
import 'package:utils/utils.dart';
|
||||
|
||||
/// Presentational sheet shared by SOS contacts and block-phone whitelist
|
||||
/// flows. Holds no state and no VM knowledge — the caller wires its own
|
||||
/// feature VM and passes the pieces down.
|
||||
class ContactFormSheet extends StatelessWidget {
|
||||
final String title;
|
||||
final Color primaryColor;
|
||||
final TextEditingController nameController;
|
||||
final TextEditingController phoneController;
|
||||
final String isoCode;
|
||||
final bool canSave;
|
||||
final bool isSubmitting;
|
||||
final String? phoneError;
|
||||
final ValueChanged<String> onCountryChanged;
|
||||
final Future<void> Function() onPickContact;
|
||||
final VoidCallback onSubmit;
|
||||
|
||||
const ContactFormSheet({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.primaryColor,
|
||||
required this.nameController,
|
||||
required this.phoneController,
|
||||
required this.isoCode,
|
||||
required this.canSave,
|
||||
required this.isSubmitting,
|
||||
required this.phoneError,
|
||||
required this.onCountryChanged,
|
||||
required this.onPickContact,
|
||||
required this.onSubmit,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: bottomInset),
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: SizeUtils.getByScreen(small: 22, big: 24),
|
||||
vertical: SizeUtils.getByScreen(small: 16, big: 18),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const _DragHandle(),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 12, big: 14)),
|
||||
_Header(
|
||||
title: title,
|
||||
primaryColor: primaryColor,
|
||||
canSave: canSave,
|
||||
isSubmitting: isSubmitting,
|
||||
onSubmit: onSubmit,
|
||||
),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 18)),
|
||||
_FieldLabel(label: context.translate(I18n.name)),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: nameController,
|
||||
decoration: _inputDecoration(
|
||||
hintText: context.translate(I18n.contactName),
|
||||
primaryColor: primaryColor,
|
||||
),
|
||||
textCapitalization: TextCapitalization.words,
|
||||
),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 18)),
|
||||
_FieldLabel(label: context.translate(I18n.phone)),
|
||||
const SizedBox(height: 8),
|
||||
_PhoneRow(
|
||||
isoCode: isoCode,
|
||||
controller: phoneController,
|
||||
primaryColor: primaryColor,
|
||||
onCountryChanged: onCountryChanged,
|
||||
onPickContact: onPickContact,
|
||||
),
|
||||
if (phoneError != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
context.translate(phoneError!),
|
||||
style: TextStyle(
|
||||
color: Colors.red.shade600,
|
||||
fontSize: SizeUtils.getByScreen(small: 13, big: 13),
|
||||
),
|
||||
),
|
||||
],
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 18)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class _DragHandle extends StatelessWidget {
|
||||
const _DragHandle();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade300,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FieldLabel extends StatelessWidget {
|
||||
final String label;
|
||||
const _FieldLabel({required this.label});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: SizeUtils.getByScreen(small: 15, big: 16),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Header extends StatelessWidget {
|
||||
final String title;
|
||||
final Color primaryColor;
|
||||
final bool canSave;
|
||||
final bool isSubmitting;
|
||||
final VoidCallback onSubmit;
|
||||
|
||||
const _Header({
|
||||
required this.title,
|
||||
required this.primaryColor,
|
||||
required this.canSave,
|
||||
required this.isSubmitting,
|
||||
required this.onSubmit,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: SizeUtils.getByScreen(small: 20, big: 21),
|
||||
fontWeight: FontWeight.w600,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: (canSave && !isSubmitting) ? onSubmit : null,
|
||||
child: isSubmitting
|
||||
? SizedBox(
|
||||
width: SizeUtils.getByScreen(small: 20, big: 22),
|
||||
height: SizeUtils.getByScreen(small: 20, big: 22),
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: primaryColor,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
context.translate(I18n.save),
|
||||
style: TextStyle(
|
||||
color: canSave ? primaryColor : Colors.grey,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: SizeUtils.getByScreen(small: 16, big: 17),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PhoneRow extends StatelessWidget {
|
||||
final String isoCode;
|
||||
final TextEditingController controller;
|
||||
final Color primaryColor;
|
||||
final ValueChanged<String> onCountryChanged;
|
||||
final Future<void> Function() onPickContact;
|
||||
|
||||
const _PhoneRow({
|
||||
required this.isoCode,
|
||||
required this.controller,
|
||||
required this.primaryColor,
|
||||
required this.onCountryChanged,
|
||||
required this.onPickContact,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
CountryPrefixPicker(
|
||||
headerText: context.translate(I18n.selectYourCountry),
|
||||
initialSelection: isoCode,
|
||||
onChanged: (country) {
|
||||
final code = country.code;
|
||||
if (code != null) onCountryChanged(code);
|
||||
},
|
||||
width: 80,
|
||||
),
|
||||
SizedBox(width: SizeUtils.getByScreen(small: 10, big: 8)),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
decoration: _inputDecoration(
|
||||
hintText: context.translate(I18n.phoneNumber),
|
||||
primaryColor: primaryColor,
|
||||
),
|
||||
keyboardType: TextInputType.phone,
|
||||
),
|
||||
),
|
||||
SizedBox(width: SizeUtils.getByScreen(small: 10, big: 8)),
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: primaryColor,
|
||||
),
|
||||
child: IconButton(
|
||||
onPressed: onPickContact,
|
||||
icon: Icon(
|
||||
SFIcons.contactsCircle,
|
||||
color: Colors.white,
|
||||
size: SizeUtils.getByScreen(small: 28, big: 26),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InputDecoration _inputDecoration({
|
||||
required String hintText,
|
||||
required Color primaryColor,
|
||||
}) {
|
||||
return InputDecoration(
|
||||
hintText: hintText,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
borderSide: BorderSide(color: primaryColor, width: 2),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
|
||||
);
|
||||
}
|
||||
@@ -32,8 +32,10 @@ class BlockPhoneViewModel extends Notifier<BlockPhoneViewState> {
|
||||
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),
|
||||
@@ -55,6 +57,7 @@ class BlockPhoneViewModel extends Notifier<BlockPhoneViewState> {
|
||||
deviceId: device.id,
|
||||
contacts: updatedContacts,
|
||||
);
|
||||
if (!ref.mounted) return;
|
||||
|
||||
unawaited(
|
||||
_tracking.legacySettingsBlockPhoneContactAdded(
|
||||
@@ -68,6 +71,7 @@ class BlockPhoneViewModel extends Notifier<BlockPhoneViewState> {
|
||||
successMessage: I18n.numberAdded,
|
||||
);
|
||||
} catch (e) {
|
||||
if (!ref.mounted) return;
|
||||
state = state.copyWith(
|
||||
isSaving: false,
|
||||
errorMessage: formatErrorMessage(e),
|
||||
@@ -89,6 +93,7 @@ class BlockPhoneViewModel extends Notifier<BlockPhoneViewState> {
|
||||
deviceId: device.id,
|
||||
contacts: updatedContacts,
|
||||
);
|
||||
if (!ref.mounted) return;
|
||||
|
||||
unawaited(
|
||||
_tracking.legacySettingsBlockPhoneContactRemoved(
|
||||
@@ -102,6 +107,7 @@ class BlockPhoneViewModel extends Notifier<BlockPhoneViewState> {
|
||||
successMessage: I18n.numberRemoved,
|
||||
);
|
||||
} catch (e) {
|
||||
if (!ref.mounted) return;
|
||||
state = state.copyWith(
|
||||
isSaving: false,
|
||||
errorMessage: formatErrorMessage(e),
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
// 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,13 +1,10 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
import 'package:utils/utils.dart';
|
||||
|
||||
import 'package:settings/src/core/domain/entities/contact_list_contact_entity.dart';
|
||||
|
||||
import '../state/block_phone_view_model.dart';
|
||||
import 'package:settings/src/core/presentation/widgets/contact_form_sheet.dart';
|
||||
import 'package:settings/src/features/block_phone/presentation/state/new_block_phone_contact_view_model.dart';
|
||||
|
||||
void showAddContactSheet(BuildContext context) {
|
||||
showModalBottomSheet<void>(
|
||||
@@ -18,220 +15,43 @@ void showAddContactSheet(BuildContext context) {
|
||||
);
|
||||
}
|
||||
|
||||
class _AddContactSheet extends ConsumerStatefulWidget {
|
||||
class _AddContactSheet extends ConsumerWidget {
|
||||
const _AddContactSheet();
|
||||
|
||||
@override
|
||||
ConsumerState<_AddContactSheet> createState() => _AddContactSheetState();
|
||||
}
|
||||
|
||||
class _AddContactSheetState extends ConsumerState<_AddContactSheet> {
|
||||
final _nameController = TextEditingController();
|
||||
final _phoneController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_phoneController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool get _canSave =>
|
||||
_nameController.text.trim().isNotEmpty &&
|
||||
_phoneController.text.trim().isNotEmpty;
|
||||
|
||||
Future<void> _pickContact() async {
|
||||
final contact = await FlutterContacts.openExternalPick();
|
||||
if (contact == null || !mounted) return;
|
||||
|
||||
final fullContact = await FlutterContacts.getContact(
|
||||
contact.id,
|
||||
withProperties: true,
|
||||
);
|
||||
if (fullContact == null || fullContact.phones.isEmpty) return;
|
||||
|
||||
_phoneController.text = fullContact.phones.first.number;
|
||||
|
||||
if (_nameController.text.trim().isEmpty) {
|
||||
_nameController.text = fullContact.displayName;
|
||||
}
|
||||
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void _submit() {
|
||||
if (!_canSave) return;
|
||||
|
||||
final vm = ref.read(blockPhoneViewModelProvider.notifier);
|
||||
vm.addContact(
|
||||
ContactListContactEntity(
|
||||
name: _nameController.text.trim(),
|
||||
phone: _phoneController.text.trim(),
|
||||
),
|
||||
);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
final primaryColor = theme.getColorFor(ThemeCode.legacyPrimary);
|
||||
final isSaving = ref.watch(
|
||||
blockPhoneViewModelProvider.select((s) => s.isSaving),
|
||||
);
|
||||
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
|
||||
final vm = ref.read(newBlockPhoneContactViewModelProvider.notifier);
|
||||
final state = ref.watch(newBlockPhoneContactViewModelProvider);
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: bottomInset),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: SizeUtils.getByScreen(small: 22, big: 24),
|
||||
vertical: SizeUtils.getByScreen(small: 16, big: 18),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade300,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 12, big: 14)),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
context.translate(I18n.addAllowedNumber),
|
||||
style: TextStyle(
|
||||
fontSize: SizeUtils.getByScreen(small: 20, big: 21),
|
||||
fontWeight: FontWeight.w600,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _canSave && !isSaving ? _submit : null,
|
||||
child: isSaving
|
||||
? SizedBox(
|
||||
width: SizeUtils.getByScreen(small: 20, big: 22),
|
||||
height: SizeUtils.getByScreen(small: 20, big: 22),
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: primaryColor,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
context.translate(I18n.save),
|
||||
style: TextStyle(
|
||||
color: _canSave ? primaryColor : Colors.grey,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: SizeUtils.getByScreen(
|
||||
small: 16,
|
||||
big: 17,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 18)),
|
||||
Text(
|
||||
context.translate(I18n.name),
|
||||
style: TextStyle(
|
||||
fontSize: SizeUtils.getByScreen(small: 15, big: 16),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _nameController,
|
||||
onChanged: (_) => setState(() {}),
|
||||
decoration: _inputDecoration(
|
||||
hintText: context.translate(I18n.contactName),
|
||||
primaryColor: primaryColor,
|
||||
),
|
||||
textCapitalization: TextCapitalization.words,
|
||||
),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 18)),
|
||||
Text(
|
||||
context.translate(I18n.phone),
|
||||
style: TextStyle(
|
||||
fontSize: SizeUtils.getByScreen(small: 15, big: 16),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _phoneController,
|
||||
onChanged: (_) => setState(() {}),
|
||||
readOnly: true,
|
||||
decoration: _inputDecoration(
|
||||
hintText: context.translate(I18n.phoneNumber),
|
||||
primaryColor: primaryColor,
|
||||
),
|
||||
keyboardType: TextInputType.phone,
|
||||
),
|
||||
),
|
||||
SizedBox(width: SizeUtils.getByScreen(small: 10, big: 8)),
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: primaryColor,
|
||||
),
|
||||
child: IconButton(
|
||||
onPressed: _pickContact,
|
||||
icon: Icon(
|
||||
SFIcons.contactsCircle,
|
||||
color: Colors.white,
|
||||
size: SizeUtils.getByScreen(small: 28, big: 26),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 18)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
ref.listen(
|
||||
newBlockPhoneContactViewModelProvider.select((s) => s.permissionBlocked),
|
||||
(_, blocked) {
|
||||
if (blocked == true) {
|
||||
showContactsPermissionDialog(
|
||||
context,
|
||||
onOpenSettings: vm.openSystemSettings,
|
||||
);
|
||||
vm.clearPermissionBlocked();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
InputDecoration _inputDecoration({
|
||||
required String hintText,
|
||||
required Color primaryColor,
|
||||
}) {
|
||||
return InputDecoration(
|
||||
hintText: hintText,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
borderSide: BorderSide(color: primaryColor, width: 2),
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 14, vertical: 14),
|
||||
return ContactFormSheet(
|
||||
title: context.translate(I18n.addAllowedNumber),
|
||||
primaryColor: theme.getColorFor(ThemeCode.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);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
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/sos_contacts/presentation/state/new_sos_contact_view_state.dart';
|
||||
import 'package:settings/src/features/sos_contacts/presentation/state/sos_contacts_view_model.dart';
|
||||
|
||||
final newSosContactViewModelProvider =
|
||||
NotifierProvider.autoDispose<NewSosContactViewModel, NewSosContactViewState>(
|
||||
NewSosContactViewModel.new,
|
||||
);
|
||||
|
||||
class NewSosContactViewModel extends Notifier<NewSosContactViewState> {
|
||||
late final TextEditingController nameController;
|
||||
late final TextEditingController phoneController;
|
||||
|
||||
@override
|
||||
NewSosContactViewState 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 NewSosContactViewState();
|
||||
}
|
||||
|
||||
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(sosContactsViewModelProvider.notifier)
|
||||
.addContact(
|
||||
ContactListContactEntity(
|
||||
name: nameController.text.trim(),
|
||||
phone: parsed.e164,
|
||||
),
|
||||
);
|
||||
|
||||
if (!ref.mounted) return false;
|
||||
state = state.copyWith(isSubmitting: false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'new_sos_contact_view_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class NewSosContactViewState with _$NewSosContactViewState {
|
||||
const factory NewSosContactViewState({
|
||||
@Default('ES') String isoCode,
|
||||
@Default(false) bool canSave,
|
||||
@Default(false) bool isSubmitting,
|
||||
@Default(false) bool permissionBlocked,
|
||||
String? phoneError,
|
||||
}) = _NewSosContactViewState;
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
// 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_sos_contact_view_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$NewSosContactViewState {
|
||||
|
||||
String get isoCode; bool get canSave; bool get isSubmitting; bool get permissionBlocked; String? get phoneError;
|
||||
/// Create a copy of NewSosContactViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$NewSosContactViewStateCopyWith<NewSosContactViewState> get copyWith => _$NewSosContactViewStateCopyWithImpl<NewSosContactViewState>(this as NewSosContactViewState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is NewSosContactViewState&&(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 'NewSosContactViewState(isoCode: $isoCode, canSave: $canSave, isSubmitting: $isSubmitting, permissionBlocked: $permissionBlocked, phoneError: $phoneError)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $NewSosContactViewStateCopyWith<$Res> {
|
||||
factory $NewSosContactViewStateCopyWith(NewSosContactViewState value, $Res Function(NewSosContactViewState) _then) = _$NewSosContactViewStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String isoCode, bool canSave, bool isSubmitting, bool permissionBlocked, String? phoneError
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$NewSosContactViewStateCopyWithImpl<$Res>
|
||||
implements $NewSosContactViewStateCopyWith<$Res> {
|
||||
_$NewSosContactViewStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final NewSosContactViewState _self;
|
||||
final $Res Function(NewSosContactViewState) _then;
|
||||
|
||||
/// Create a copy of NewSosContactViewState
|
||||
/// 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 [NewSosContactViewState].
|
||||
extension NewSosContactViewStatePatterns on NewSosContactViewState {
|
||||
/// 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( _NewSosContactViewState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _NewSosContactViewState() 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( _NewSosContactViewState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _NewSosContactViewState():
|
||||
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( _NewSosContactViewState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _NewSosContactViewState() 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 _NewSosContactViewState() 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 _NewSosContactViewState():
|
||||
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 _NewSosContactViewState() when $default != null:
|
||||
return $default(_that.isoCode,_that.canSave,_that.isSubmitting,_that.permissionBlocked,_that.phoneError);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _NewSosContactViewState implements NewSosContactViewState {
|
||||
const _NewSosContactViewState({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 NewSosContactViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$NewSosContactViewStateCopyWith<_NewSosContactViewState> get copyWith => __$NewSosContactViewStateCopyWithImpl<_NewSosContactViewState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _NewSosContactViewState&&(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 'NewSosContactViewState(isoCode: $isoCode, canSave: $canSave, isSubmitting: $isSubmitting, permissionBlocked: $permissionBlocked, phoneError: $phoneError)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$NewSosContactViewStateCopyWith<$Res> implements $NewSosContactViewStateCopyWith<$Res> {
|
||||
factory _$NewSosContactViewStateCopyWith(_NewSosContactViewState value, $Res Function(_NewSosContactViewState) _then) = __$NewSosContactViewStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String isoCode, bool canSave, bool isSubmitting, bool permissionBlocked, String? phoneError
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$NewSosContactViewStateCopyWithImpl<$Res>
|
||||
implements _$NewSosContactViewStateCopyWith<$Res> {
|
||||
__$NewSosContactViewStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _NewSosContactViewState _self;
|
||||
final $Res Function(_NewSosContactViewState) _then;
|
||||
|
||||
/// Create a copy of NewSosContactViewState
|
||||
/// 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(_NewSosContactViewState(
|
||||
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
|
||||
@@ -34,8 +34,10 @@ class SosContactsViewModel extends Notifier<SosContactsViewState> {
|
||||
final contacts = await _repository.getEmergencyContacts(
|
||||
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),
|
||||
@@ -57,6 +59,7 @@ class SosContactsViewModel extends Notifier<SosContactsViewState> {
|
||||
deviceId: device.id,
|
||||
contacts: updatedContacts,
|
||||
);
|
||||
if (!ref.mounted) return;
|
||||
|
||||
unawaited(
|
||||
_tracking.legacySettingsSosContactAdded(
|
||||
@@ -70,6 +73,7 @@ class SosContactsViewModel extends Notifier<SosContactsViewState> {
|
||||
successMessage: I18n.sosNumberAdded,
|
||||
);
|
||||
} catch (e) {
|
||||
if (!ref.mounted) return;
|
||||
state = state.copyWith(
|
||||
isSaving: false,
|
||||
errorMessage: formatErrorMessage(e),
|
||||
@@ -91,6 +95,7 @@ class SosContactsViewModel extends Notifier<SosContactsViewState> {
|
||||
deviceId: device.id,
|
||||
contacts: updatedContacts,
|
||||
);
|
||||
if (!ref.mounted) return;
|
||||
|
||||
unawaited(
|
||||
_tracking.legacySettingsSosContactRemoved(
|
||||
@@ -104,6 +109,7 @@ class SosContactsViewModel extends Notifier<SosContactsViewState> {
|
||||
successMessage: I18n.sosNumberRemoved,
|
||||
);
|
||||
} catch (e) {
|
||||
if (!ref.mounted) return;
|
||||
state = state.copyWith(
|
||||
isSaving: false,
|
||||
errorMessage: formatErrorMessage(e),
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
import 'package:utils/utils.dart';
|
||||
|
||||
import 'package:settings/src/core/domain/entities/contact_list_contact_entity.dart';
|
||||
|
||||
import '../state/sos_contacts_view_model.dart';
|
||||
import 'package:settings/src/core/presentation/widgets/contact_form_sheet.dart';
|
||||
import 'package:settings/src/features/sos_contacts/presentation/state/new_sos_contact_view_model.dart';
|
||||
|
||||
void showAddSosContactSheet(BuildContext context) {
|
||||
showModalBottomSheet<void>(
|
||||
@@ -18,221 +15,43 @@ void showAddSosContactSheet(BuildContext context) {
|
||||
);
|
||||
}
|
||||
|
||||
class _AddSosContactSheet extends ConsumerStatefulWidget {
|
||||
class _AddSosContactSheet extends ConsumerWidget {
|
||||
const _AddSosContactSheet();
|
||||
|
||||
@override
|
||||
ConsumerState<_AddSosContactSheet> createState() =>
|
||||
_AddSosContactSheetState();
|
||||
}
|
||||
|
||||
class _AddSosContactSheetState extends ConsumerState<_AddSosContactSheet> {
|
||||
final _nameController = TextEditingController();
|
||||
final _phoneController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_phoneController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool get _canSave =>
|
||||
_nameController.text.trim().isNotEmpty &&
|
||||
_phoneController.text.trim().isNotEmpty;
|
||||
|
||||
Future<void> _pickContact() async {
|
||||
final contact = await FlutterContacts.openExternalPick();
|
||||
if (contact == null || !mounted) return;
|
||||
|
||||
final fullContact = await FlutterContacts.getContact(
|
||||
contact.id,
|
||||
withProperties: true,
|
||||
);
|
||||
if (fullContact == null || fullContact.phones.isEmpty) return;
|
||||
|
||||
_phoneController.text = fullContact.phones.first.number;
|
||||
|
||||
if (_nameController.text.trim().isEmpty) {
|
||||
_nameController.text = fullContact.displayName;
|
||||
}
|
||||
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void _submit() {
|
||||
if (!_canSave) return;
|
||||
|
||||
final vm = ref.read(sosContactsViewModelProvider.notifier);
|
||||
vm.addContact(
|
||||
ContactListContactEntity(
|
||||
name: _nameController.text.trim(),
|
||||
phone: _phoneController.text.trim(),
|
||||
),
|
||||
);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
final primaryColor = theme.getColorFor(ThemeCode.legacyPrimary);
|
||||
final isSaving = ref.watch(
|
||||
sosContactsViewModelProvider.select((s) => s.isSaving),
|
||||
);
|
||||
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
|
||||
final vm = ref.read(newSosContactViewModelProvider.notifier);
|
||||
final state = ref.watch(newSosContactViewModelProvider);
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: bottomInset),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: SizeUtils.getByScreen(small: 22, big: 24),
|
||||
vertical: SizeUtils.getByScreen(small: 16, big: 18),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade300,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 12, big: 14)),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
context.translate(I18n.addSosContact),
|
||||
style: TextStyle(
|
||||
fontSize: SizeUtils.getByScreen(small: 20, big: 21),
|
||||
fontWeight: FontWeight.w600,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _canSave && !isSaving ? _submit : null,
|
||||
child: isSaving
|
||||
? SizedBox(
|
||||
width: SizeUtils.getByScreen(small: 20, big: 22),
|
||||
height: SizeUtils.getByScreen(small: 20, big: 22),
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: primaryColor,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
context.translate(I18n.save),
|
||||
style: TextStyle(
|
||||
color: _canSave ? primaryColor : Colors.grey,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: SizeUtils.getByScreen(
|
||||
small: 16,
|
||||
big: 17,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 18)),
|
||||
Text(
|
||||
context.translate(I18n.name),
|
||||
style: TextStyle(
|
||||
fontSize: SizeUtils.getByScreen(small: 15, big: 16),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _nameController,
|
||||
onChanged: (_) => setState(() {}),
|
||||
decoration: _inputDecoration(
|
||||
hintText: context.translate(I18n.contactName),
|
||||
primaryColor: primaryColor,
|
||||
),
|
||||
textCapitalization: TextCapitalization.words,
|
||||
),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 18)),
|
||||
Text(
|
||||
context.translate(I18n.phone),
|
||||
style: TextStyle(
|
||||
fontSize: SizeUtils.getByScreen(small: 15, big: 16),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _phoneController,
|
||||
onChanged: (_) => setState(() {}),
|
||||
readOnly: true,
|
||||
decoration: _inputDecoration(
|
||||
hintText: context.translate(I18n.phoneNumber),
|
||||
primaryColor: primaryColor,
|
||||
),
|
||||
keyboardType: TextInputType.phone,
|
||||
),
|
||||
),
|
||||
SizedBox(width: SizeUtils.getByScreen(small: 10, big: 8)),
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: primaryColor,
|
||||
),
|
||||
child: IconButton(
|
||||
onPressed: _pickContact,
|
||||
icon: Icon(
|
||||
SFIcons.contactsCircle,
|
||||
color: Colors.white,
|
||||
size: SizeUtils.getByScreen(small: 28, big: 26),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 18)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
ref.listen(
|
||||
newSosContactViewModelProvider.select((s) => s.permissionBlocked),
|
||||
(_, blocked) {
|
||||
if (blocked == true) {
|
||||
showContactsPermissionDialog(
|
||||
context,
|
||||
onOpenSettings: vm.openSystemSettings,
|
||||
);
|
||||
vm.clearPermissionBlocked();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
InputDecoration _inputDecoration({
|
||||
required String hintText,
|
||||
required Color primaryColor,
|
||||
}) {
|
||||
return InputDecoration(
|
||||
hintText: hintText,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
borderSide: BorderSide(color: primaryColor, width: 2),
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 14, vertical: 14),
|
||||
return ContactFormSheet(
|
||||
title: context.translate(I18n.addSosContact),
|
||||
primaryColor: theme.getColorFor(ThemeCode.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);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user