Rewrite Share functionality in message view screen

The shared text now contains subject, date, sender, and recipients in addition to the message body text.
This commit is contained in:
cketti 2020-10-05 05:22:12 +02:00
parent 8f7e9ee73c
commit 3133573139
10 changed files with 107 additions and 63 deletions

View file

@ -4,8 +4,6 @@ interface CoreResourceProvider {
fun defaultSignature(): String
fun defaultIdentityDescription(): String
fun sendAlternateChooserTitle(): String
fun internalStorageProviderName(): String
fun externalStorageProviderName(): String

View file

@ -23,7 +23,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import android.os.Process;
import android.os.SystemClock;
@ -36,7 +35,6 @@ import com.fsck.k9.Account.Expunge;
import com.fsck.k9.CoreResourceProvider;
import com.fsck.k9.DI;
import com.fsck.k9.K9;
import com.fsck.k9.K9.Intents;
import com.fsck.k9.Preferences;
import com.fsck.k9.backend.BackendManager;
import com.fsck.k9.backend.api.Backend;
@ -56,7 +54,6 @@ import com.fsck.k9.controller.MessagingControllerCommands.PendingMoveOrCopy;
import com.fsck.k9.controller.MessagingControllerCommands.PendingSetFlag;
import com.fsck.k9.controller.ProgressBodyFactory.ProgressListener;
import com.fsck.k9.helper.MutableBoolean;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.AuthenticationFailedException;
import com.fsck.k9.mail.CertificateValidationException;
import com.fsck.k9.mail.FetchProfile;
@ -64,12 +61,9 @@ import com.fsck.k9.mail.FetchProfile.Item;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.FolderClass;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.MessageRetrievalListener;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MessageExtractor;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.mailstore.LocalStore;
@ -2203,50 +2197,6 @@ public class MessagingController {
return !backend.getSupportsTrashFolder();
}
public void sendAlternate(Context context, Account account, LocalMessage message) {
Timber.d("Got message %s:%s:%s for sendAlternate",
account.getDescription(), message.getFolder(), message.getUid());
Intent msg = new Intent(Intent.ACTION_SEND);
String quotedText = null;
Part part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
if (part == null) {
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
}
if (part != null) {
quotedText = MessageExtractor.getTextFromPart(part);
}
if (quotedText != null) {
msg.putExtra(Intent.EXTRA_TEXT, quotedText);
}
msg.putExtra(Intent.EXTRA_SUBJECT, message.getSubject());
Address[] from = message.getFrom();
String[] senders = new String[from.length];
for (int i = 0; i < from.length; i++) {
senders[i] = from[i].toString();
}
msg.putExtra(Intents.Share.EXTRA_FROM, senders);
Address[] to = message.getRecipients(RecipientType.TO);
String[] recipientsTo = new String[to.length];
for (int i = 0; i < to.length; i++) {
recipientsTo[i] = to[i].toString();
}
msg.putExtra(Intent.EXTRA_EMAIL, recipientsTo);
Address[] cc = message.getRecipients(RecipientType.CC);
String[] recipientsCc = new String[cc.length];
for (int i = 0; i < cc.length; i++) {
recipientsCc[i] = cc[i].toString();
}
msg.putExtra(Intent.EXTRA_CC, recipientsCc);
msg.setType("text/plain");
Intent chooserIntent = Intent.createChooser(msg, resourceProvider.sendAlternateChooserTitle());
context.startActivity(chooserIntent);
}
public boolean performPeriodicMailSync(Account account) {
final CountDownLatch latch = new CountDownLatch(1);
MutableBoolean syncError = new MutableBoolean(false);

View file

@ -4,4 +4,5 @@ import org.koin.dsl.module
val extractorModule = module {
single { AttachmentInfoExtractor(get()) }
single { TextPartFinder() }
}

View file

@ -5,10 +5,6 @@ class TestCoreResourceProvider : CoreResourceProvider {
override fun defaultIdentityDescription() = "initial identity"
override fun sendAlternateChooserTitle(): String {
throw UnsupportedOperationException("not implemented")
}
override fun internalStorageProviderName(): String {
throw UnsupportedOperationException("not implemented")
}

View file

@ -8,8 +8,6 @@ class K9CoreResourceProvider(private val context: Context) : CoreResourceProvide
override fun defaultSignature(): String = context.getString(R.string.default_signature)
override fun defaultIdentityDescription(): String = context.getString(R.string.default_identity_description)
override fun sendAlternateChooserTitle(): String = context.getString(R.string.send_alternate_chooser_title)
override fun internalStorageProviderName(): String =
context.getString(R.string.local_storage_provider_internal_label)

View file

@ -8,8 +8,6 @@ class K9CoreResourceProvider(private val context: Context) : CoreResourceProvide
override fun defaultSignature(): String = context.getString(R.string.default_signature)
override fun defaultIdentityDescription(): String = context.getString(R.string.default_identity_description)
override fun sendAlternateChooserTitle(): String = context.getString(R.string.send_alternate_chooser_title)
override fun internalStorageProviderName(): String =
context.getString(R.string.local_storage_provider_internal_label)

View file

@ -6,6 +6,7 @@ import com.fsck.k9.ui.helper.DisplayHtmlUiFactory
import com.fsck.k9.ui.helper.HtmlSettingsProvider
import com.fsck.k9.ui.helper.HtmlToSpanned
import com.fsck.k9.ui.helper.SizeFormatter
import com.fsck.k9.ui.share.ShareIntentBuilder
import org.koin.dsl.module
val uiModule = module {
@ -14,4 +15,5 @@ val uiModule = module {
single { HtmlSettingsProvider(get()) }
single { DisplayHtmlUiFactory(get()) }
factory { (context: Context) -> SizeFormatter(context.resources) }
factory { ShareIntentBuilder(resourceProvider = get(), textPartFinder = get(), quoteDateFormatter = get()) }
}

View file

@ -52,6 +52,7 @@ import com.fsck.k9.ui.base.ThemeManager;
import com.fsck.k9.ui.messageview.CryptoInfoDialog.OnClickShowCryptoKeyListener;
import com.fsck.k9.ui.messageview.MessageCryptoPresenter.MessageCryptoMvpView;
import com.fsck.k9.ui.settings.account.AccountSettingsActivity;
import com.fsck.k9.ui.share.ShareIntentBuilder;
import com.fsck.k9.view.MessageCryptoDisplayStatus;
import timber.log.Timber;
@ -491,7 +492,13 @@ public class MessageViewFragment extends Fragment implements ConfirmationDialogF
public void onSendAlternate() {
if (mMessage != null) {
mController.sendAlternate(getActivity(), mAccount, mMessage);
ShareIntentBuilder shareIntentBuilder = DI.get(ShareIntentBuilder.class);
Intent shareIntent = shareIntentBuilder.createShareIntent(mMessage);
String shareTitle = getString(R.string.send_alternate_chooser_title);
Intent chooserIntent = Intent.createChooser(shareIntent, shareTitle);
startActivity(chooserIntent);
}
}

View file

@ -0,0 +1,96 @@
package com.fsck.k9.ui.share
import android.content.Intent
import com.fsck.k9.CoreResourceProvider
import com.fsck.k9.mail.Address
import com.fsck.k9.mail.Message.RecipientType
import com.fsck.k9.mail.Part
import com.fsck.k9.mail.internet.MessageExtractor
import com.fsck.k9.mail.internet.MimeUtility
import com.fsck.k9.mailstore.LocalMessage
import com.fsck.k9.message.extractors.TextPartFinder
import com.fsck.k9.message.html.HtmlConverter
import com.fsck.k9.message.quote.QuoteDateFormatter
/**
* Create a Share intent containing important headers and the body text of a message
*/
class ShareIntentBuilder(
private val resourceProvider: CoreResourceProvider,
private val textPartFinder: TextPartFinder,
private val quoteDateFormatter: QuoteDateFormatter
) {
// TODO: Pass MessageViewInfo and extract text from there
// TODO: Use display HTML for EXTRA_HTML_TEXT and convert it to plain text for EXTRA_TEXT
fun createShareIntent(message: LocalMessage): Intent {
val shareText = createShareText(message)
return Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, shareText)
}
}
private fun createShareText(message: LocalMessage): String {
val bodyText = extractBodyText(message)
val sentDate = quoteDateFormatter.format(message.sentDate)
return buildString {
message.subject?.let { subject ->
append(resourceProvider.messageHeaderSubject())
append(' ')
append(subject)
append('\n')
}
if (sentDate.isNotEmpty()) {
append(resourceProvider.messageHeaderDate())
append(' ')
append(sentDate)
append('\n')
}
message.from.displayString()?.let { fromAddresses ->
append(resourceProvider.messageHeaderFrom())
append(' ')
append(fromAddresses)
append('\n')
}
message.getRecipients(RecipientType.TO).displayString()?.let { toAddresses ->
append(resourceProvider.messageHeaderTo())
append(' ')
append(toAddresses)
append('\n')
}
message.getRecipients(RecipientType.CC).displayString()?.let { ccAddresses ->
append(resourceProvider.messageHeaderCc())
append(' ')
append(ccAddresses)
append('\n')
}
append('\n')
append(bodyText)
}
}
private fun extractBodyText(message: LocalMessage): String {
val part = textPartFinder.findFirstTextPart(message)
if (part == null || part.body == null) return ""
val text = MessageExtractor.getTextFromPart(part) ?: return ""
return convertFromHtmlIfNecessary(part, text)
}
private fun convertFromHtmlIfNecessary(textPart: Part, text: String): String {
return if (MimeUtility.isSameMimeType(textPart.mimeType, "text/html")) {
HtmlConverter.htmlToText(text)
} else {
text
}
}
private fun Array<Address>.displayString() = Address.toString(this)?.let { if (it.isEmpty()) null else it }
}

View file

@ -5,8 +5,6 @@ class TestCoreResourceProvider : CoreResourceProvider {
override fun defaultIdentityDescription() = throw UnsupportedOperationException("not implemented")
override fun sendAlternateChooserTitle() = throw UnsupportedOperationException("not implemented")
override fun internalStorageProviderName() = throw UnsupportedOperationException("not implemented")
override fun externalStorageProviderName() = throw UnsupportedOperationException("not implemented")