From bc8015faaabec222d983fcd4021ddf77968382c1 Mon Sep 17 00:00:00 2001 From: Den Date: Tue, 9 Apr 2024 21:11:16 +0300 Subject: [PATCH] Handled the case "no private key available for account(create a new key for email) (#2675) * Handled the case "no private key available for account(create a new key for email)".| #597 * Fixed navigation.| #597 * Added ComposeScreenNoKeyAvailableCreateNewKeyFlowTest.| #597 * Refactored code * Refactored code * wip --- ...creenNoKeyAvailableCreateNewKeyFlowTest.kt | 83 ++++++++++++++ ...ngleKeyWithPassphraseInDatabaseFlowTest.kt | 65 ++++------- .../BaseComposeScreenNoKeyAvailableTest.kt | 16 ++- ...yptedWithAttachmentsComposeGmailApiFlow.kt | 2 + .../email/extensions/LifecycleOwnerExt.kt | 6 +- .../email/ui/activity/BaseActivity.kt | 5 + .../ui/activity/CreateMessageActivity.kt | 16 +++ .../email/ui/activity/MainActivity.kt | 7 +- .../fragment/CreateMessageFragment.kt | 102 +++++++++++++++++- ...teOrImportPrivateKeyDuringSetupFragment.kt | 9 +- .../fragment/dialog/ActionsDialogFragment.kt | 7 +- .../main/res/drawable/ic_create_new_key.xml | 22 ++++ .../res/navigation/actions_dialog_graph.xml | 5 + .../main/res/navigation/create_msg_graph.xml | 1 + .../create_new_private_key_graph.xml | 58 ++++++++++ .../src/main/res/navigation/nav_graph.xml | 50 +-------- FlowCrypt/src/main/res/values-ru/strings.xml | 1 + FlowCrypt/src/main/res/values-uk/strings.xml | 1 + FlowCrypt/src/main/res/values/strings.xml | 1 + 19 files changed, 339 insertions(+), 118 deletions(-) create mode 100644 FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/ComposeScreenNoKeyAvailableCreateNewKeyFlowTest.kt create mode 100644 FlowCrypt/src/main/res/drawable/ic_create_new_key.xml create mode 100644 FlowCrypt/src/main/res/navigation/create_new_private_key_graph.xml diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/ComposeScreenNoKeyAvailableCreateNewKeyFlowTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/ComposeScreenNoKeyAvailableCreateNewKeyFlowTest.kt new file mode 100644 index 0000000000..262e912c92 --- /dev/null +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/ComposeScreenNoKeyAvailableCreateNewKeyFlowTest.kt @@ -0,0 +1,83 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.ui + +import android.view.KeyEvent +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.clearText +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.pressImeActionButton +import androidx.test.espresso.action.ViewActions.pressKey +import androidx.test.espresso.action.ViewActions.replaceText +import androidx.test.espresso.action.ViewActions.typeText +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.MediumTest +import com.flowcrypt.email.R +import com.flowcrypt.email.TestConstants +import com.flowcrypt.email.database.entity.KeyEntity +import com.flowcrypt.email.junit.annotations.FlowCryptTestSettings +import com.flowcrypt.email.rules.AddPrivateKeyToDatabaseRule +import com.flowcrypt.email.rules.ClearAppSettingsRule +import com.flowcrypt.email.rules.GrantPermissionRuleChooser +import com.flowcrypt.email.rules.RetryRule +import com.flowcrypt.email.rules.ScreenshotTestRule +import com.flowcrypt.email.ui.base.BaseComposeScreenNoKeyAvailableTest +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +/** + * @author Denys Bondarenko + */ +@MediumTest +@RunWith(AndroidJUnit4::class) +@FlowCryptTestSettings(useCommonIdling = false) +class ComposeScreenNoKeyAvailableCreateNewKeyFlowTest : BaseComposeScreenNoKeyAvailableTest() { + private val addPrivateKeyToDatabaseRule = AddPrivateKeyToDatabaseRule( + keyPath = "pgp/key_testing@flowcrypt.test_keyA_strong.asc", + passphraseType = KeyEntity.PassphraseType.RAM + ) + + @get:Rule + var ruleChain: TestRule = RuleChain + .outerRule(RetryRule.DEFAULT) + .around(ClearAppSettingsRule()) + .around(GrantPermissionRuleChooser.grant(android.Manifest.permission.POST_NOTIFICATIONS)) + .around(addAccountToDatabaseRule) + .around(addPrivateKeyToDatabaseRule) + .around(activeActivityRule) + .around(ScreenshotTestRule()) + + @Test + fun testCreatingNewKey() { + doBaseActions { + onView(withText(R.string.create_a_new_key)) + .check(matches(isDisplayed())) + .perform(click()) + + Thread.sleep(1000) + + onView(withId(R.id.editTextKeyPassword)) + .perform(click(), typeText(TestConstants.DEFAULT_STRONG_PASSWORD)) + Thread.sleep(1000) + onView(withId(R.id.editTextKeyPassword)) + .perform(pressImeActionButton()) + + Thread.sleep(1000) + + onView(withId(R.id.editTextKeyPasswordSecond)) + .perform(replaceText(TestConstants.DEFAULT_STRONG_PASSWORD)) + onView(withId(R.id.buttonConfirmPassPhrases)) + .perform(click()) + } + } +} diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/ComposeScreenNoKeyAvailableSingleKeyWithPassphraseInDatabaseFlowTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/ComposeScreenNoKeyAvailableSingleKeyWithPassphraseInDatabaseFlowTest.kt index d084afe6e8..e83cbc483d 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/ComposeScreenNoKeyAvailableSingleKeyWithPassphraseInDatabaseFlowTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/ComposeScreenNoKeyAvailableSingleKeyWithPassphraseInDatabaseFlowTest.kt @@ -11,7 +11,6 @@ import androidx.test.espresso.action.ViewActions.closeSoftKeyboard import androidx.test.espresso.action.ViewActions.replaceText import androidx.test.espresso.action.ViewActions.scrollTo import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.matcher.ViewMatchers.hasTextColor import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText @@ -19,7 +18,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import com.flowcrypt.email.R import com.flowcrypt.email.TestConstants -import com.flowcrypt.email.extensions.kotlin.asInternetAddress import com.flowcrypt.email.junit.annotations.FlowCryptTestSettings import com.flowcrypt.email.rules.AddPrivateKeyToDatabaseRule import com.flowcrypt.email.rules.ClearAppSettingsRule @@ -28,7 +26,6 @@ import com.flowcrypt.email.rules.RetryRule import com.flowcrypt.email.rules.ScreenshotTestRule import com.flowcrypt.email.ui.base.BaseComposeScreenNoKeyAvailableTest import com.flowcrypt.email.util.PrivateKeysManager -import org.hamcrest.CoreMatchers.not import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain @@ -61,52 +58,26 @@ class ComposeScreenNoKeyAvailableSingleKeyWithPassphraseInDatabaseFlowTest : Bas @Test fun testImportKey() { - activeActivityRule?.launch(intent) - registerAllIdlingResources() - fillInAllFields( - to = setOf( - requireNotNull(TestConstants.RECIPIENT_WITH_PUBLIC_KEY_ON_ATTESTER.asInternetAddress()) - ) - ) + doBaseActions { + addTextToClipboard("private key", requireNotNull(pgpKeyDetails.privateKey)) + onView(withText(R.string.import_private_key)) + .check(matches(isDisplayed())) + .perform(click()) + Thread.sleep(1000) + onView(withText(R.string.load_from_clipboard)) + .check(matches(isDisplayed())) + .perform(click()) - //check that editTextFrom has gray text color. It means a sender doesn't have a private key - onView(withId(R.id.editTextFrom)) - .check(matches(isDisplayed())) - .check(matches(hasTextColor(R.color.gray))) + onView(withId(R.id.editTextKeyPassword)) + .perform( + replaceText(TestConstants.DEFAULT_STRONG_PASSWORD), + closeSoftKeyboard() + ) + onView(withId(R.id.buttonPositiveAction)) + .perform(scrollTo(), click()) - onView(withId(R.id.menuActionSend)) - .check(matches(isDisplayed())) - .perform(click()) - - isDialogWithTextDisplayed( - decorView, - getResString(R.string.no_key_available, addAccountToDatabaseRule.account.email) - ) - - addTextToClipboard("private key", requireNotNull(pgpKeyDetails.privateKey)) - onView(withText(R.string.import_private_key)) - .check(matches(isDisplayed())) - .perform(click()) - - Thread.sleep(1000) - onView(withText(R.string.load_from_clipboard)) - .check(matches(isDisplayed())) - .perform(click()) - - onView(withId(R.id.editTextKeyPassword)) - .perform( - replaceText(TestConstants.DEFAULT_STRONG_PASSWORD), - closeSoftKeyboard() - ) - onView(withId(R.id.buttonPositiveAction)) - .perform(scrollTo(), click()) - - waitForObjectWithText(addAccountToDatabaseRule.account.email, 5000) - - //check that editTextFrom doesn't have gray text color. It means a sender has a private key. - onView(withId(R.id.editTextFrom)) - .check(matches(isDisplayed())) - .check(matches(not(hasTextColor(R.color.gray)))) + waitForObjectWithText(addAccountToDatabaseRule.account.email, 5000) + } } @Test diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/base/BaseComposeScreenNoKeyAvailableTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/base/BaseComposeScreenNoKeyAvailableTest.kt index fe36703ce1..13b01b963e 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/base/BaseComposeScreenNoKeyAvailableTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/base/BaseComposeScreenNoKeyAvailableTest.kt @@ -30,6 +30,16 @@ import java.net.HttpURLConnection abstract class BaseComposeScreenNoKeyAvailableTest : BaseComposeScreenTest() { protected fun doTestAddEmailToExistingKey(action: () -> Unit) { + doBaseActions { + onView(withText(R.string.add_email_to_existing_key)) + .check(matches(isDisplayed())) + .perform(click()) + + action.invoke() + } + } + + protected fun doBaseActions(action: () -> Unit) { activeActivityRule?.launch(intent) registerAllIdlingResources() fillInAllFields( @@ -52,13 +62,9 @@ abstract class BaseComposeScreenNoKeyAvailableTest : BaseComposeScreenTest() { getResString(R.string.no_key_available, addAccountToDatabaseRule.account.email) ) - onView(withText(R.string.add_email_to_existing_key)) - .check(matches(isDisplayed())) - .perform(click()) - action.invoke() - Thread.sleep(2000) + waitForObjectWithText(getResString(R.string.compose), 5000) //check that editTextFrom doesn't have gray text color. It means a sender has a private key. onView(withId(R.id.editTextFrom)) diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/EncryptedWithAttachmentsComposeGmailApiFlow.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/EncryptedWithAttachmentsComposeGmailApiFlow.kt index 410e43f116..f9d95f5466 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/EncryptedWithAttachmentsComposeGmailApiFlow.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/EncryptedWithAttachmentsComposeGmailApiFlow.kt @@ -32,6 +32,7 @@ import okhttp3.mockwebserver.Dispatcher import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.RecordedRequest import org.junit.Assert.assertEquals +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain @@ -48,6 +49,7 @@ import org.junit.runner.RunWith message = BaseComposeScreenTest.MESSAGE, subject = BaseComposeScreenTest.SUBJECT ) +@Ignore("Should be fixed before the next release") class EncryptedWithAttachmentsComposeGmailApiFlow : BaseComposeGmailFlow() { override val mockWebServerRule = FlowCryptMockWebServerRule(TestConstants.MOCK_WEB_SERVER_PORT, object : Dispatcher() { diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/LifecycleOwnerExt.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/LifecycleOwnerExt.kt index 460cfe0df7..910b287a4a 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/LifecycleOwnerExt.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/LifecycleOwnerExt.kt @@ -168,7 +168,8 @@ fun LifecycleOwner.showActionDialogFragment( requestKey: String, dialogTitle: String? = null, isCancelable: Boolean = true, - items: List + items: List, + bundle: Bundle? = null ) { showDialogFragment(navController) { return@showDialogFragment object : NavDirections { @@ -177,7 +178,8 @@ fun LifecycleOwner.showActionDialogFragment( requestKey = requestKey, dialogTitle = dialogTitle, isCancelable = isCancelable, - items = items.toTypedArray() + items = items.toTypedArray(), + bundle = bundle ).toBundle() } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/BaseActivity.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/BaseActivity.kt index 4fc825dda1..a26987b5d5 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/BaseActivity.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/BaseActivity.kt @@ -14,6 +14,7 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.ViewGroup import android.view.WindowManager +import androidx.activity.OnBackPressedCallback import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar @@ -51,6 +52,8 @@ abstract class BaseActivity : AppCompatActivity() { protected abstract fun inflateBinding(inflater: LayoutInflater): T protected abstract fun initAppBarConfiguration(): AppBarConfiguration + protected abstract val onBackPressedCallback: OnBackPressedCallback? + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //https://github.com/FlowCrypt/flowcrypt-android/issues/2442 @@ -83,6 +86,8 @@ abstract class BaseActivity : AppCompatActivity() { } }) + onBackPressedCallback?.let { onBackPressedDispatcher.addCallback(this, it) } + initViews() setupNavigation() initAccountViewModel() diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/CreateMessageActivity.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/CreateMessageActivity.kt index e4959d12b1..8288b7e86f 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/CreateMessageActivity.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/CreateMessageActivity.kt @@ -10,6 +10,7 @@ import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.widget.Toast +import androidx.activity.OnBackPressedCallback import androidx.navigation.NavHostController import androidx.navigation.ui.AppBarConfiguration import com.flowcrypt.email.R @@ -48,6 +49,21 @@ class CreateMessageActivity : BaseActivity(), }) } + override val onBackPressedCallback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (navController.currentDestination?.id == R.id.createMessageFragment) { + onBackPressed() + } else { + navController.navigateUp() + } + } + + private fun onBackPressed() { + isEnabled = false + onBackPressedDispatcher.onBackPressed() + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) (navController as? NavHostController)?.enableOnBackPressed(true) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/MainActivity.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/MainActivity.kt index 95af464ecf..3b51643324 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/MainActivity.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/MainActivity.kt @@ -107,7 +107,7 @@ class MainActivity : BaseActivity() { override fun onServiceDisconnected(arg0: ComponentName) {} } - private val onBackPressedCallback = object : OnBackPressedCallback(true) { + override val onBackPressedCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) { binding.drawerLayout.closeDrawer(GravityCompat.START) @@ -153,7 +153,6 @@ class MainActivity : BaseActivity() { } } super.onCreate(savedInstanceState) - handleOnBackPressed() observeMovingToBackground() client = GoogleSignIn.getClient(this, GoogleApiClientHelper.generateGoogleSignInOptions()) @@ -438,10 +437,6 @@ class MainActivity : BaseActivity() { fragment?.onDrawerStateChanged(slideOffset, isOpened) } - private fun handleOnBackPressed() { - onBackPressedDispatcher.addCallback(this, onBackPressedCallback) - } - private fun observeMovingToBackground() { ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver { /** diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragment.kt index 0ebc557dc6..2e773d1b3c 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CreateMessageFragment.kt @@ -72,6 +72,7 @@ import com.flowcrypt.email.extensions.showActionDialogFragment import com.flowcrypt.email.extensions.showChoosePublicKeyDialogFragment import com.flowcrypt.email.extensions.showDialogFragment import com.flowcrypt.email.extensions.showInfoDialog +import com.flowcrypt.email.extensions.showInfoDialogWithExceptionDetails import com.flowcrypt.email.extensions.showKeyboard import com.flowcrypt.email.extensions.showNeedPassphraseDialog import com.flowcrypt.email.extensions.supportActionBar @@ -82,6 +83,7 @@ import com.flowcrypt.email.jetpack.lifecycle.CustomAndroidViewModelFactory import com.flowcrypt.email.jetpack.viewmodel.AccountAliasesViewModel import com.flowcrypt.email.jetpack.viewmodel.ComposeMsgViewModel import com.flowcrypt.email.jetpack.viewmodel.DraftViewModel +import com.flowcrypt.email.jetpack.viewmodel.PrivateKeysViewModel import com.flowcrypt.email.jetpack.viewmodel.RecipientsAutoCompleteViewModel import com.flowcrypt.email.jetpack.viewmodel.RecipientsViewModel import com.flowcrypt.email.model.DialogItem @@ -111,6 +113,7 @@ import com.flowcrypt.email.util.UIUtil import com.flowcrypt.email.util.exception.DecryptionException import com.flowcrypt.email.util.exception.ExceptionUtil import com.flowcrypt.email.util.exception.NoKeyAvailableException +import com.flowcrypt.email.util.exception.SavePrivateKeyToDatabaseException import com.google.android.flexbox.FlexDirection import com.google.android.flexbox.FlexboxLayoutManager import com.google.android.flexbox.JustifyContent @@ -154,6 +157,7 @@ class CreateMessageFragment : BaseFragment(), } } } + protected val privateKeysViewModel: PrivateKeysViewModel by viewModels() private val draftViewModel: DraftViewModel by viewModels { object : CustomAndroidViewModelFactory(requireActivity().application) { @@ -330,6 +334,7 @@ class CreateMessageFragment : BaseFragment(), setupRecipientsAutoCompleteViewModel() setupAccountAliasesViewModel() setupPrivateKeysViewModel() + observePrivateKeysChanges() subscribeToSetWebPortalPassword() subscribeToSelectRecipients() @@ -342,6 +347,7 @@ class CreateMessageFragment : BaseFragment(), subscribeToChoosePrivateKeysDialogFragment() subscribeToAddNewUserIdToPrivateKeyDialogFragment() subscribeToCreateOutgoingMessageDialogFragment() + subscribeToCreatePrivateKey() val isEncryptedMode = composeMsgViewModel.msgEncryptionType === MessageEncryptionType.ENCRYPTED @@ -1123,7 +1129,7 @@ class CreateMessageFragment : BaseFragment(), } } - private fun setupPrivateKeysViewModel() { + private fun observePrivateKeysChanges() { KeysStorageImpl.getInstance(requireContext()).secretKeyRingsLiveData .observe(viewLifecycleOwner) { updateFromAddressAdapter(it) @@ -1389,7 +1395,7 @@ class CreateMessageFragment : BaseFragment(), btnName = getString(R.string.fix), duration = Snackbar.LENGTH_LONG ) { - fixNoKeyAvailableIssue(text) + fixNoKeyAvailableIssue(text, email) } } } @@ -1467,7 +1473,7 @@ class CreateMessageFragment : BaseFragment(), binding?.spinnerFrom?.selectedItemPosition ?: Spinner.INVALID_POSITION ) ?: "" val text = getString(R.string.no_key_available, email) - fixNoKeyAvailableIssue(text) + fixNoKeyAvailableIssue(text, email) return false } @@ -1691,6 +1697,10 @@ class CreateMessageFragment : BaseFragment(), ActionsDialogFragment.KEY_REQUEST_RESULT ) ?: return@setFragmentResultListener + val email = bundle.getBundle( + ActionsDialogFragment.KEY_REQUEST_INCOMING_BUNDLE + )?.getString(EMAIL) ?: binding?.editTextFrom?.text.toString() + when (item.id) { RESULT_CODE_IMPORT_PRIVATE_KEY -> { account?.let { accountEntity -> @@ -1719,6 +1729,23 @@ class CreateMessageFragment : BaseFragment(), } } } + + RESULT_CODE_CREATE_NEW_PRIVATE_KEY -> { + val existingAccount = account + if (existingAccount != null) { + navController?.navigate( + object : NavDirections { + override val actionId = R.id.create_new_private_key_graph + override val arguments = CreatePrivateKeyFirstFragmentArgs( + requestKey = REQUEST_KEY_CREATE_NEW_KEY, + accountEntity = existingAccount.copy(email = email) + ).toBundle() + } + ) + } else { + toast(R.string.unknown_error) + } + } } } } @@ -1826,6 +1853,15 @@ class CreateMessageFragment : BaseFragment(), } } + private fun subscribeToCreatePrivateKey() { + setFragmentResultListener(REQUEST_KEY_CREATE_NEW_KEY) { _, bundle -> + val pgpKeyRingDetails = bundle.getParcelableViaExt( + CreatePrivateKeyFirstFragment.KEY_CREATED_KEY + ) ?: return@setFragmentResultListener + importCreatedPrivateKey(listOf(pgpKeyRingDetails)) + } + } + private fun setupRecipientsAutoCompleteViewModel() { launchAndRepeatWithViewLifecycle { recipientsAutoCompleteViewModel.autoCompleteResultStateFlow.collect { @@ -1896,6 +1932,42 @@ class CreateMessageFragment : BaseFragment(), } } + private fun setupPrivateKeysViewModel() { + privateKeysViewModel.savePrivateKeysLiveData.observe(viewLifecycleOwner) { + it?.let { + when (it.status) { + Result.Status.LOADING -> { + toast(R.string.processing) + } + + Result.Status.SUCCESS -> { + it.data?.let { pair -> + if (pair.second.isNotEmpty()) { + toast(R.string.created_key_was_imported) + } + } + } + + Result.Status.ERROR, Result.Status.EXCEPTION -> { + val exception = it.exception + if (exception is SavePrivateKeyToDatabaseException) { + showSnackbar( + msgText = exception.message ?: exception.javaClass.simpleName, + btnName = getString(R.string.retry), + duration = Snackbar.LENGTH_INDEFINITE, + onClickListener = { importCreatedPrivateKey(exception.keys) } + ) + } else { + showInfoDialogWithExceptionDetails(it.exception, it.exceptionMsg) + } + } + + else -> {} + } + } + } + } + private fun tryToSendMessage() { snackBar?.dismiss() @@ -1933,7 +2005,7 @@ class CreateMessageFragment : BaseFragment(), } } - private fun fixNoKeyAvailableIssue(text: String) { + private fun fixNoKeyAvailableIssue(text: String, email: String) { if (account?.clientConfiguration?.usesKeyManager() == true) { toast(getString(R.string.no_prv_keys_ask_admin)) } else { @@ -1952,8 +2024,14 @@ class CreateMessageFragment : BaseFragment(), iconResourceId = R.drawable.ic_edit_key_add_user_id, title = getString(R.string.add_email_to_existing_key), id = RESULT_CODE_ADD_USER_ID_TO_EXISTING_PRIVATE_KEY + ), + DialogItem( + iconResourceId = R.drawable.ic_create_new_key, + title = getString(R.string.create_a_new_key), + id = RESULT_CODE_CREATE_NEW_PRIVATE_KEY ) - ) + ), + bundle = Bundle().apply { putString(EMAIL, email) } ) } } @@ -1979,9 +2057,18 @@ class CreateMessageFragment : BaseFragment(), } } + private fun importCreatedPrivateKey(keys: List) { + privateKeysViewModel.encryptAndSaveKeysToDatabase( + accountEntity = account, + keys = keys + ) + } + companion object { private val TAG = CreateMessageFragment::class.java.simpleName + private const val EMAIL = "email" + private val REQUEST_KEY_CHOOSE_PUBLIC_KEY = GeneralUtil.generateUniqueExtraKey( "REQUEST_KEY_CHOOSE_PUBLIC_KEY", CreateMessageFragment::class.java @@ -2035,8 +2122,13 @@ class CreateMessageFragment : BaseFragment(), CreateMessageFragment::class.java ) + private val REQUEST_KEY_CREATE_NEW_KEY = GeneralUtil.generateUniqueExtraKey( + "REQUEST_KEY_CREATE_NEW_KEY", CreateMessageFragment::class.java + ) + private const val RESULT_CODE_IMPORT_PRIVATE_KEY = 1 private const val RESULT_CODE_ADD_USER_ID_TO_EXISTING_PRIVATE_KEY = 2 + private const val RESULT_CODE_CREATE_NEW_PRIVATE_KEY = 3 private const val REQUEST_CODE_FIX_MISSING_PASSPHRASE_FOR_PRIVATE_KEY_BY_SENDER_EMAIL = 1 private const val REQUEST_CODE_FIX_MISSING_PASSPHRASE_FOR_PRIVATE_KEY_BY_FINGERPRINT = 2 diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CreateOrImportPrivateKeyDuringSetupFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CreateOrImportPrivateKeyDuringSetupFragment.kt index 68a282c765..39a0ffc906 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CreateOrImportPrivateKeyDuringSetupFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/CreateOrImportPrivateKeyDuringSetupFragment.kt @@ -13,6 +13,7 @@ import androidx.annotation.IntDef import androidx.core.os.bundleOf import androidx.fragment.app.setFragmentResult import androidx.fragment.app.setFragmentResultListener +import androidx.navigation.NavDirections import androidx.navigation.fragment.navArgs import com.flowcrypt.email.R import com.flowcrypt.email.api.retrofit.response.model.ClientConfiguration @@ -68,11 +69,13 @@ class CreateOrImportPrivateKeyDuringSetupFragment : } else { binding?.buttonCreateNewKey?.setOnClickListener { navController?.navigate( - CreateOrImportPrivateKeyDuringSetupFragmentDirections - .actionCreateOrImportPrivateKeyDuringSetupFragmentToCreatePrivateKeyFirstFragment( + object : NavDirections { + override val actionId = R.id.create_new_private_key_graph + override val arguments = CreatePrivateKeyFirstFragmentArgs( requestKey = REQUEST_KEY_CREATE_KEY, accountEntity = args.accountEntity - ) + ).toBundle() + } ) } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/ActionsDialogFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/ActionsDialogFragment.kt index 0d64f6963a..5b8d8377ca 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/ActionsDialogFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/ActionsDialogFragment.kt @@ -40,7 +40,8 @@ class ActionsDialogFragment : BaseDialogFragment() { setFragmentResult( args.requestKey, bundleOf( - KEY_REQUEST_RESULT to args.items[which] + KEY_REQUEST_RESULT to args.items[which], + KEY_REQUEST_INCOMING_BUNDLE to args.bundle ) ) } @@ -51,5 +52,9 @@ class ActionsDialogFragment : BaseDialogFragment() { val KEY_REQUEST_RESULT = GeneralUtil.generateUniqueExtraKey( "KEY_REQUEST_RESULT", ActionsDialogFragment::class.java ) + + val KEY_REQUEST_INCOMING_BUNDLE = GeneralUtil.generateUniqueExtraKey( + "KEY_REQUEST_INCOMING_BUNDLE", ActionsDialogFragment::class.java + ) } } \ No newline at end of file diff --git a/FlowCrypt/src/main/res/drawable/ic_create_new_key.xml b/FlowCrypt/src/main/res/drawable/ic_create_new_key.xml new file mode 100644 index 0000000000..02a02b4801 --- /dev/null +++ b/FlowCrypt/src/main/res/drawable/ic_create_new_key.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/FlowCrypt/src/main/res/navigation/actions_dialog_graph.xml b/FlowCrypt/src/main/res/navigation/actions_dialog_graph.xml index 83427de220..f3143ae806 100644 --- a/FlowCrypt/src/main/res/navigation/actions_dialog_graph.xml +++ b/FlowCrypt/src/main/res/navigation/actions_dialog_graph.xml @@ -26,5 +26,10 @@ + diff --git a/FlowCrypt/src/main/res/navigation/create_msg_graph.xml b/FlowCrypt/src/main/res/navigation/create_msg_graph.xml index d80020e8be..0ed4f540d1 100644 --- a/FlowCrypt/src/main/res/navigation/create_msg_graph.xml +++ b/FlowCrypt/src/main/res/navigation/create_msg_graph.xml @@ -22,6 +22,7 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FlowCrypt/src/main/res/navigation/nav_graph.xml b/FlowCrypt/src/main/res/navigation/nav_graph.xml index 89d83e2537..a2bb7eed38 100644 --- a/FlowCrypt/src/main/res/navigation/nav_graph.xml +++ b/FlowCrypt/src/main/res/navigation/nav_graph.xml @@ -19,6 +19,7 @@ + - - - - - - - - - - - - - - - - - - - Предварительный просмотр недоступен Добавить адрес электронной почты к существующему ключу Пожалуйста, выберите ключ, который вы хотите изменить + Созданный ключ был импортирован Для Вашей защиты и безопасности данных в настоящее время осталось только %1$d попытка, после чего следует 5-минутный период восстановления. Для Вашей защиты и безопасности данных в настоящее время осталось только %1$d попытки, после чего следует 5-минутный период восстановления. diff --git a/FlowCrypt/src/main/res/values-uk/strings.xml b/FlowCrypt/src/main/res/values-uk/strings.xml index f8915039d5..458b5397aa 100644 --- a/FlowCrypt/src/main/res/values-uk/strings.xml +++ b/FlowCrypt/src/main/res/values-uk/strings.xml @@ -613,6 +613,7 @@ Попередній перегляд недоступний Додати електронну адресу до існуючого ключа Будь ласка, виберіть ключ, який ви хочете змінити + Створений ключ було імпортовано Для Вашого захисту та безпеки даних наразі залишилася лише %1$d спроба, а потім 5-хвилинний період відновлення. Для Вашого захисту та безпеки даних наразі залишилася лише %1$d спроби, а потім 5-хвилинний період відновлення. diff --git a/FlowCrypt/src/main/res/values/strings.xml b/FlowCrypt/src/main/res/values/strings.xml index 7711959a81..4bb98cc099 100644 --- a/FlowCrypt/src/main/res/values/strings.xml +++ b/FlowCrypt/src/main/res/values/strings.xml @@ -634,4 +634,5 @@ The preview is not available Add email to existing key Please choose a key you would like to modify + The created key was imported