From 755e73d63a9d9bd0ea15f88dcda6da8e8009a5ec Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 16 Jan 2022 21:07:43 +0200 Subject: [PATCH] ios permissions --- lib/ui/auth/login_view.dart | 830 ++++++++++---------- lib/ui/client/edit/client_edit_details.dart | 25 +- lib/ui/vendor/edit/vendor_edit_details.dart | 25 +- 3 files changed, 442 insertions(+), 438 deletions(-) diff --git a/lib/ui/auth/login_view.dart b/lib/ui/auth/login_view.dart index 873b00dd3..64b22d13b 100644 --- a/lib/ui/auth/login_view.dart +++ b/lib/ui/auth/login_view.dart @@ -295,449 +295,451 @@ class _LoginState extends State { final double horizontalPadding = calculateLayout(context) == AppLayout.desktop ? 40 : 16; - return ScrollableListView( - children: [ - if (isDesktopOS()) - AppTitleBar() - else - Container( - width: double.infinity, - height: 24, - color: state.accentColor, - ), - Padding( - padding: EdgeInsets.symmetric(vertical: 25), - child: Center( - child: InkWell( - // TODO correct this - child: Image.asset( - state.prefState.enableDarkMode - ? 'assets/images/logo_dark.png' - : 'assets/images/logo_light.png', - height: 50), - onTap: isApple() - ? null - : () { - launch(kSiteUrl, - forceSafariVC: false, forceWebView: false); - }, - onLongPress: () { - if (kReleaseMode) { - return; - } + return SafeArea( + child: ScrollableListView( + children: [ + if (isDesktopOS()) + AppTitleBar() + else + Container( + width: double.infinity, + height: 24, + color: state.accentColor, + ), + Padding( + padding: EdgeInsets.symmetric(vertical: 25), + child: Center( + child: InkWell( + // TODO correct this + child: Image.asset( + state.prefState.enableDarkMode + ? 'assets/images/logo_dark.png' + : 'assets/images/logo_light.png', + height: 50), + onTap: isApple() + ? null + : () { + launch(kSiteUrl, + forceSafariVC: false, forceWebView: false); + }, + onLongPress: () { + if (kReleaseMode) { + return; + } - setState(() => _tokenLogin = !_tokenLogin); - }, + setState(() => _tokenLogin = !_tokenLogin); + }, + ), ), ), - ), - if (_tokenLogin) - FormCard( - forceNarrow: calculateLayout(context) != AppLayout.mobile, - children: [ - DecoratedFormField( - autofocus: true, - label: localization.token, - controller: _tokenController, - keyboardType: TextInputType.text, - ), - AppButton( - label: localization.submit.toUpperCase(), - onPressed: () { - final Completer completer = Completer(); - viewModel.onTokenLoginPressed(context, completer, - token: _tokenController.text); - }, - ) - ], - ), - AnimatedOpacity( - duration: Duration(milliseconds: 500), - opacity: viewModel.authState.isAuthenticated ? 0 : 1, - child: Form( - key: _formKey, - child: AutofillGroup( - child: FormCard( - elevation: 20, - forceNarrow: calculateLayout(context) != AppLayout.mobile, - internalPadding: const EdgeInsets.all(0), - children: [ - Column( - children: [ - if (!isApple() && - (!kIsWeb || !state.authState.isSelfHost)) - Row( - children: [ - Expanded( - child: Material( - color: _createAccount - ? state.accentColor - : Colors.transparent, - child: InkWell( - child: Center( - child: Padding( - padding: const EdgeInsets.all(16), - child: Text( - localization.signUp, - style: Theme.of(context) - .textTheme - .headline6 - .copyWith( - fontWeight: FontWeight.w600, - fontSize: 18, - color: _createAccount - ? Colors.white - : null), - ), - )), - onTap: () { - setState(() { - _createAccount = true; - _isSelfHosted = false; - _loginError = ''; - }); - }, + if (_tokenLogin) + FormCard( + forceNarrow: calculateLayout(context) != AppLayout.mobile, + children: [ + DecoratedFormField( + autofocus: true, + label: localization.token, + controller: _tokenController, + keyboardType: TextInputType.text, + ), + AppButton( + label: localization.submit.toUpperCase(), + onPressed: () { + final Completer completer = Completer(); + viewModel.onTokenLoginPressed(context, completer, + token: _tokenController.text); + }, + ) + ], + ), + AnimatedOpacity( + duration: Duration(milliseconds: 500), + opacity: viewModel.authState.isAuthenticated ? 0 : 1, + child: Form( + key: _formKey, + child: AutofillGroup( + child: FormCard( + elevation: 20, + forceNarrow: calculateLayout(context) != AppLayout.mobile, + internalPadding: const EdgeInsets.all(0), + children: [ + Column( + children: [ + if (!isApple() && + (!kIsWeb || !state.authState.isSelfHost)) + Row( + children: [ + Expanded( + child: Material( + color: _createAccount + ? state.accentColor + : Colors.transparent, + child: InkWell( + child: Center( + child: Padding( + padding: const EdgeInsets.all(16), + child: Text( + localization.signUp, + style: Theme.of(context) + .textTheme + .headline6 + .copyWith( + fontWeight: FontWeight.w600, + fontSize: 18, + color: _createAccount + ? Colors.white + : null), + ), + )), + onTap: () { + setState(() { + _createAccount = true; + _isSelfHosted = false; + _loginError = ''; + }); + }, + ), ), ), - ), - Expanded( - child: Material( - color: _createAccount - ? Colors.transparent - : state.accentColor, - child: InkWell( - child: Center( - child: Padding( - padding: const EdgeInsets.all(16), - child: Text( - localization.login, - style: Theme.of(context) - .textTheme - .headline6 - .copyWith( - fontWeight: FontWeight.w600, - fontSize: 18, - color: _createAccount - ? null - : Colors.white), - ), - )), - onTap: () { - setState(() { - _createAccount = false; - _loginError = ''; - }); - }, + Expanded( + child: Material( + color: _createAccount + ? Colors.transparent + : state.accentColor, + child: InkWell( + child: Center( + child: Padding( + padding: const EdgeInsets.all(16), + child: Text( + localization.login, + style: Theme.of(context) + .textTheme + .headline6 + .copyWith( + fontWeight: FontWeight.w600, + fontSize: 18, + color: _createAccount + ? null + : Colors.white), + ), + )), + onTap: () { + setState(() { + _createAccount = false; + _loginError = ''; + }); + }, + ), ), ), + ], + ), + SizedBox(height: 20), + if (!_recoverPassword) ...[ + if (!_createAccount && (!kIsWeb || !kReleaseMode)) ...[ + RuledText(localization.selectPlatform), + AppToggleButtons( + tabLabels: [ + localization.hosted, + localization.selfhosted, + ], + selectedIndex: _isSelfHosted ? 1 : 0, + onTabChanged: (index) { + setState(() { + _isSelfHosted = index == 1; + _loginError = ''; + if (index == 1) { + _emailLogin = true; + } + }); + }, ), ], - ), - SizedBox(height: 20), - if (!_recoverPassword) ...[ - if (!_createAccount && (!kIsWeb || !kReleaseMode)) ...[ - RuledText(localization.selectPlatform), - AppToggleButtons( - tabLabels: [ - localization.hosted, - localization.selfhosted, + if (!_isSelfHosted && !_hideGoogle) ...[ + RuledText(localization.selectMethod), + AppToggleButtons( + tabLabels: + calculateLayout(context) == AppLayout.mobile + ? [ + 'Google', + localization.email, + ] + : [ + _createAccount + ? localization.googleSignUp + : localization.googleSignIn, + _createAccount + ? localization.emailSignUp + : localization.emailSignIn, + ], + selectedIndex: _emailLogin ? 1 : 0, + onTabChanged: (index) { + setState(() { + _emailLogin = index == 1; + _loginError = ''; + }); + }, + ), + ], + ], + Padding( + padding: + EdgeInsets.symmetric(horizontal: horizontalPadding), + child: Column( + children: [ + if (_emailLogin) + DecoratedFormField( + controller: _emailController, + label: localization.email, + keyboardType: TextInputType.emailAddress, + autovalidate: _autoValidate, + validator: (val) => + val.isEmpty || val.trim().isEmpty + ? localization.pleaseEnterYourEmail + : null, + autofillHints: [AutofillHints.email], + autofocus: true, + onSavePressed: (_) => _submitForm(), + ), + if (_emailLogin && !_recoverPassword) + PasswordFormField( + controller: _passwordController, + autoValidate: false, + newPassword: _createAccount, + onSavePressed: (_) => _submitForm(), + ), + if (!_createAccount && !_recoverPassword) + DecoratedFormField( + controller: _oneTimePasswordController, + label: + '${localization.oneTimePassword} (${localization.optional})', + onSavePressed: (_) => _submitForm(), + keyboardType: TextInputType.number, + autofillHints: [AutofillHints.oneTimeCode], + ), + if (_isSelfHosted && !kIsWeb) + DecoratedFormField( + controller: _urlController, + label: localization.url, + validator: (val) => + val.isEmpty || val.trim().isEmpty + ? localization.pleaseEnterYourUrl + : null, + keyboardType: TextInputType.url, + onSavePressed: (_) => _submitForm(), + ), + if (_isSelfHosted && !_recoverPassword) + PasswordFormField( + labelText: + '${localization.secret} (${localization.optional})', + controller: _secretController, + autoValidate: _autoValidate, + validate: false, + onSavePressed: (_) => _submitForm(), + ), + if (_createAccount) + Padding( + padding: EdgeInsets.only(top: 10), + child: Column( + children: [ + CheckboxListTile( + onChanged: (value) => + setState(() => _termsChecked = value), + controlAffinity: + ListTileControlAffinity.leading, + activeColor: convertHexStringToColor( + kDefaultAccentColor), + value: _termsChecked, + title: RichText( + text: TextSpan( + children: [ + TextSpan( + style: aboutTextStyle, + text: localization.iAgreeToThe + + ' ', + ), + LinkTextSpan( + style: linkStyle, + url: kTermsOfServiceURL, + text: localization.termsOfService, + ), + ], + ), + ), + ), + CheckboxListTile( + onChanged: (value) => setState( + () => _privacyChecked = value), + controlAffinity: + ListTileControlAffinity.leading, + activeColor: convertHexStringToColor( + kDefaultAccentColor), + value: _privacyChecked, + title: RichText( + text: TextSpan( + children: [ + TextSpan( + style: aboutTextStyle, + text: localization.iAgreeToThe + + ' ', + ), + LinkTextSpan( + style: linkStyle, + url: kPrivacyPolicyURL, + text: localization.privacyPolicy, + ), + ], + ), + ), + ), + ], + ), + ), ], - selectedIndex: _isSelfHosted ? 1 : 0, - onTabChanged: (index) { - setState(() { - _isSelfHosted = index == 1; - _loginError = ''; - if (index == 1) { - _emailLogin = true; - } - }); - }, ), - ], - if (!_isSelfHosted && !_hideGoogle) ...[ - RuledText(localization.selectMethod), - AppToggleButtons( - tabLabels: - calculateLayout(context) == AppLayout.mobile - ? [ - 'Google', - localization.email, - ] - : [ - _createAccount - ? localization.googleSignUp - : localization.googleSignIn, - _createAccount - ? localization.emailSignUp - : localization.emailSignIn, - ], - selectedIndex: _emailLogin ? 1 : 0, - onTabChanged: (index) { - setState(() { - _emailLogin = index == 1; - _loginError = ''; - }); - }, - ), - ], + ), ], - Padding( - padding: - EdgeInsets.symmetric(horizontal: horizontalPadding), - child: Column( + ), + if (_loginError.isNotEmpty && + !_loginError.contains(OTP_ERROR)) + Container( + padding: EdgeInsets.only( + top: 20, + left: horizontalPadding, + right: horizontalPadding), + child: Row( + children: [ + Expanded( + child: SelectableText( + _loginError, + style: TextStyle( + color: Colors.red, + ), + ), + ), + IconButton( + icon: Icon(Icons.content_copy), + tooltip: localization.copyError, + onPressed: () { + Clipboard.setData( + ClipboardData(text: _loginError)); + }), + ], + ), + ), + Padding( + padding: EdgeInsets.only( + top: 20, bottom: 10, left: 16, right: 16), + child: RoundedLoadingButton( + height: 50, + borderRadius: 4, + width: 430, + controller: _buttonController, + color: state.accentColor, + onPressed: () => _submitForm(), + child: Row( + mainAxisSize: MainAxisSize.min, children: [ if (_emailLogin) - DecoratedFormField( - controller: _emailController, - label: localization.email, - keyboardType: TextInputType.emailAddress, - autovalidate: _autoValidate, - validator: (val) => - val.isEmpty || val.trim().isEmpty - ? localization.pleaseEnterYourEmail - : null, - autofillHints: [AutofillHints.email], - autofocus: true, - onSavePressed: (_) => _submitForm(), - ), - if (_emailLogin && !_recoverPassword) - PasswordFormField( - controller: _passwordController, - autoValidate: false, - newPassword: _createAccount, - onSavePressed: (_) => _submitForm(), - ), - if (!_createAccount && !_recoverPassword) - DecoratedFormField( - controller: _oneTimePasswordController, - label: - '${localization.oneTimePassword} (${localization.optional})', - onSavePressed: (_) => _submitForm(), - keyboardType: TextInputType.number, - autofillHints: [AutofillHints.oneTimeCode], - ), - if (_isSelfHosted && !kIsWeb) - DecoratedFormField( - controller: _urlController, - label: localization.url, - validator: (val) => - val.isEmpty || val.trim().isEmpty - ? localization.pleaseEnterYourUrl - : null, - keyboardType: TextInputType.url, - onSavePressed: (_) => _submitForm(), - ), - if (_isSelfHosted && !_recoverPassword) - PasswordFormField( - labelText: - '${localization.secret} (${localization.optional})', - controller: _secretController, - autoValidate: _autoValidate, - validate: false, - onSavePressed: (_) => _submitForm(), - ), - if (_createAccount) - Padding( - padding: EdgeInsets.only(top: 10), - child: Column( - children: [ - CheckboxListTile( - onChanged: (value) => - setState(() => _termsChecked = value), - controlAffinity: - ListTileControlAffinity.leading, - activeColor: convertHexStringToColor( - kDefaultAccentColor), - value: _termsChecked, - title: RichText( - text: TextSpan( - children: [ - TextSpan( - style: aboutTextStyle, - text: localization.iAgreeToThe + - ' ', - ), - LinkTextSpan( - style: linkStyle, - url: kTermsOfServiceURL, - text: localization.termsOfService, - ), - ], - ), - ), - ), - CheckboxListTile( - onChanged: (value) => setState( - () => _privacyChecked = value), - controlAffinity: - ListTileControlAffinity.leading, - activeColor: convertHexStringToColor( - kDefaultAccentColor), - value: _privacyChecked, - title: RichText( - text: TextSpan( - children: [ - TextSpan( - style: aboutTextStyle, - text: localization.iAgreeToThe + - ' ', - ), - LinkTextSpan( - style: linkStyle, - url: kPrivacyPolicyURL, - text: localization.privacyPolicy, - ), - ], - ), - ), - ), - ], - ), + Icon(Icons.mail, color: Colors.white) + else + ClipOval( + child: Image.asset( + 'assets/images/google_logo.png', + width: 30, + height: 30), ), + SizedBox(width: 10), + Text( + _recoverPassword + ? localization.recoverPassword + : _createAccount + ? (_emailLogin + ? localization.emailSignUp + : localization.googleSignUp) + : (_emailLogin + ? localization.emailSignIn + : localization.googleSignIn), + style: TextStyle(fontSize: 18, color: Colors.white), + ) ], ), ), - ], - ), - if (_loginError.isNotEmpty && - !_loginError.contains(OTP_ERROR)) - Container( - padding: EdgeInsets.only( - top: 20, - left: horizontalPadding, - right: horizontalPadding), - child: Row( - children: [ - Expanded( - child: SelectableText( - _loginError, - style: TextStyle( - color: Colors.red, + ), + SizedBox(height: 4), + Flex( + direction: calculateLayout(context) == AppLayout.desktop + ? Axis.horizontal + : Axis.vertical, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (!_createAccount && _emailLogin) + InkWell( + onTap: () { + setState(() { + _recoverPassword = !_recoverPassword; + }); + }, + child: Padding( + padding: const EdgeInsets.all(14), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (!_recoverPassword) + Icon(MdiIcons.lock, size: 16), + SizedBox(width: 8), + Text(_recoverPassword + ? localization.cancel + : localization.recoverPassword), + ]), + ), + ), + if (!_recoverPassword && !_isSelfHosted) + InkWell( + onTap: () { + launch(kStatusCheckUrl); + }, + child: Padding( + padding: const EdgeInsets.all(14), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.security, size: 16), + SizedBox(width: 8), + Text(localization.checkStatus) + ], ), ), ), - IconButton( - icon: Icon(Icons.content_copy), - tooltip: localization.copyError, - onPressed: () { - Clipboard.setData( - ClipboardData(text: _loginError)); - }), - ], - ), - ), - Padding( - padding: EdgeInsets.only( - top: 20, bottom: 10, left: 16, right: 16), - child: RoundedLoadingButton( - height: 50, - borderRadius: 4, - width: 430, - controller: _buttonController, - color: state.accentColor, - onPressed: () => _submitForm(), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (_emailLogin) - Icon(Icons.mail, color: Colors.white) - else - ClipOval( - child: Image.asset( - 'assets/images/google_logo.png', - width: 30, - height: 30), - ), - SizedBox(width: 10), - Text( - _recoverPassword - ? localization.recoverPassword - : _createAccount - ? (_emailLogin - ? localization.emailSignUp - : localization.googleSignUp) - : (_emailLogin - ? localization.emailSignIn - : localization.googleSignIn), - style: TextStyle(fontSize: 18, color: Colors.white), - ) - ], - ), - ), - ), - SizedBox(height: 4), - Flex( - direction: calculateLayout(context) == AppLayout.desktop - ? Axis.horizontal - : Axis.vertical, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - if (!_createAccount && _emailLogin) - InkWell( - onTap: () { - setState(() { - _recoverPassword = !_recoverPassword; - }); - }, - child: Padding( - padding: const EdgeInsets.all(14), - child: Row( + if (!_recoverPassword && kIsWeb) + InkWell( + onTap: () => launch(getNativeAppUrl(platform)), + child: Padding( + padding: const EdgeInsets.all(14), + child: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (!_recoverPassword) - Icon(MdiIcons.lock, size: 16), + children: [ + Icon(getNativeAppIcon(platform), size: 16), SizedBox(width: 8), - Text(_recoverPassword - ? localization.cancel - : localization.recoverPassword), - ]), - ), - ), - if (!_recoverPassword && !_isSelfHosted) - InkWell( - onTap: () { - launch(kStatusCheckUrl); - }, - child: Padding( - padding: const EdgeInsets.all(14), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.security, size: 16), - SizedBox(width: 8), - Text(localization.checkStatus) - ], + Text('$platform ${localization.app}') + ], + ), ), ), - ), - if (!_recoverPassword && kIsWeb) - InkWell( - onTap: () => launch(getNativeAppUrl(platform)), - child: Padding( - padding: const EdgeInsets.all(14), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(getNativeAppIcon(platform), size: 16), - SizedBox(width: 8), - Text('$platform ${localization.app}') - ], - ), - ), - ), - ], - ), - SizedBox(height: 16), - ], + ], + ), + SizedBox(height: 16), + ], + ), ), ), ), - ), - ], + ], + ), ); } } diff --git a/lib/ui/client/edit/client_edit_details.dart b/lib/ui/client/edit/client_edit_details.dart index 5dff5caff..2b3c9138a 100644 --- a/lib/ui/client/edit/client_edit_details.dart +++ b/lib/ui/client/edit/client_edit_details.dart @@ -11,6 +11,7 @@ import 'package:contacts_service/contacts_service.dart'; // Project imports: import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/data/models/entities.dart'; +import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/redux/static/static_selectors.dart'; import 'package:invoiceninja_flutter/ui/app/form_card.dart'; import 'package:invoiceninja_flutter/ui/app/forms/custom_field.dart'; @@ -131,24 +132,24 @@ class ClientEditDetailsState extends State { String countryId; countryMap.keys.forEach((countryId) { - final country = countryMap[countryId]; - if (country.name.toLowerCase() == contactAddress.country.toLowerCase()) { + final country = countryMap[countryId] ?? CountryEntity(); + if (country.name.toLowerCase() == contactAddress?.country?.toLowerCase()) { countryId = country.id; } }); widget.viewModel.onChanged(client.rebuild((b) => b - ..name = contact.company ?? '' - ..address1 = contactAddress.street ?? '' - ..city = contactAddress.city ?? '' - ..state = contactAddress.region ?? '' - ..postalCode = contactAddress.postcode ?? '' - ..countryId = countryId + ..name = (contact?.company ?? '').trim() + ..address1 = (contactAddress?.street ?? '').trim() + ..city = (contactAddress?.city ?? '').trim() + ..state = (contactAddress?.region ?? '').trim() + ..postalCode = (contactAddress?.postcode ?? '').trim() + ..countryId = countryId ?? '' ..contacts[0] = client.contacts[0].rebuild((b) => b - ..firstName = contact.givenName ?? '' - ..lastName = contact.familyName ?? '' - ..email = contactEmail.value ?? '' - ..phone = contactPhone.value ?? '') + ..firstName = (contact?.givenName ?? '').trim() + ..lastName = (contact?.familyName ?? '').trim() + ..email = (contactEmail?.value ?? '').trim() + ..phone = (contactPhone?.value ?? '').trim()) ..updatedAt = DateTime.now().millisecondsSinceEpoch)); } diff --git a/lib/ui/vendor/edit/vendor_edit_details.dart b/lib/ui/vendor/edit/vendor_edit_details.dart index 5c0f4c360..697b4eea5 100644 --- a/lib/ui/vendor/edit/vendor_edit_details.dart +++ b/lib/ui/vendor/edit/vendor_edit_details.dart @@ -11,6 +11,7 @@ import 'package:contacts_service/contacts_service.dart'; // Project imports: import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/data/models/entities.dart'; +import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/ui/app/form_card.dart'; import 'package:invoiceninja_flutter/ui/app/forms/custom_field.dart'; import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart'; @@ -129,24 +130,24 @@ class VendorEditDetailsState extends State { String countryId; countryMap.keys.forEach((countryId) { - final country = countryMap[countryId]; - if (country.name.toLowerCase() == contactAddress.country.toLowerCase()) { + final country = countryMap[countryId] ?? CountryEntity(); + if (country.name.toLowerCase() == contactAddress?.country?.toLowerCase()) { countryId = country.id; } }); widget.viewModel.onChanged(vendor.rebuild((b) => b - ..name = contact.company ?? '' - ..address1 = contactAddress.street ?? '' - ..city = contactAddress.city ?? '' - ..state = contactAddress.region ?? '' - ..postalCode = contactAddress.postcode ?? '' - ..countryId = countryId + ..name = (contact?.company ?? '').trim() + ..address1 = (contactAddress?.street ?? '').trim() + ..city = (contactAddress?.city ?? '').trim() + ..state = (contactAddress?.region ?? '').trim() + ..postalCode = (contactAddress?.postcode ?? '').trim() + ..countryId = countryId ?? '' ..contacts[0] = vendor.contacts[0].rebuild((b) => b - ..firstName = contact.givenName ?? '' - ..lastName = contact.familyName ?? '' - ..email = contactEmail.value ?? '' - ..phone = contactPhone.value ?? '') + ..firstName = (contact?.givenName ?? '').trim() + ..lastName = (contact?.familyName ?? '').trim() + ..email = (contactEmail?.value ?? '').trim() + ..phone = (contactPhone?.value ?? '').trim()) ..updatedAt = DateTime.now().millisecondsSinceEpoch)); }