From 7957efdaf036e4fe4efec2936e57d238135edd70 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Fri, 21 Jan 2022 09:41:58 +0200 Subject: [PATCH 1/9] Added ProvidePasswordToProtectMsgFragment. Added WebPortalPasswordStrengthViewModel. Refactored code.| #1618 --- .../WebPortalPasswordStrengthViewModel.kt | 64 ++++++++ .../fragment/CreateMessageFragment.kt | 44 ++++-- .../ProvidePasswordToProtectMsgFragment.kt | 145 ++++++++++++++++++ ...ovidePasswordToProtectMsgDialogFragment.kt | 78 ---------- .../ui/widget/PgpContactsNachoTextView.kt | 8 +- .../SingleCharacterSpanChipTokenizer.kt | 64 -------- ...agment_provide_password_to_protect_msg.xml | 70 +++++++-- .../main/res/navigation/create_msg_graph.xml | 12 +- FlowCrypt/src/main/res/values-ru/strings.xml | 7 + FlowCrypt/src/main/res/values/strings.xml | 7 + FlowCrypt/src/main/res/values/styles.xml | 10 +- 11 files changed, 330 insertions(+), 179 deletions(-) create mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/WebPortalPasswordStrengthViewModel.kt create mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/ProvidePasswordToProtectMsgFragment.kt delete mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/ProvidePasswordToProtectMsgDialogFragment.kt delete mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/ui/widget/SingleCharacterSpanChipTokenizer.kt diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/WebPortalPasswordStrengthViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/WebPortalPasswordStrengthViewModel.kt new file mode 100644 index 0000000000..8c806e406f --- /dev/null +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/WebPortalPasswordStrengthViewModel.kt @@ -0,0 +1,64 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.jetpack.viewmodel + +import android.app.Application +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.flowcrypt.email.util.coroutines.runners.ControlledRunner +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.util.* + +/** + * This [ViewModel] implementation can be used to check the web portal strength + * + * @author Denis Bondarenko + * Date: 1/21/22 + * Time: 08:15 AM + * E-mail: DenBond7@gmail.com + */ +class WebPortalPasswordStrengthViewModel(application: Application) : + BaseAndroidViewModel(application) { + private val controlledRunnerForChecking = ControlledRunner>() + + private val pwdStrengthResultMutableStateFlow: MutableStateFlow> = + MutableStateFlow(emptyList()) + val pwdStrengthResultStateFlow: StateFlow> = + pwdStrengthResultMutableStateFlow.asStateFlow() + + fun check(password: CharSequence) { + viewModelScope.launch { + pwdStrengthResultMutableStateFlow.value = controlledRunnerForChecking.cancelPreviousThenRun { + return@cancelPreviousThenRun checkInternal(password) + } + } + } + + private suspend fun checkInternal(password: CharSequence): List = + withContext(Dispatchers.Default) { + return@withContext Requirement.values().map { + RequirementItem(it, it.regex.containsMatchIn(password)) + } + } + + data class RequirementItem( + val requirement: Requirement, + val isMatching: Boolean + ) + + enum class Requirement constructor(val regex: Regex) { + ONE_UPPERCASE("\\p{Lu}".toRegex(RegexOption.MULTILINE)), + ONE_LOWERCASE("\\p{Ll}".toRegex(RegexOption.MULTILINE)), + ONE_NUMBER("\\d".toRegex(RegexOption.MULTILINE)), + ONE_SPECIAL_CHARACTER("[&\"#-'_%-/@,;:!*()]".toRegex(RegexOption.MULTILINE)), + MIN_LENGTH(".{8,}".toRegex(RegexOption.MULTILINE)); + } +} 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 4d213b28f6..824ad6c882 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 @@ -12,7 +12,6 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle -import android.text.SpannableStringBuilder import android.text.format.Formatter import android.util.Log import android.view.ContextMenu @@ -69,6 +68,7 @@ import com.flowcrypt.email.extensions.showKeyboard import com.flowcrypt.email.extensions.showNeedPassphraseDialog import com.flowcrypt.email.extensions.toast import com.flowcrypt.email.extensions.visible +import com.flowcrypt.email.extensions.visibleOrGone import com.flowcrypt.email.jetpack.viewmodel.AccountAliasesViewModel import com.flowcrypt.email.jetpack.viewmodel.ComposeMsgViewModel import com.flowcrypt.email.jetpack.viewmodel.RecipientsViewModel @@ -82,13 +82,11 @@ import com.flowcrypt.email.ui.activity.fragment.base.BaseSyncFragment import com.flowcrypt.email.ui.activity.fragment.dialog.ChoosePublicKeyDialogFragment import com.flowcrypt.email.ui.activity.fragment.dialog.FixNeedPassphraseIssueDialogFragment import com.flowcrypt.email.ui.activity.fragment.dialog.NoPgpFoundDialogFragment -import com.flowcrypt.email.ui.activity.fragment.dialog.ProvidePasswordToProtectMsgDialogFragment import com.flowcrypt.email.ui.adapter.FromAddressesAdapter import com.flowcrypt.email.ui.adapter.RecipientAdapter import com.flowcrypt.email.ui.widget.CustomChipSpanChipCreator import com.flowcrypt.email.ui.widget.PGPContactChipSpan import com.flowcrypt.email.ui.widget.PgpContactsNachoTextView -import com.flowcrypt.email.ui.widget.SingleCharacterSpanChipTokenizer import com.flowcrypt.email.util.FileAndDirectoryUtils import com.flowcrypt.email.util.GeneralUtil import com.flowcrypt.email.util.UIUtil @@ -98,6 +96,7 @@ import com.google.android.material.snackbar.Snackbar import com.hootsuite.nachos.NachoTextView import com.hootsuite.nachos.chip.Chip import com.hootsuite.nachos.terminator.ChipTerminatorHandler +import com.hootsuite.nachos.tokenizer.SpanChipTokenizer import com.hootsuite.nachos.validator.ChipifyingNachoValidator import org.apache.commons.io.FileUtils import org.bouncycastle.openpgp.PGPSecretKeyRing @@ -168,7 +167,6 @@ class CreateMessageFragment : BaseSyncFragment(), View.OnFocusChangeListener, override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) - setupComposeMsgViewModel() subscribeToSetWebPortalPassword() initExtras(activity?.intent) } @@ -185,6 +183,7 @@ class CreateMessageFragment : BaseSyncFragment(), View.OnFocusChangeListener, updateActionBarTitle() initNonEncryptedHintView() initViews() + setupComposeMsgViewModel() setupAccountAliasesViewModel() setupPrivateKeysViewModel() setupRecipientsViewModel() @@ -882,9 +881,7 @@ class CreateMessageFragment : BaseSyncFragment(), View.OnFocusChangeListener, list: List? ) { view ?: return - val builder = SpannableStringBuilder(view.text) - - val pgpContactChipSpans = builder.getSpans(0, view.length(), PGPContactChipSpan::class.java) + val pgpContactChipSpans = view.text.getSpans(0, view.length(), PGPContactChipSpan::class.java) if (pgpContactChipSpans.isNotEmpty()) { for (recipientWithPubKeys in list ?: emptyList()) { @@ -917,7 +914,7 @@ class CreateMessageFragment : BaseSyncFragment(), View.OnFocusChangeListener, ' ', ChipTerminatorHandler .BEHAVIOR_CHIPIFY_TO_TERMINATOR ) - pgpContactsNachoTextView?.chipTokenizer = SingleCharacterSpanChipTokenizer( + pgpContactsNachoTextView?.chipTokenizer = SpanChipTokenizer( requireContext(), CustomChipSpanChipCreator(requireContext()), PGPContactChipSpan::class.java ) @@ -986,7 +983,7 @@ class CreateMessageFragment : BaseSyncFragment(), View.OnFocusChangeListener, binding?.btnSetWebPortalPassword?.setOnClickListener { navController?.navigate( CreateMessageFragmentDirections - .actionCreateMessageFragmentToProvidePasswordToProtectMsgDialogFragment( + .actionCreateMessageFragmentToProvidePasswordToProtectMsgFragment( composeMsgViewModel.webPortalPasswordStateFlow.value.toString() ) ) @@ -1557,7 +1554,6 @@ class CreateMessageFragment : BaseSyncFragment(), View.OnFocusChangeListener, progressBar?.invisible() it.data?.let { list -> composeMsgViewModel.replaceRecipients(recipientType, list) - updateChips(nachoTextView, list) } baseActivity.countingIdlingResource.decrementSafely() } @@ -1645,17 +1641,35 @@ class CreateMessageFragment : BaseSyncFragment(), View.OnFocusChangeListener, val hasRecipientsWithoutPgp = recipients.any { recipient -> !recipient.recipientWithPubKeys.hasAtLeastOnePubKey() } if (hasRecipientsWithoutPgp) { - if (binding?.btnSetWebPortalPassword?.visibility == View.GONE) { - composeMsgViewModel.setWebPortalPassword() - } binding?.btnSetWebPortalPassword?.visible() } else { binding?.btnSetWebPortalPassword?.gone() + composeMsgViewModel.setWebPortalPassword() } } } } + lifecycleScope.launchWhenStarted { + composeMsgViewModel.recipientsToStateFlow.collect { recipients -> + updateChips(binding?.editTextRecipientTo, recipients.map { it.recipientWithPubKeys }) + } + } + + lifecycleScope.launchWhenStarted { + composeMsgViewModel.recipientsCcStateFlow.collect { recipients -> + binding?.layoutCc?.visibleOrGone(recipients.isNotEmpty()) + updateChips(binding?.editTextRecipientCc, recipients.map { it.recipientWithPubKeys }) + } + } + + lifecycleScope.launchWhenStarted { + composeMsgViewModel.recipientsBccStateFlow.collect { recipients -> + binding?.layoutBcc?.visibleOrGone(recipients.isNotEmpty()) + updateChips(binding?.editTextRecipientBcc, recipients.map { it.recipientWithPubKeys }) + } + } + lifecycleScope.launchWhenStarted { composeMsgViewModel.webPortalPasswordStateFlow.collect { webPortalPassword -> if (isPasswordProtectedFunctionalityEnabled()) { @@ -1829,10 +1843,10 @@ class CreateMessageFragment : BaseSyncFragment(), View.OnFocusChangeListener, private fun subscribeToSetWebPortalPassword() { setFragmentResultListener( - ProvidePasswordToProtectMsgDialogFragment.REQUEST_KEY_PASSWORD + ProvidePasswordToProtectMsgFragment.REQUEST_KEY_PASSWORD ) { _, bundle -> val password = - bundle.getCharSequence(ProvidePasswordToProtectMsgDialogFragment.KEY_PASSWORD) ?: "" + bundle.getCharSequence(ProvidePasswordToProtectMsgFragment.KEY_PASSWORD) ?: "" composeMsgViewModel.setWebPortalPassword(password) } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/ProvidePasswordToProtectMsgFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/ProvidePasswordToProtectMsgFragment.kt new file mode 100644 index 0000000000..daadd95679 --- /dev/null +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/ProvidePasswordToProtectMsgFragment.kt @@ -0,0 +1,145 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.ui.activity.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import android.widget.CheckedTextView +import androidx.core.graphics.BlendModeColorFilterCompat +import androidx.core.graphics.BlendModeCompat +import androidx.core.os.bundleOf +import androidx.core.widget.addTextChangedListener +import androidx.fragment.app.setFragmentResult +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.navArgs +import com.flowcrypt.email.R +import com.flowcrypt.email.databinding.FragmentProvidePasswordToProtectMsgBinding +import com.flowcrypt.email.extensions.hideKeyboard +import com.flowcrypt.email.extensions.navController +import com.flowcrypt.email.extensions.toast +import com.flowcrypt.email.jetpack.viewmodel.WebPortalPasswordStrengthViewModel +import com.flowcrypt.email.ui.activity.fragment.base.BaseFragment +import com.flowcrypt.email.util.UIUtil + +class ProvidePasswordToProtectMsgFragment : BaseFragment() { + private var binding: FragmentProvidePasswordToProtectMsgBinding? = null + private val args by navArgs() + private val webPortalPasswordStrengthViewModel: WebPortalPasswordStrengthViewModel by viewModels() + + override val contentResourceId: Int = R.layout.fragment_provide_password_to_protect_msg + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + binding = FragmentProvidePasswordToProtectMsgBinding.inflate(inflater, container, false) + return binding?.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + supportActionBar?.setTitle(R.string.web_portal_password) + initViews() + initWebPortalPasswordStrengthViewModel() + } + + private fun initViews() { + binding?.eTPassphrase?.addTextChangedListener { editable -> + webPortalPasswordStrengthViewModel.check(editable.toString()) + } + + binding?.eTPassphrase?.setOnEditorActionListener { v, actionId, _ -> + return@setOnEditorActionListener when (actionId) { + EditorInfo.IME_ACTION_DONE -> { + checkAndMoveOn() + v.hideKeyboard() + true + } + else -> false + } + } + + binding?.btSetPassphrase?.setOnClickListener { + checkAndMoveOn() + } + + binding?.eTPassphrase?.setText(args.defaultPassword) + } + + private fun checkAndMoveOn() { + if (binding?.eTPassphrase?.text?.isEmpty() == true) { + toast(getString(R.string.password_cannot_be_empty)) + } else { + navController?.navigateUp() + setFragmentResult( + REQUEST_KEY_PASSWORD, + bundleOf(KEY_PASSWORD to (binding?.eTPassphrase?.text ?: "")) + ) + } + } + + private fun initWebPortalPasswordStrengthViewModel() { + lifecycleScope.launchWhenStarted { + webPortalPasswordStrengthViewModel.pwdStrengthResultStateFlow.collect { + updateStrengthViews(it) + } + } + } + + private fun updateStrengthViews(list: List) { + list.forEach { + when (it.requirement) { + WebPortalPasswordStrengthViewModel.Requirement.ONE_LOWERCASE -> { + updateRequirementItem(binding?.checkedTVOneUppercase, it) + } + WebPortalPasswordStrengthViewModel.Requirement.ONE_UPPERCASE -> { + updateRequirementItem(binding?.checkedTVOneLowercase, it) + } + WebPortalPasswordStrengthViewModel.Requirement.ONE_NUMBER -> { + updateRequirementItem(binding?.checkedTVOneNumber, it) + } + WebPortalPasswordStrengthViewModel.Requirement.ONE_SPECIAL_CHARACTER -> { + updateRequirementItem(binding?.checkedTVOneSpecialCharacter, it) + } + WebPortalPasswordStrengthViewModel.Requirement.MIN_LENGTH -> { + updateRequirementItem(binding?.checkedTVMinLength, it) + } + } + } + + binding?.btSetPassphrase?.apply { + isEnabled = list.all { it.isMatching } + background?.colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat( + UIUtil.getColor( + requireContext(), if (isEnabled) R.color.colorPrimary else R.color.silver + ), BlendModeCompat.MODULATE + ) + } + } + + private fun updateRequirementItem( + view: CheckedTextView?, + it: WebPortalPasswordStrengthViewModel.RequirementItem + ) { + view?.apply { + isChecked = it.isMatching + setTextColor( + UIUtil.getColor( + requireContext(), + if (it.isMatching) R.color.colorPrimary else R.color.orange + ) + ) + } + } + + companion object { + const val REQUEST_KEY_PASSWORD = "REQUEST_KEY_PASSWORD" + const val KEY_PASSWORD = "KEY_PASSWORD" + } +} diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/ProvidePasswordToProtectMsgDialogFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/ProvidePasswordToProtectMsgDialogFragment.kt deleted file mode 100644 index bbe3ddd5fd..0000000000 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/ProvidePasswordToProtectMsgDialogFragment.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com - * Contributors: DenBond7 - */ - -package com.flowcrypt.email.ui.activity.fragment.dialog - -import android.app.AlertDialog -import android.app.Dialog -import android.os.Bundle -import android.view.LayoutInflater -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo -import androidx.core.os.bundleOf -import androidx.core.widget.addTextChangedListener -import androidx.fragment.app.setFragmentResult -import androidx.navigation.fragment.navArgs -import com.flowcrypt.email.R -import com.flowcrypt.email.databinding.FragmentProvidePasswordToProtectMsgBinding -import com.flowcrypt.email.extensions.hideKeyboard -import com.flowcrypt.email.extensions.navController -import com.flowcrypt.email.extensions.toast - -class ProvidePasswordToProtectMsgDialogFragment : BaseDialogFragment() { - private var binding: FragmentProvidePasswordToProtectMsgBinding? = null - private val args by navArgs() - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val builder = AlertDialog.Builder(activity) - binding = FragmentProvidePasswordToProtectMsgBinding.inflate( - LayoutInflater.from(requireContext()), - if ((view != null) and (view is ViewGroup)) view as ViewGroup? else null, - false - ) - - binding?.eTPassphrase?.addTextChangedListener { editable -> - //check typed password will be added a bit later(soon) - } - - binding?.eTPassphrase?.setOnEditorActionListener { v, actionId, _ -> - return@setOnEditorActionListener when (actionId) { - EditorInfo.IME_ACTION_DONE -> { - checkAndMoveOn() - v.hideKeyboard() - true - } - else -> false - } - } - - binding?.btSetPassphrase?.setOnClickListener { - checkAndMoveOn() - } - - binding?.eTPassphrase?.setText(args.defaultPassword) - - builder.setView(binding?.root) - builder.setTitle(null) - return builder.create() - } - - private fun checkAndMoveOn() { - if (binding?.eTPassphrase?.text?.isEmpty() == true) { - toast(getString(R.string.password_cannot_be_empty)) - } else { - navController?.navigateUp() - setFragmentResult( - REQUEST_KEY_PASSWORD, - bundleOf(KEY_PASSWORD to (binding?.eTPassphrase?.text ?: "")) - ) - } - } - - companion object { - const val REQUEST_KEY_PASSWORD = "REQUEST_KEY_PASSWORD" - const val KEY_PASSWORD = "KEY_PASSWORD" - } -} diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/widget/PgpContactsNachoTextView.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/widget/PgpContactsNachoTextView.kt index 8285775952..d00d7de5c8 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/widget/PgpContactsNachoTextView.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/widget/PgpContactsNachoTextView.kt @@ -148,12 +148,12 @@ class PgpContactsNachoTextView(context: Context, attrs: AttributeSet) : if (chipTokenizer != null) { val stringBuilder = StringBuilder() - val chips = Arrays.asList(*chipTokenizer!!.findAllChips(start, end, editable)) + val chips = listOf(*chipTokenizer!!.findAllChips(start, end, editable)) for (i in chips.indices) { val chip = chips[i] stringBuilder.append(chip.text) if (i != chips.size - 1) { - stringBuilder.append(SingleCharacterSpanChipTokenizer.CHIP_SEPARATOR_WHITESPACE) + stringBuilder.append(CHIP_SEPARATOR_WHITESPACE) } } @@ -290,4 +290,8 @@ class PgpContactsNachoTextView(context: Context, attrs: AttributeSet) : return layout.getLineForOffset(offset) } } + + companion object { + const val CHIP_SEPARATOR_WHITESPACE = ' ' + } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/widget/SingleCharacterSpanChipTokenizer.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/widget/SingleCharacterSpanChipTokenizer.kt deleted file mode 100644 index 91a2bbbe88..0000000000 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/widget/SingleCharacterSpanChipTokenizer.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com - * Contributors: DenBond7 - */ - -package com.flowcrypt.email.ui.widget - -import android.content.Context - -import com.hootsuite.nachos.NachoTextView -import com.hootsuite.nachos.chip.Chip -import com.hootsuite.nachos.chip.ChipCreator -import com.hootsuite.nachos.tokenizer.SpanChipTokenizer - -/** - * Define a custom chip separator in [NachoTextView] - * - * @author DenBond7 - * Date: 19.05.2017 - * Time: 14:14 - * E-mail: DenBond7@gmail.com - */ -class SingleCharacterSpanChipTokenizer -@JvmOverloads constructor( - context: Context, - chipCreator: ChipCreator, - chipClass: Class, - private val symbol: Char = CHIP_SEPARATOR_WHITESPACE -) : SpanChipTokenizer( - context, - chipCreator, chipClass -) { - override fun findTokenStart(text: CharSequence, cursor: Int): Int { - var i = cursor - - while (i > 0 && text[i - 1] != symbol) { - i-- - } - while (i < cursor && text[i] == symbol) { - i++ - } - - return i - } - - override fun findTokenEnd(text: CharSequence, cursor: Int): Int { - var i = cursor - val len = text.length - - while (i < len) { - if (text[i] == symbol) { - return i - } else { - i++ - } - } - - return len - } - - companion object { - const val CHIP_SEPARATOR_WHITESPACE = ' ' - } -} diff --git a/FlowCrypt/src/main/res/layout/fragment_provide_password_to_protect_msg.xml b/FlowCrypt/src/main/res/layout/fragment_provide_password_to_protect_msg.xml index d517013183..bedbb31139 100644 --- a/FlowCrypt/src/main/res/layout/fragment_provide_password_to_protect_msg.xml +++ b/FlowCrypt/src/main/res/layout/fragment_provide_password_to_protect_msg.xml @@ -4,7 +4,6 @@ --> @@ -51,27 +50,72 @@ android:layout_height="@dimen/default_margin_content_small" android:progress="0" /> - - + + + + + + + + + + + +