From 35c28c5ad2a164201778543647b3bb1dda9459c2 Mon Sep 17 00:00:00 2001 From: shamim-emon Date: Wed, 11 Dec 2024 09:48:54 +0600 Subject: [PATCH] Initial focus in message compose screen --- .../main/java/com/fsck/k9/UiKoinModules.kt | 2 + .../com/fsck/k9/activity/MessageCompose.java | 169 +++++------------- .../k9/activity/compose/RecipientMvpView.kt | 3 + .../k9/activity/compose/RecipientPresenter.kt | 4 + .../fsck/k9/ui/compose/IntentDataMapper.kt | 76 ++++++++ .../java/com/fsck/k9/ui/compose/KoinModule.kt | 7 + 6 files changed, 141 insertions(+), 120 deletions(-) create mode 100644 legacy/ui/legacy/src/main/java/com/fsck/k9/ui/compose/IntentDataMapper.kt create mode 100644 legacy/ui/legacy/src/main/java/com/fsck/k9/ui/compose/KoinModule.kt diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/UiKoinModules.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/UiKoinModules.kt index e0737e7e18d..a7ea4c4fa20 100644 --- a/legacy/ui/legacy/src/main/java/com/fsck/k9/UiKoinModules.kt +++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/UiKoinModules.kt @@ -10,6 +10,7 @@ import com.fsck.k9.ui.account.accountUiModule import com.fsck.k9.ui.base.uiBaseModule import com.fsck.k9.ui.changelog.changelogUiModule import com.fsck.k9.ui.choosefolder.chooseFolderUiModule +import com.fsck.k9.ui.compose.composeModule import com.fsck.k9.ui.endtoend.endToEndUiModule import com.fsck.k9.ui.folders.foldersUiModule import com.fsck.k9.ui.identity.identityUiModule @@ -36,6 +37,7 @@ val uiModules = listOf( chooseFolderUiModule, contactsModule, accountModule, + composeModule, viewModule, changelogUiModule, messageSourceModule, diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/MessageCompose.java b/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/MessageCompose.java index 068b75f247a..befb6de0168 100644 --- a/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/MessageCompose.java +++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/MessageCompose.java @@ -4,7 +4,6 @@ import java.io.File; import java.util.Date; import java.util.HashMap; -import java.util.List; import java.util.Locale; import java.util.Map; import java.util.regex.Pattern; @@ -19,7 +18,6 @@ import android.content.IntentSender.SendIntentException; import android.content.pm.ActivityInfo; import android.net.Uri; -import android.nfc.NfcAdapter; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; @@ -110,6 +108,8 @@ import com.fsck.k9.ui.R; import com.fsck.k9.ui.base.K9Activity; import app.k9mail.legacy.ui.theme.ThemeManager; +import com.fsck.k9.ui.compose.IntentData; +import com.fsck.k9.ui.compose.IntentDataMapper; import com.fsck.k9.ui.compose.QuotedMessageMvpView; import com.fsck.k9.ui.compose.QuotedMessagePresenter; import com.fsck.k9.ui.compose.WrapUriTextWatcher; @@ -118,16 +118,15 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.textview.MaterialTextView; import org.openintents.openpgp.OpenPgpApiManager; -import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpIntentStarter; import timber.log.Timber; @SuppressWarnings("deprecation") // TODO get rid of activity dialogs and indeterminate progress bars public class MessageCompose extends K9Activity implements OnClickListener, - CancelListener, AttachmentDownloadCancelListener, OnFocusChangeListener, - OnOpenPgpInlineChangeListener, OnOpenPgpSignOnlyChangeListener, MessageBuilder.Callback, - AttachmentPresenter.AttachmentsChangedListener, OnOpenPgpDisableListener { + CancelListener, AttachmentDownloadCancelListener, OnFocusChangeListener, + OnOpenPgpInlineChangeListener, OnOpenPgpSignOnlyChangeListener, MessageBuilder.Callback, + AttachmentPresenter.AttachmentsChangedListener, OnOpenPgpDisableListener { private static final int DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE = 1; private static final int DIALOG_CONFIRM_DISCARD_ON_BACK = 2; @@ -140,7 +139,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, public static final String ACTION_FORWARD = "com.fsck.k9.intent.action.FORWARD"; public static final String ACTION_FORWARD_AS_ATTACHMENT = "com.fsck.k9.intent.action.FORWARD_AS_ATTACHMENT"; public static final String ACTION_EDIT_DRAFT = "com.fsck.k9.intent.action.EDIT_DRAFT"; - private static final String ACTION_AUTOCRYPT_PEER = "org.autocrypt.PEER_ACTION"; + public static final String ACTION_AUTOCRYPT_PEER = "org.autocrypt.PEER_ACTION"; public static final String EXTRA_ACCOUNT = "account"; public static final String EXTRA_MESSAGE_REFERENCE = "message_reference"; @@ -185,6 +184,8 @@ public class MessageCompose extends K9Activity implements OnClickListener, private final MessagingController messagingController = DI.get(MessagingController.class); private final Preferences preferences = DI.get(Preferences.class); + private final IntentDataMapper indentDataMapper = DI.get(IntentDataMapper.class); + private final Contacts contacts = DI.get(Contacts.class); private QuotedMessagePresenter quotedMessagePresenter; @@ -222,6 +223,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, private Action action; + private RecipientMvpView recipientMvpView; private boolean requestReadReceipt = false; private MaterialTextView chooseIdentityView; @@ -304,7 +306,7 @@ public void onCreate(Bundle savedInstanceState) { ReplyToView replyToView = new ReplyToView(this); replyToPresenter = new ReplyToPresenter(replyToView); - RecipientMvpView recipientMvpView = new RecipientMvpView(this); + recipientMvpView = new RecipientMvpView(this); ComposePgpInlineDecider composePgpInlineDecider = new ComposePgpInlineDecider(); ComposePgpEnableByDefaultDecider composePgpEnableByDefaultDecider = new ComposePgpEnableByDefaultDecider(); @@ -374,14 +376,44 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { relatedMessageProcessed = savedInstanceState.getBoolean(STATE_KEY_SOURCE_MESSAGE_PROCED, false); } + IntentData intentData = indentDataMapper.initFromIntent(intent); + + if (intentData.getMailToUri() != null) { + Uri uri = intentData.getMailToUri(); + if (MailTo.isMailTo(uri)) { + MailTo mailTo = MailTo.parse(uri); + initializeFromMailto(mailTo); + } + } + + if (intentData.getExtraText() != null && messageContentView.getText().length() == 0) { + messageContentView.setText(CrLfConverter.toLf(intentData.getExtraText())); + } + + if (intentData.getExtraStream() != null) { + attachmentPresenter.addExternalAttachment(intentData.getExtraStream(), intentData.getIntentType()); + } + + if (intentData.getSubject() != null && subjectView.getText().length() == 0) { + subjectView.setText(intentData.getSubject()); + } + + if (intentData.getShouldInitFromSendOrViewIntent()) { + recipientPresenter.initFromSendOrViewIntent(intent); + } + + if (intentData.getTrustId() != null) { + recipientPresenter.initFromTrustIdAction(intentData.getTrustId()); + } - if (initFromIntent(intent)) { + if (intentData.getStartedByExternalIntent()) { action = Action.COMPOSE; changesMadeSinceLastSave = true; } else { String action = intent.getAction(); if (ACTION_COMPOSE.equals(action)) { this.action = Action.COMPOSE; + recipientMvpView.requestFocusOnToField(); } else if (ACTION_REPLY.equals(action)) { this.action = Action.REPLY; } else if (ACTION_REPLY_ALL.equals(action)) { @@ -450,15 +482,6 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { relatedFlag = Flag.FORWARDED; } - if (action == Action.REPLY || action == Action.REPLY_ALL || - action == Action.EDIT_DRAFT) { - //change focus to message body. - messageContentView.requestFocus(); - } else { - // Explicitly set focus to "To:" input field (see issue 2998) - recipientMvpView.requestFocusOnToField(); - } - updateMessageFormat(); // Set font size of input controls @@ -480,109 +503,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { setProgressBarIndeterminateVisibility(true); currentMessageBuilder.reattachCallback(this); } - } - - /** - * Handle external intents that trigger the message compose activity. - * - *

- * Supported external intents: - *

- *

- * - * @param intent - * The (external) intent that started the activity. - * - * @return {@code true}, if this activity was started by an external intent. {@code false}, - * otherwise. - */ - private boolean initFromIntent(final Intent intent) { - boolean startedByExternalIntent = false; - final String action = intent.getAction(); - if (Intent.ACTION_VIEW.equals(action) || Intent.ACTION_SENDTO.equals(action) || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) { - /* - * Someone has clicked a mailto: link, or scanned an NFC tag. The address is in the URI. - */ - if (intent.getData() != null) { - Uri uri = intent.getData(); - if (MailTo.isMailTo(uri)) { - MailTo mailTo = MailTo.parse(uri); - initializeFromMailto(mailTo); - } - } - - /* - * Note: According to the documentation ACTION_VIEW and ACTION_SENDTO don't accept - * EXTRA_* parameters. - * And previously we didn't process these EXTRAs. But it looks like nobody bothers to - * read the official documentation and just copies wrong sample code that happens to - * work with the AOSP Email application. And because even big players get this wrong, - * we're now finally giving in and read the EXTRAs for those actions (below). - */ - } - - if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action) || - Intent.ACTION_SENDTO.equals(action) || Intent.ACTION_VIEW.equals(action)) { - startedByExternalIntent = true; - - /* - * Note: Here we allow a slight deviation from the documented behavior. - * EXTRA_TEXT is used as message body (if available) regardless of the MIME - * type of the intent. In addition one or multiple attachments can be added - * using EXTRA_STREAM. - */ - CharSequence text = intent.getCharSequenceExtra(Intent.EXTRA_TEXT); - // Only use EXTRA_TEXT if the body hasn't already been set by the mailto URI - if (text != null && messageContentView.getText().length() == 0) { - messageContentView.setText(CrLfConverter.toLf(text)); - } - - String type = intent.getType(); - if (Intent.ACTION_SEND.equals(action)) { - Uri stream = IntentCompat.getParcelableExtra(intent,Intent.EXTRA_STREAM, Uri.class); - if (stream != null) { - attachmentPresenter.addExternalAttachment(stream, type); - } - } else { - List list = IntentCompat.getParcelableArrayListExtra( - intent, - Intent.EXTRA_STREAM, - Parcelable.class - ); - if (list != null) { - for (Parcelable parcelable : list) { - Uri stream = (Uri) parcelable; - if (stream != null) { - attachmentPresenter.addExternalAttachment(stream, type); - } - } - } - } - - String subject = intent.getStringExtra(Intent.EXTRA_SUBJECT); - // Only use EXTRA_SUBJECT if the subject hasn't already been set by the mailto URI - if (subject != null && subjectView.getText().length() == 0) { - subjectView.setText(subject); - } - - recipientPresenter.initFromSendOrViewIntent(intent); - } - - if (ACTION_AUTOCRYPT_PEER.equals(action)) { - String trustId = intent.getStringExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID); - if (trustId != null) { - recipientPresenter.initFromTrustIdAction(trustId); - startedByExternalIntent = true; - } - } - - return startedByExternalIntent; } @Override @@ -1677,6 +1598,14 @@ public void onMessageDataLoadFailed() { public void onMessageViewInfoLoadFinished(MessageViewInfo messageViewInfo) { internalMessageHandler.sendEmptyMessage(MSG_PROGRESS_OFF); loadLocalMessageForDisplay(messageViewInfo, action); + + if(!recipientPresenter.isToAddressAdded()) { + recipientMvpView.requestFocusOnToField(); + } else if (subjectView.getText().length() == 0) { + subjectView.requestFocus(); + } else { + messageContentView.requestFocus(); + } } @Override diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/compose/RecipientMvpView.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/compose/RecipientMvpView.kt index c18427f8879..a86e847a1d3 100644 --- a/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/compose/RecipientMvpView.kt +++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/compose/RecipientMvpView.kt @@ -71,6 +71,9 @@ class RecipientMvpView(private val activity: MessageCompose) : View.OnFocusChang val bccRecipients: List get() = bccView.objects + val isToAddressAdded: Boolean + get() = presenter.isToAddressAdded() + val isCcTextEmpty: Boolean get() = ccView.text.isEmpty() diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/compose/RecipientPresenter.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/compose/RecipientPresenter.kt index 372912ee475..347f79656fe 100644 --- a/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/compose/RecipientPresenter.kt +++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/compose/RecipientPresenter.kt @@ -64,6 +64,7 @@ class RecipientPresenter( private val replyToParser: ReplyToParser, private val draftStateHeaderParser: AutocryptDraftStateHeaderParser, ) { + private var isToAddressAdded: Boolean = false private lateinit var account: Account private var alwaysBccAddresses: Array
? = null private var hasContactPicker: Boolean? = null @@ -254,8 +255,11 @@ class RecipientPresenter( private fun addToAddresses(vararg toAddresses: Address) { addRecipientsFromAddresses(RecipientType.TO, *toAddresses) + isToAddressAdded = true } + fun isToAddressAdded() = isToAddressAdded + private fun addCcAddresses(vararg ccAddresses: Address) { if (ccAddresses.isNotEmpty()) { addRecipientsFromAddresses(RecipientType.CC, *ccAddresses) diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/compose/IntentDataMapper.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/compose/IntentDataMapper.kt new file mode 100644 index 00000000000..c166010ec96 --- /dev/null +++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/compose/IntentDataMapper.kt @@ -0,0 +1,76 @@ +package com.fsck.k9.ui.compose + +import android.content.Intent +import android.net.Uri +import android.nfc.NfcAdapter +import android.os.Parcelable +import androidx.core.content.IntentCompat +import com.fsck.k9.activity.MessageCompose +import org.openintents.openpgp.util.OpenPgpApi + +class IntentDataMapper { + + fun initFromIntent(intent: Intent): IntentData { + val action: String? = intent.action + var intentData = IntentData() + + if (Intent.ACTION_VIEW == action || Intent.ACTION_SENDTO == action || NfcAdapter.ACTION_NDEF_DISCOVERED == action) { + intent.data?.let { data -> + intentData = intentData.copy(mailToUri = data) + } + } + + if ((Intent.ACTION_SEND == action || Intent.ACTION_SEND_MULTIPLE == action || + Intent.ACTION_SENDTO == action || Intent.ACTION_VIEW == action) + ) { + intentData = intentData.copy( + startedByExternalIntent = true, + extraText = intent.getCharSequenceExtra(Intent.EXTRA_TEXT), + intentType = intent.type, + ) + + if ((Intent.ACTION_SEND == action)) { + val extraStream = IntentCompat.getParcelableExtra( + intent, + Intent.EXTRA_STREAM, + Uri::class.java, + ) + intentData = intentData.copy(extraStream = extraStream) + } else { + val list: List? = IntentCompat.getParcelableArrayListExtra( + intent, + Intent.EXTRA_STREAM, + Parcelable::class.java, + ) + list?.let { + for (parcelable in it) { + intentData = intentData.copy(extraStream = parcelable as Uri) + } + } + } + intentData = intentData.copy( + subject = intent.getStringExtra(Intent.EXTRA_SUBJECT), + shouldInitFromSendOrViewIntent = true + ) + } + + if ((MessageCompose.ACTION_AUTOCRYPT_PEER == action)) { + intentData = intentData.copy( + trustId = intent.getStringExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID), + startedByExternalIntent = true, + ) + } + return intentData + } +} + +data class IntentData( + val startedByExternalIntent: Boolean = false, + val shouldInitFromSendOrViewIntent: Boolean = false, + val mailToUri: Uri? = null, + val extraText: CharSequence? = null, + val intentType: String? = null, + val extraStream: Uri? = null, + val subject: String? = null, + val trustId: String? = null, +) diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/compose/KoinModule.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/compose/KoinModule.kt new file mode 100644 index 00000000000..1d5c0e38f48 --- /dev/null +++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/compose/KoinModule.kt @@ -0,0 +1,7 @@ +package com.fsck.k9.ui.compose + +import org.koin.dsl.module + +val composeModule = module { + factory { IntentDataMapper() } +}