package de.thedevstack.conversationsplus.ui.adapter; import android.graphics.Typeface; import android.support.annotation.NonNull; import android.text.Spannable; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import java.util.List; import de.thedevstack.conversationsplus.ConversationsPlusColors; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlSession; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.FileParams; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.enums.FileStatus; import de.thedevstack.conversationsplus.enums.MessageStatus; import de.thedevstack.conversationsplus.services.avatar.AvatarCache; import de.thedevstack.conversationsplus.services.avatar.AvatarService; import de.thedevstack.conversationsplus.services.filetransfer.http.download.AutomaticFileDownload; import de.thedevstack.conversationsplus.ui.ConversationActivity; import de.thedevstack.conversationsplus.ui.listeners.ContactPictureOnClickListener; import de.thedevstack.conversationsplus.ui.listeners.ContactPictureOnLongClickListener; import de.thedevstack.conversationsplus.ui.listeners.OpenFileOnClickListener; import de.thedevstack.conversationsplus.ui.listeners.OpenLocationOnClickListener; import de.thedevstack.conversationsplus.utils.CryptoHelper; import de.thedevstack.conversationsplus.utils.GeoHelper; import de.thedevstack.conversationsplus.utils.ImageUtil; import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.utils.UIHelper; import de.thedevstack.conversationsplus.utils.ui.TextViewUtil; import de.thedevstack.conversationsplus.utils.ui.ViewUtil; import static de.thedevstack.conversationsplus.ui.adapter.MessageViewHolder.ME_COMMAND; import static de.thedevstack.conversationsplus.ui.adapter.MessageViewHolder.RECEIVED; import static de.thedevstack.conversationsplus.ui.adapter.MessageViewHolder.SENT; import static de.thedevstack.conversationsplus.ui.adapter.MessageViewHolder.STATUS; public class MessageAdapter extends ArrayAdapter { private ConversationActivity activity; private OnLongClickListener openContextMenu = new OnLongClickListener() { @Override public boolean onLongClick(View v) { v.showContextMenu(); return true; } }; public MessageAdapter(ConversationActivity activity, List messages) { super(activity, 0, messages); this.activity = activity; } @Override public int getViewTypeCount() { return 4; } public int getItemViewType(Message message) { if (message.hasMeCommand()) { return ME_COMMAND; } else if (message.getType() == Message.TYPE_STATUS) { return STATUS; } else if (MessageUtil.isIncomingMessage(message)) { return RECEIVED; } return SENT; } @Override public int getItemViewType(int position) { return this.getItemViewType(getItem(position)); } private int getMessageTextColor(boolean onDark, boolean primary) { if (onDark) { return primary ? ConversationsPlusColors.primaryTextOnDark() : ConversationsPlusColors.secondaryTextOnDark(); } else { return primary ? ConversationsPlusColors.primaryText() : ConversationsPlusColors.secondaryText(); } } private void displayStatus(MessageViewHolder viewHolder, Message message, int type) { String filesize = null; String info = null; boolean error = false; ViewUtil.gone(viewHolder.indicatorReceived); boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI && MessageUtil.isIncomingMessage(message); if (message.hasFileAttached() || MessageUtil.hasDownloadableLink(message)) { FileParams params = message.getFileParams(); if (null != params) { filesize = UIHelper.getHumanReadableFileSize(params.getSize()); } if (message.getTransferable() != null && message.getTransferable().getStatus() == Transferable.STATUS_FAILED) { error = true; } } Transferable d = message.getTransferable(); switch (message.getMessageStatus()) { case WAITING: info = getContext().getString(R.string.waiting); break; case TRANSMITTING: if (null != d) { int resId = R.string.receiving_file; if (MessageUtil.isOutgoingMessage(message)) { resId = R.string.sending_file; } info = getContext().getString(resId, d.getProgress()); } else if (null != message.getFileParams() && FileStatus.NEEDS_UPLOAD == message.getFileParams().getFileStatus()) { // FIXME: Remove when Jingle is migrated info = getContext().getString(R.string.offering); } else { if (!MessageUtil.needsDownload(message)) { int resId = R.string.receiving; if (MessageUtil.isOutgoingMessage(message)) { resId = R.string.sending; } info = getContext().getString(resId); } } break; case RECEIVED: if (ConversationsPlusPreferences.indicateReceived()) { viewHolder.indicatorReceived.setVisibility(View.VISIBLE); } break; case DISPLAYED: if (ConversationsPlusPreferences.indicateReceived()) { viewHolder.indicatorReceived.setVisibility(View.VISIBLE); } break; case FAILED: info = getContext().getString(R.string.send_failed); error = true; break; case CANCELED: info = getContext().getString(R.string.send_canceled); error = true; break; default: if (multiReceived) { info = UIHelper.getMessageDisplayName(message); } } this.displayEncryptionIndicator(message, viewHolder); this.displayMessageTime(message, viewHolder, filesize, info, error, type == SENT); this.displayRemoteFileStatus(message, viewHolder); } private void displayRemoteFileStatus(Message message, MessageViewHolder viewHolder) { if (message.hasFileAttached() && null != message.getFileParams() && null != viewHolder.remoteFileStatus) { FileStatus fileStatus = message.getFileParams().getFileStatus(); if (fileStatus == FileStatus.DELETE_FAILED || fileStatus == FileStatus.DELETED || fileStatus == FileStatus.DELETING || fileStatus == FileStatus.NOT_FOUND) { TextViewUtil.visible(viewHolder.remoteFileStatus); switch (fileStatus) { case DELETE_FAILED: TextViewUtil.setColor(viewHolder.remoteFileStatus, R.color.error); viewHolder.remoteFileStatus.setText(R.string.remote_filestatus_delete_failed); break; case DELETED: viewHolder.remoteFileStatus.setText(R.string.remote_filestatus_delete_success); break; case DELETING: viewHolder.remoteFileStatus.setText(R.string.remote_filestatus_delete_inprogress); break; case NOT_FOUND: TextViewUtil.setColor(viewHolder.remoteFileStatus, R.color.error); viewHolder.remoteFileStatus.setText(R.string.remote_filestatus_not_found); break; } } else { TextViewUtil.gone(viewHolder.remoteFileStatus); } } } private void displayMessageTime(Message message, MessageViewHolder viewHolder, String filesize, String info, boolean error, boolean isSent) { if (error && isSent) { viewHolder.time.setTextColor(ConversationsPlusColors.warning()); } else { viewHolder.time.setTextColor(this.getMessageTextColor(viewHolder.darkBackground, false)); } String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(), message.getTimeSent()); String timeText = null; if (MessageUtil.isIncomingMessage(message)) { StringBuilder timeTextBuilder = new StringBuilder(); timeTextBuilder.append((null != formatedTime) ? formatedTime + ((null != info || null != filesize) ? " \u00B7 " : "") : ""); timeTextBuilder.append((null != filesize) ? filesize + ((null != info) ? " \u00B7 " : "") : ""); timeTextBuilder.append((null != info) ? info : ""); timeText = timeTextBuilder.toString(); } else { if ((filesize != null) && (info != null)) { timeText = filesize + " \u00B7 " + info; } else if ((filesize == null) && (info != null)) { if (error) { timeText = info + " \u00B7 " + formatedTime; } else { timeText = info; } } else if ((filesize != null) && (info == null)) { timeText = filesize + " \u00B7 " + formatedTime; } else { timeText = formatedTime; } } TextViewUtil.setTextWithoutAutoLink(viewHolder.time, timeText); } private void displayEncryptionIndicator(Message message, MessageViewHolder viewHolder) { if (message.getEncryption() == Message.ENCRYPTION_NONE) { viewHolder.indicator.setVisibility(View.GONE); } else { viewHolder.indicator.setImageResource(viewHolder.darkBackground ? R.drawable.ic_lock_white_18dp : R.drawable.ic_lock_black_18dp); viewHolder.indicator.setVisibility(View.VISIBLE); if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { XmppAxolotlSession.Trust trust = message.getConversation() .getAccount().getAxolotlService().getFingerprintTrust( message.getFingerprint()); if (trust == null || (!trust.trusted() && !trust.trustedInactive())) { viewHolder.indicator.setColorFilter(ConversationsPlusColors.warning()); viewHolder.indicator.setAlpha(1.0f); } else { viewHolder.indicator.clearColorFilter(); if (viewHolder.darkBackground) { viewHolder.indicator.setAlpha(0.7f); } else { viewHolder.indicator.setAlpha(0.57f); } } } else { viewHolder.indicator.clearColorFilter(); if (viewHolder.darkBackground) { viewHolder.indicator.setAlpha(0.7f); } else { viewHolder.indicator.setAlpha(0.57f); } } } } private void displayInfoMessage(MessageViewHolder viewHolder, String text) { ViewUtil.gone(viewHolder.download_button, viewHolder.image); TextViewUtil.showAndSetText(viewHolder.messageBody, text); viewHolder.messageBody.setTextColor(getMessageTextColor(viewHolder.darkBackground, false)); viewHolder.messageBody.setTypeface(null, Typeface.ITALIC); viewHolder.messageBody.setTextIsSelectable(false); } private void displayDecryptionFailed(MessageViewHolder viewHolder) { ViewUtil.gone(viewHolder.download_button, viewHolder.image); TextViewUtil.showAndSetText(viewHolder.messageBody, R.string.decryption_failed); viewHolder.messageBody.setTextColor(getMessageTextColor(viewHolder.darkBackground, false)); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); viewHolder.messageBody.setTextIsSelectable(false); } private void displayTextMessage(final MessageViewHolder viewHolder, final Message message) { ViewUtil.gone(viewHolder.download_button, viewHolder.image); ViewUtil.visible(viewHolder.messageBody); viewHolder.messageBody.setIncludeFontPadding(true); if (message.getBody() != null) { final String nick = UIHelper.getMessageDisplayName(message); String body; if (message.hasMeCommand()) { body = message.getBodyReplacedMeCommand(nick); } else { body = message.getBody(); } final SpannableString formattedBody = new SpannableString(body); if (message.getType() != Message.TYPE_PRIVATE) { if (message.hasMeCommand()) { final Spannable span = new SpannableString(formattedBody); span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); viewHolder.messageBody.setText(span); } else { viewHolder.messageBody.setText(formattedBody); } } else { String privateMarker; if (MessageUtil.isIncomingMessage(message)) { privateMarker = activity.getString(R.string.private_message); } else { final String to; if (message.getCounterpart() != null) { to = message.getCounterpart().getResourcepart(); } else { to = ""; } privateMarker = activity.getString(R.string.private_message_to, to); } final Spannable span = new SpannableString(privateMarker + " " + formattedBody); span.setSpan(new ForegroundColorSpan(getMessageTextColor(viewHolder.darkBackground, false)), 0, privateMarker .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); span.setSpan(new StyleSpan(Typeface.BOLD), 0, privateMarker.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); if (message.hasMeCommand()) { span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarker.length() + 1, privateMarker.length() + 1 + nick.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } viewHolder.messageBody.setText(span); } TextViewUtil.linkifyXmpp(viewHolder.messageBody); } else { viewHolder.messageBody.setText(""); viewHolder.messageBody.setTextIsSelectable(false); } viewHolder.messageBody.setTextColor(this.getMessageTextColor(viewHolder.darkBackground, true)); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); viewHolder.messageBody.setOnLongClickListener(openContextMenu); } private void displayDownloadButton(MessageViewHolder viewHolder, String btnText, OnClickListener onClickListener) { TextViewUtil.showAndSetText(viewHolder.download_button, btnText) .setOnClickListener(onClickListener); viewHolder.download_button.setOnLongClickListener(openContextMenu); } private void displayFileInfoForFileMessage(final Message message, MessageViewHolder viewHolder) { ViewUtil.visible(viewHolder.messageBody); StringBuilder fileInfos = new StringBuilder(); String filename = UIHelper.getDisplayFilename(message); fileInfos.append((null != filename && !filename.isEmpty()) ? (filename) : ""); TextViewUtil.setTextWithoutAutoLink(viewHolder.messageBody, fileInfos); } private void displayDownloadableMessage(MessageViewHolder viewHolder, final Message message) { ViewUtil.gone(viewHolder.image); FileParams fileParams = message.getFileParams(); String btnText; int resId = R.string.download_x_file; if ((message.getTransferable() != null && message.getTransferable().getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) || (null != fileParams && -1 == fileParams.getSize())) { resId = R.string.check_x_filesize; } if (null != fileParams) { this.displayFileInfoForFileMessage(message, viewHolder); btnText = activity.getString(resId, ""); } else { viewHolder.messageBody.setVisibility(View.GONE); btnText = activity.getString(resId, UIHelper.getFileDescriptionString(activity, message)); } this.displayDownloadButton(viewHolder, btnText, new OnClickListener() { @Override public void onClick(View v) { activity.startDownloadable(message); } }); } private void displayOpenableMessage(MessageViewHolder viewHolder, final Message message) { ViewUtil.gone(viewHolder.image); FileParams fileParams = message.getFileParams(); String btnText; if (null != fileParams) { this.displayFileInfoForFileMessage(message, viewHolder); btnText = activity.getString(R.string.cplus_open); } else { viewHolder.messageBody.setVisibility(View.GONE); btnText = activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message)); } this.displayDownloadButton(viewHolder, btnText, new OpenFileOnClickListener(this.activity, message)); } private void displayLocationMessage(MessageViewHolder viewHolder, final Message message) { ViewUtil.gone(viewHolder.image, viewHolder.messageBody); TextViewUtil.showAndSetText(viewHolder.download_button, R.string.show_location) .setOnClickListener(new OpenLocationOnClickListener(this.activity, message)); viewHolder.download_button.setOnLongClickListener(openContextMenu); } private void displayImageMessage(MessageViewHolder viewHolder, final Message message) { ViewUtil.gone(viewHolder.download_button, viewHolder.messageBody); ViewUtil.visible(viewHolder.image); ImageUtil.loadBitmap(message, viewHolder.image, viewHolder.messageBody, true); viewHolder.image.setOnClickListener(new OpenFileOnClickListener(this.activity, message)); viewHolder.image.setOnLongClickListener(openContextMenu); } private View displayStatusMessage(final Message message, MessageViewHolder viewHolder) { final Conversation conversation = message.getConversation(); ViewUtil.visible(viewHolder.status_message, viewHolder.contact_picture); if (conversation.getMode() == Conversation.MODE_SINGLE) { viewHolder.contact_picture.setImageBitmap(AvatarCache.get(conversation.getContact(), activity.getPixel(32))); viewHolder.contact_picture.setAlpha(0.5f); } viewHolder.status_message.setText(message.getBody()); return viewHolder.view; } private void displayFileMessage(final Message message, MessageViewHolder viewHolder) { if (!(message.trusted() && MessageUtil.needsDownload(message) && ConversationsPlusPreferences.autoAcceptFileSize() > 0 && message.isHttpUploaded() || ConversationsPlusPreferences.autoDownloadFileLink())) { new AutomaticFileDownload().transferFile(message); } Transferable transferable = message.getTransferable(); if (FileStatus.CHECKING_FILE_SIZE == message.getFileParams().getFileStatus()) { displayInfoMessage(viewHolder, activity.getString(R.string.checking_remote_filesize)); } else if (MessageUtil.isAttachedFileAnImage(message) && hasFileStatusToShowMessage(message)) { displayImageMessage(viewHolder, message); } else if ((MessageUtil.isTypeFileAndDecrypted(message) || hasFileStatusToShowMessage(message)) && !MessageUtil.needsDownload(message)) { displayOpenableMessage(viewHolder, message); } else if (Message.Decision.NEVER == message.treatAsDownloadable() || !MessageUtil.mayFileRemoteAvailable(message)) { displayTextMessage(viewHolder, message); } else if (null != transferable) { switch (transferable.getStatus()) { case Transferable.STATUS_OFFER: case Transferable.STATUS_OFFER_CHECK_FILESIZE: displayDownloadableMessage(viewHolder, message); break; case Transferable.STATUS_DELETED: case Transferable.STATUS_CHECKING: case Transferable.STATUS_FAILED: case Transferable.STATUS_DOWNLOADING: case Transferable.STATUS_UNKNOWN: displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first); break; } } else { displayDownloadableMessage(viewHolder, message); } } private boolean hasFileStatusToShowMessage(Message message) { Transferable transferable = message.getTransferable(); return (FileStatus.DOWNLOADED == message.getFileParams().getFileStatus() || FileStatus.DELETED == message.getFileParams().getFileStatus() || FileStatus.DELETING == message.getFileParams().getFileStatus() || FileStatus.DELETE_FAILED == message.getFileParams().getFileStatus() || FileStatus.NEEDS_UPLOAD == message.getFileParams().getFileStatus() || FileStatus.UPLOADED == message.getFileParams().getFileStatus() || FileStatus.UPLOAD_FAILED == message.getFileParams().getFileStatus() || (null != transferable && (transferable.isCanceled() || Transferable.STATUS_UPLOADING == transferable.getStatus()))); } private void displayAvatar(final Message message, int type, MessageViewHolder viewHolder) { if (type == ME_COMMAND || (type == RECEIVED)) { // && message.getConversation().getMode() == Conversation.MODE_MULTI ImageView imageView = viewHolder.contact_picture; if (null != imageView) { AvatarService.getInstance().loadAvatar(message, imageView); imageView.setOnClickListener(new ContactPictureOnClickListener(this.activity, message)); if (message.getConversation().getMode() == Conversation.MODE_MULTI) { imageView.setOnLongClickListener(new ContactPictureOnLongClickListener(this.activity, message)); } } } } @Override public View getView(int position, View view, @NonNull ViewGroup parent) { final Message message = getItem(position); if (null == message || MessageStatus.DISPLAYED == message.getMessageStatus() || MessageStatus.RECEIVED == message.getMessageStatus()) { return view; } final boolean isInValidSession = message.isValidInSession(); final Conversation conversation = message.getConversation(); final Account account = conversation.getAccount(); final int type = getItemViewType(position); MessageViewHolder viewHolder; if (null == view) { viewHolder = new MessageViewHolder(this.activity, type, parent); view = viewHolder.view; } else { viewHolder = (MessageViewHolder) view.getTag(); if (null == viewHolder) { return view; } } if (type == STATUS) { return displayStatusMessage(message, viewHolder); } this.displayAvatar(message, type, viewHolder); viewHolder.darkBackground = (type == RECEIVED && !isInValidSession); if (null != message.getTransferable() || message.hasFileAttached() || MessageUtil.hasDownloadableLink(message)) { displayFileMessage(message, viewHolder); } else if (GeoHelper.isGeoUri(message.getBody())) { displayLocationMessage(viewHolder, message); } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { displayPgpEncryptedMessage(viewHolder, account); } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { displayDecryptionFailed(viewHolder); } else { displayTextMessage(viewHolder, message); } this.displayStatus(viewHolder, message, type); if (type == RECEIVED) { if (isInValidSession) { ViewUtil.gone(viewHolder.encryption); } else { viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning); TextViewUtil.showAndSetText(viewHolder.encryption, CryptoHelper.encryptionTypeToText(message.getEncryption())); } } return view; } private void displayPgpEncryptedMessage(MessageViewHolder viewHolder, Account account) { if (activity.hasPgp()) { if (account.getPgpDecryptionService().isRunning()) { displayInfoMessage(viewHolder, activity.getString(R.string.message_decrypting)); } else { displayInfoMessage(viewHolder, activity.getString(R.string.pgp_message)); } } else { displayInfoMessage(viewHolder, activity.getString(R.string.install_openkeychain)); viewHolder.message_box.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { activity.showInstallPgpDialog(); } }); } } }