diff --git a/lib/ui/app/menu_drawer.dart b/lib/ui/app/menu_drawer.dart index 06d6ce0c1..f47dae352 100644 --- a/lib/ui/app/menu_drawer.dart +++ b/lib/ui/app/menu_drawer.dart @@ -421,6 +421,48 @@ class _MenuDrawerState extends State { }, ), ) + else if (state.user.isTwoFactorEnabled && + !state.user.phoneVerified && + state.isHosted) + if (state.isMenuCollapsed) + Tooltip( + message: + localization.verifyPhoneNumber2faHelp, + child: ListTile( + contentPadding: + const EdgeInsets.only(left: 12), + leading: IconButton( + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) => + UserSmsVerification( + showChangeNumber: true, + ), + ); + }, + icon: Icon(Icons.warning, + color: Colors.orange), + ), + ), + ) + else + Material( + child: ListTile( + tileColor: Colors.orange.shade800, + subtitle: Text( + localization.verifyPhoneNumber2faHelp, + style: TextStyle(color: Colors.white), + ), + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) => + UserSmsVerification(), + ); + }, + ), + ) else if (state.company.isDisabled && state.userCompany.isAdmin) if (state.isMenuCollapsed) diff --git a/lib/ui/app/sms_verification.dart b/lib/ui/app/sms_verification.dart index e9d990d3c..0d4e77e2a 100644 --- a/lib/ui/app/sms_verification.dart +++ b/lib/ui/app/sms_verification.dart @@ -9,6 +9,7 @@ import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/data/web_client.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; +import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart'; import 'package:invoiceninja_flutter/ui/app/forms/app_form.dart'; import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart'; import 'package:invoiceninja_flutter/ui/app/pinput.dart'; @@ -188,7 +189,12 @@ class _AccountSmsVerificationState extends State { } class UserSmsVerification extends StatefulWidget { - const UserSmsVerification({Key key}) : super(key: key); + const UserSmsVerification({ + Key key, + this.showChangeNumber = false, + }) : super(key: key); + + final bool showChangeNumber; @override State createState() => _UserSmsVerificationState(); @@ -268,7 +274,7 @@ class _UserSmsVerificationState extends State { _isLoading = false; }); if (navigator.canPop()) { - navigator.pop(); + navigator.pop(true); } showToast(localization.verifiedPhoneNumber); store.dispatch(RefreshData()); @@ -289,6 +295,8 @@ class _UserSmsVerificationState extends State { @override Widget build(BuildContext context) { final localization = AppLocalization.of(context); + final store = StoreProvider.of(context); + final state = store.state; return AlertDialog( title: Text(localization.verifyPhoneNumber), @@ -301,7 +309,8 @@ class _UserSmsVerificationState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Text(localization.codeWasSent), + Text(localization.codeWasSentTo + .replaceFirst(':number', state.user.phone)), SizedBox(height: 20), AppPinput( onCompleted: (code) => _code = code, @@ -317,6 +326,15 @@ class _UserSmsVerificationState extends State { ), ), if (!_isLoading) ...[ + TextButton( + onPressed: () { + store.dispatch(ViewSettings(section: kSettingsUserDetails)); + Navigator.of(context).pop(); + }, + child: Text( + localization.changeNumber.toUpperCase(), + ), + ), TextButton( onPressed: () => _sendCode(), child: Text( diff --git a/lib/ui/settings/user_details.dart b/lib/ui/settings/user_details.dart index f7ca26cae..799d09867 100644 --- a/lib/ui/settings/user_details.dart +++ b/lib/ui/settings/user_details.dart @@ -384,7 +384,7 @@ class _UserDetailsState extends State .toUpperCase(), textAlign: TextAlign.center, ), - onPressed: () { + onPressed: () async { if (state.settingsUIState.isChanged) { showMessageDialog( context: context, @@ -406,11 +406,19 @@ class _UserDetailsState extends State if (!kReleaseMode && (state.isHosted && !state.user.phoneVerified)) { - showDialog( + final bool phoneVerified = await showDialog( context: context, builder: (BuildContext context) => UserSmsVerification(), ); + + if (phoneVerified) { + showDialog( + context: context, + builder: (BuildContext context) => + _EnableTwoFactor(state: viewModel.state), + ); + } } else { showDialog( context: context, diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index 34f7eb691..b08f2aa04 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -16,6 +16,7 @@ mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { // STARTER: lang key - do not remove comment + 'change_number': 'Change Number', 'resend_code': 'Resend Code', 'base_type': 'Base Type', 'category_type': 'Category Type', @@ -118,7 +119,8 @@ mixin LocalizationsProvider on LocaleCodeAware { 'expense_tax_rates': 'Expense Tax Rates', 'invoice_item_tax_rates': 'Invoice Item Tax Rates', 'verified_phone_number': 'Successfully verified phone number', - 'code_was_sent': 'A code has been sent via SMS', + 'code_was_sent': 'A code has been sent via SMS', + 'code_was_sent_to': 'A code has been sent via SMS to :number', 'resend': 'Resend', 'verify': 'Verify', 'enter_phone_number': 'Please provide a phone number', @@ -126,6 +128,8 @@ mixin LocalizationsProvider on LocaleCodeAware { 'verify_phone_number': 'Verify Phone Number', 'verify_phone_number_help': 'Please verify your phone number to send emails', + 'verify_phone_number_2fa_help': + 'Please verify your phone number for 2FA backup', 'merged_clients': 'Successfully merged clients', 'merge_into': 'Merge Into', 'merge': 'Merge', @@ -87471,6 +87475,19 @@ mixin LocalizationsProvider on LocaleCodeAware { _localizedValues[localeCode]['resend_code'] ?? _localizedValues['en']['resend_code']; + String get verifyPhoneNumber2faHelp => + _localizedValues[localeCode]['verify_phone_number_2fa_help'] ?? + _localizedValues['en']['verify_phone_number_2fa_help']; + + String get codeWasSentTo => + _localizedValues[localeCode]['code_was_sent_to'] ?? + _localizedValues['en']['code_was_sent_to']; + +String get changeNumber => + _localizedValues[localeCode]['change_number'] ?? + _localizedValues['en']['change_number']; + + // STARTER: lang field - do not remove comment String lookup(String key) {