@@ -29,19 +29,16 @@ import androidx.annotation.AttrRes;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.widget.ImageViewCompat;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.color.MaterialColors;
+import com.google.common.base.Joiner;
import com.google.common.base.Strings;
-
-import java.net.URI;
-import java.util.List;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import com.google.common.collect.ImmutableList;
import eu.siacs.conversations.AppSettings;
import eu.siacs.conversations.Config;
@@ -80,6 +77,12 @@ import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.mam.MamReference;
+import java.net.URI;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
public class MessageAdapter extends ArrayAdapter<Message> {
public static final String DATE_SEPARATOR_BODY = "DATE_SEPARATOR";
@@ -97,7 +100,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
private boolean colorfulChatBubbles = false;
private final boolean mForceNames;
- public MessageAdapter(final XmppActivity activity, final List<Message> messages, final boolean forceNames) {
+ public MessageAdapter(
+ final XmppActivity activity, final List<Message> messages, final boolean forceNames) {
super(activity, 0, messages);
this.audioPlayer = new AudioPlayer(this);
this.activity = activity;
@@ -136,8 +140,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
return activity;
}
- public void setOnContactPictureLongClicked(
- OnContactPictureLongClicked listener) {
+ public void setOnContactPictureLongClicked(OnContactPictureLongClicked listener) {
this.mOnContactPictureLongClickedListener = listener;
}
@@ -167,93 +170,72 @@ public class MessageAdapter extends ArrayAdapter<Message> {
return this.getItemViewType(getItem(position));
}
- private void displayStatus(ViewHolder viewHolder, Message message, int type, final BubbleColor bubbleColor) {
- String filesize = null;
- String info = null;
- boolean error = false;
+ private void displayStatus(
+ final ViewHolder viewHolder,
+ final Message message,
+ final int type,
+ final BubbleColor bubbleColor) {
+ final int mergedStatus = message.getMergedStatus();
+ final boolean error;
if (viewHolder.indicatorReceived != null) {
viewHolder.indicatorReceived.setVisibility(View.GONE);
}
-
- if (viewHolder.edit_indicator != null) {
- if (message.edited()) {
- viewHolder.edit_indicator.setVisibility(View.VISIBLE);
- setImageTint(viewHolder.edit_indicator, bubbleColor);
- } else {
- viewHolder.edit_indicator.setVisibility(View.GONE);
- }
- }
final Transferable transferable = message.getTransferable();
- boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
- && message.getMergedStatus() <= Message.STATUS_RECEIVED;
- if (message.isFileOrImage() || transferable != null || MessageUtils.unInitiatedButKnownSize(message)) {
- FileParams params = message.getFileParams();
- filesize = params.size != null ? UIHelper.filesizeToString(params.size) : null;
- if (transferable != null && (transferable.getStatus() == Transferable.STATUS_FAILED || transferable.getStatus() == Transferable.STATUS_CANCELLED)) {
+ final boolean multiReceived =
+ message.getConversation().getMode() == Conversation.MODE_MULTI
+ && mergedStatus <= Message.STATUS_RECEIVED;
+ final String fileSize;
+ if (message.isFileOrImage()
+ || transferable != null
+ || MessageUtils.unInitiatedButKnownSize(message)) {
+ final FileParams params = message.getFileParams();
+ fileSize = params.size != null ? UIHelper.filesizeToString(params.size) : null;
+ if (message.getStatus() == Message.STATUS_SEND_FAILED
+ || (transferable != null
+ && (transferable.getStatus() == Transferable.STATUS_FAILED
+ || transferable.getStatus()
+ == Transferable.STATUS_CANCELLED))) {
error = true;
+ } else {
+ error = message.getStatus() == Message.STATUS_SEND_FAILED;
}
+ } else {
+ fileSize = null;
+ error = message.getStatus() == Message.STATUS_SEND_FAILED;
}
- switch (message.getMergedStatus()) {
- case Message.STATUS_WAITING:
- info = getContext().getString(R.string.waiting);
- break;
- case Message.STATUS_UNSEND:
- if (transferable != null) {
- info = getContext().getString(R.string.sending_file, transferable.getProgress());
+ if (type == SENT) {
+ final @DrawableRes Integer receivedIndicator = getMessageStatusAsDrawable(message, mergedStatus);
+ if (receivedIndicator == null) {
+ viewHolder.indicatorReceived.setVisibility(View.INVISIBLE);
+ } else {
+ viewHolder.indicatorReceived.setImageResource(receivedIndicator);
+ if (mergedStatus == Message.STATUS_SEND_FAILED) {
+ setImageTintError(viewHolder.indicatorReceived);
} else {
- info = getContext().getString(R.string.sending);
+ setImageTint(viewHolder.indicatorReceived, bubbleColor);
}
- break;
- case Message.STATUS_OFFERED:
- info = getContext().getString(R.string.offering);
- break;
- case Message.STATUS_SEND_RECEIVED:
- case Message.STATUS_SEND_DISPLAYED:
- setImageTint(viewHolder.indicatorReceived, bubbleColor);
viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
- break;
- case Message.STATUS_SEND_FAILED:
- final String errorMessage = message.getErrorMessage();
- if (Message.ERROR_MESSAGE_CANCELLED.equals(errorMessage)) {
- info = getContext().getString(R.string.cancelled);
- } else if (errorMessage != null) {
- final String[] errorParts = errorMessage.split("\\u001f", 2);
- if (errorParts.length == 2) {
- switch (errorParts[0]) {
- case "file-too-large":
- info = getContext().getString(R.string.file_too_large);
- break;
- default:
- info = getContext().getString(R.string.send_failed);
- break;
- }
- } else {
- info = getContext().getString(R.string.send_failed);
- }
- } else {
- info = getContext().getString(R.string.send_failed);
- }
- error = true;
- break;
- default:
- if (mForceNames || multiReceived) {
- info = UIHelper.getMessageDisplayName(message);
- }
- break;
+ }
}
+ final var additionalStatusInfo = getAdditionalStatusInfo(message, mergedStatus);
+
if (error && type == SENT) {
- viewHolder.time.setTextColor(MaterialColors.getColor(viewHolder.time, com.google.android.material.R.attr.colorError));
+ viewHolder.time.setTextColor(
+ MaterialColors.getColor(
+ viewHolder.time, com.google.android.material.R.attr.colorError));
} else {
- setTextColor(viewHolder.time,bubbleColor);
+ setTextColor(viewHolder.time, bubbleColor);
}
if (message.getEncryption() == Message.ENCRYPTION_NONE) {
viewHolder.indicator.setVisibility(View.GONE);
} else {
boolean verified = false;
if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
- final FingerprintStatus status = message.getConversation()
- .getAccount().getAxolotlService().getFingerprintTrust(
- message.getFingerprint());
+ final FingerprintStatus status =
+ message.getConversation()
+ .getAccount()
+ .getAxolotlService()
+ .getFingerprintTrust(message.getFingerprint());
if (status != null && status.isVerified()) {
verified = true;
}
@@ -263,51 +245,124 @@ public class MessageAdapter extends ArrayAdapter<Message> {
} else {
viewHolder.indicator.setImageResource(R.drawable.ic_lock_24dp);
}
- setImageTint(viewHolder.indicator, bubbleColor);
+ if (error && type == SENT) {
+ setImageTintError(viewHolder.indicator);
+ } else {
+ setImageTint(viewHolder.indicator, bubbleColor);
+ }
viewHolder.indicator.setVisibility(View.VISIBLE);
}
- final String formattedTime = UIHelper.readableTimeDifferenceFull(getContext(), message.getMergedTimeSent());
+ if (viewHolder.edit_indicator != null) {
+ if (message.edited()) {
+ viewHolder.edit_indicator.setVisibility(View.VISIBLE);
+ if (error && type == SENT) {
+ setImageTintError(viewHolder.edit_indicator);
+ } else {
+ setImageTint(viewHolder.edit_indicator, bubbleColor);
+ }
+ } else {
+ viewHolder.edit_indicator.setVisibility(View.GONE);
+ }
+ }
+
+ final String formattedTime =
+ UIHelper.readableTimeDifferenceFull(getContext(), message.getMergedTimeSent());
final String bodyLanguage = message.getBodyLanguage();
- final String bodyLanguageInfo = bodyLanguage == null ? "" : String.format(" \u00B7 %s", bodyLanguage.toUpperCase(Locale.US));
+ final ImmutableList.Builder<String> timeInfoBuilder = new ImmutableList.Builder<>();
if (message.getStatus() <= Message.STATUS_RECEIVED) {
- if ((filesize != null) && (info != null)) {
- viewHolder.time.setText(formattedTime + " \u00B7 " + filesize + " \u00B7 " + info + bodyLanguageInfo);
- } else if ((filesize == null) && (info != null)) {
- viewHolder.time.setText(formattedTime + " \u00B7 " + info + bodyLanguageInfo);
- } else if ((filesize != null) && (info == null)) {
- viewHolder.time.setText(formattedTime + " \u00B7 " + filesize + bodyLanguageInfo);
- } else {
- viewHolder.time.setText(formattedTime + bodyLanguageInfo);
+ timeInfoBuilder.add(formattedTime);
+ if (fileSize != null) {
+ timeInfoBuilder.add(fileSize);
+ }
+ if (mForceNames || multiReceived) {
+ final String displayName = UIHelper.getMessageDisplayName(message);
+ if (displayName != null) {
+ timeInfoBuilder.add(displayName);
+ }
+ }
+ if (bodyLanguage != null) {
+ timeInfoBuilder.add(bodyLanguage.toUpperCase(Locale.US));
}
} else {
- if ((filesize != null) && (info != null)) {
- viewHolder.time.setText(filesize + " \u00B7 " + info + bodyLanguageInfo);
- } else if ((filesize == null) && (info != null)) {
- if (error) {
- viewHolder.time.setText(info + " \u00B7 " + formattedTime + bodyLanguageInfo);
+ if (bodyLanguage != null) {
+ timeInfoBuilder.add(bodyLanguage.toUpperCase(Locale.US));
+ }
+ if (fileSize != null) {
+ timeInfoBuilder.add(fileSize);
+ }
+ // for space reasons we display only 'additional status info' (send progress or concrete failure reason) or the time
+ if (additionalStatusInfo != null) {
+ timeInfoBuilder.add(additionalStatusInfo);
+ } else {
+ timeInfoBuilder.add(formattedTime);
+ }
+ }
+ final var timeInfo = timeInfoBuilder.build();
+ viewHolder.time.setText(Joiner.on(" \u00B7 ").join(timeInfo));
+ }
+
+ public static @DrawableRes Integer getMessageStatusAsDrawable(final Message message, final int status) {
+ final var transferable = message.getTransferable();
+ return switch (status) {
+ case Message.STATUS_WAITING -> R.drawable.ic_more_horiz_24dp;
+ case Message.STATUS_UNSEND -> transferable == null
+ ? null
+ : R.drawable.ic_upload_24dp;
+ case Message.STATUS_SEND -> R.drawable.ic_done_24dp;
+ case Message.STATUS_SEND_RECEIVED, Message.STATUS_SEND_DISPLAYED -> R
+ .drawable
+ .ic_done_all_24dp;
+ case Message.STATUS_SEND_FAILED -> {
+ final String errorMessage = message.getErrorMessage();
+ if (Message.ERROR_MESSAGE_CANCELLED.equals(errorMessage)) {
+ yield R.drawable.ic_cancel_24dp;
} else {
- viewHolder.time.setText(info);
+ yield R.drawable.ic_error_24dp;
}
- } else if ((filesize != null) && (info == null)) {
- viewHolder.time.setText(filesize + " \u00B7 " + formattedTime + bodyLanguageInfo);
+ }
+ case Message.STATUS_OFFERED -> R.drawable.ic_p2p_24dp;
+ default -> null;
+ };
+ }
+
+ @Nullable
+ private String getAdditionalStatusInfo(final Message message, final int mergedStatus) {
+ final String additionalStatusInfo;
+ if (mergedStatus == Message.STATUS_SEND_FAILED) {
+ final String errorMessage = Strings.nullToEmpty(message.getErrorMessage());
+ final String[] errorParts = errorMessage.split("\\u001f", 2);
+ if (errorParts.length == 2 && errorParts[0].equals("file-too-large")) {
+ additionalStatusInfo = getContext().getString(R.string.file_too_large);
} else {
- viewHolder.time.setText(formattedTime + bodyLanguageInfo);
+ additionalStatusInfo = null;
}
+ } else if (mergedStatus == Message.STATUS_UNSEND) {
+ final var transferable = message.getTransferable();
+ if (transferable == null) {
+ return null;
+ }
+ return getContext().getString(R.string.sending_file, transferable.getProgress());
+ } else {
+ additionalStatusInfo = null;
}
+ return additionalStatusInfo;
}
- private void displayInfoMessage(ViewHolder viewHolder, CharSequence text, final BubbleColor bubbleColor) {
+ private void displayInfoMessage(
+ ViewHolder viewHolder, CharSequence text, final BubbleColor bubbleColor) {
viewHolder.download_button.setVisibility(View.GONE);
viewHolder.audioPlayer.setVisibility(View.GONE);
viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.VISIBLE);
viewHolder.messageBody.setText(text);
- viewHolder.messageBody.setTextColor(bubbleToOnSurfaceVariant(viewHolder.messageBody,bubbleColor));
+ viewHolder.messageBody.setTextColor(
+ bubbleToOnSurfaceVariant(viewHolder.messageBody, bubbleColor));
viewHolder.messageBody.setTextIsSelectable(false);
}
- private void displayEmojiMessage(final ViewHolder viewHolder, final String body, final BubbleColor bubbleColor) {
+ private void displayEmojiMessage(
+ final ViewHolder viewHolder, final String body, final BubbleColor bubbleColor) {
viewHolder.download_button.setVisibility(View.GONE);
viewHolder.audioPlayer.setVisibility(View.GONE);
viewHolder.image.setVisibility(View.GONE);
@@ -315,14 +370,21 @@ public class MessageAdapter extends ArrayAdapter<Message> {
setTextColor(viewHolder.messageBody, bubbleColor);
final Spannable span = new SpannableString(body);
float size = Emoticons.isEmoji(body) ? 3.0f : 2.0f;
- span.setSpan(new RelativeSizeSpan(size), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ span.setSpan(
+ new RelativeSizeSpan(size), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
viewHolder.messageBody.setText(span);
}
- private void applyQuoteSpan(final TextView textView, SpannableStringBuilder body, int start, int end, final BubbleColor bubbleColor) {
+ private void applyQuoteSpan(
+ final TextView textView,
+ SpannableStringBuilder body,
+ int start,
+ int end,
+ final BubbleColor bubbleColor) {
if (start > 1 && !"\n\n".equals(body.subSequence(start - 2, start).toString())) {
body.insert(start++, "\n");
- body.setSpan(new DividerSpan(false), start - 2, start, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ body.setSpan(
+ new DividerSpan(false), start - 2, start, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
end++;
}
if (end < body.length() - 1 && !"\n\n".equals(body.subSequence(end, end + 2).toString())) {
@@ -330,14 +392,21 @@ public class MessageAdapter extends ArrayAdapter<Message> {
body.setSpan(new DividerSpan(false), end, end + 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
- body.setSpan(new QuoteSpan(bubbleToOnSurfaceVariant(textView, bubbleColor), metrics), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ body.setSpan(
+ new QuoteSpan(bubbleToOnSurfaceVariant(textView, bubbleColor), metrics),
+ start,
+ end,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
/**
- * Applies QuoteSpan to group of lines which starts with > or Β» characters.
- * Appends likebreaks and applies DividerSpan to them to show a padding between quote and text.
+ * Applies QuoteSpan to group of lines which starts with > or Β» characters. Appends likebreaks
+ * and applies DividerSpan to them to show a padding between quote and text.
*/
- private boolean handleTextQuotes(final TextView textView, final SpannableStringBuilder body, final BubbleColor bubbleColor) {
+ private boolean handleTextQuotes(
+ final TextView textView,
+ final SpannableStringBuilder body,
+ final BubbleColor bubbleColor) {
boolean startsWithQuote = false;
int quoteDepth = 1;
while (QuoteHelper.bodyContainsQuoteStart(body) && quoteDepth <= Config.QUOTE_MAX_DEPTH) {
@@ -388,7 +457,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
return startsWithQuote;
}
- private void displayTextMessage(final ViewHolder viewHolder, final Message message, final BubbleColor bubbleColor, int type) {
+ private void displayTextMessage(
+ final ViewHolder viewHolder,
+ final Message message,
+ final BubbleColor bubbleColor,
+ int type) {
viewHolder.download_button.setVisibility(View.GONE);
viewHolder.image.setVisibility(View.GONE);
viewHolder.audioPlayer.setVisibility(View.GONE);
@@ -407,7 +480,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
body = new SpannableStringBuilder(body, 0, Config.MAX_DISPLAY_MESSAGE_CHARS);
body.append("\u2026");
}
- Message.MergeSeparator[] mergeSeparators = body.getSpans(0, body.length(), Message.MergeSeparator.class);
+ Message.MergeSeparator[] mergeSeparators =
+ body.getSpans(0, body.length(), Message.MergeSeparator.class);
for (Message.MergeSeparator mergeSeparator : mergeSeparators) {
int start = body.getSpanStart(mergeSeparator);
int end = body.getSpanEnd(mergeSeparator);
@@ -416,7 +490,10 @@ public class MessageAdapter extends ArrayAdapter<Message> {
boolean startsWithQuote = handleTextQuotes(viewHolder.messageBody, body, bubbleColor);
if (!message.isPrivateMessage()) {
if (hasMeCommand) {
- body.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(),
+ body.setSpan(
+ new StyleSpan(Typeface.BOLD_ITALIC),
+ 0,
+ nick.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} else {
@@ -425,37 +502,66 @@ public class MessageAdapter extends ArrayAdapter<Message> {
privateMarker = activity.getString(R.string.private_message);
} else {
Jid cp = message.getCounterpart();
- privateMarker = activity.getString(R.string.private_message_to, Strings.nullToEmpty(cp == null ? null : cp.getResource()));
+ privateMarker =
+ activity.getString(
+ R.string.private_message_to,
+ Strings.nullToEmpty(cp == null ? null : cp.getResource()));
}
body.insert(0, privateMarker);
int privateMarkerIndex = privateMarker.length();
if (startsWithQuote) {
body.insert(privateMarkerIndex, "\n\n");
- body.setSpan(new DividerSpan(false), privateMarkerIndex, privateMarkerIndex + 2,
+ body.setSpan(
+ new DividerSpan(false),
+ privateMarkerIndex,
+ privateMarkerIndex + 2,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
body.insert(privateMarkerIndex, " ");
}
- body.setSpan(new ForegroundColorSpan(bubbleToOnSurfaceVariant(viewHolder.messageBody, bubbleColor)), 0, privateMarkerIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- body.setSpan(new StyleSpan(Typeface.BOLD), 0, privateMarkerIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ body.setSpan(
+ new ForegroundColorSpan(
+ bubbleToOnSurfaceVariant(viewHolder.messageBody, bubbleColor)),
+ 0,
+ privateMarkerIndex,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ body.setSpan(
+ new StyleSpan(Typeface.BOLD),
+ 0,
+ privateMarkerIndex,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
if (hasMeCommand) {
- body.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarkerIndex + 1,
- privateMarkerIndex + 1 + nick.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ body.setSpan(
+ new StyleSpan(Typeface.BOLD_ITALIC),
+ privateMarkerIndex + 1,
+ privateMarkerIndex + 1 + nick.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
- if (message.getConversation().getMode() == Conversation.MODE_MULTI && message.getStatus() == Message.STATUS_RECEIVED) {
+ if (message.getConversation().getMode() == Conversation.MODE_MULTI
+ && message.getStatus() == Message.STATUS_RECEIVED) {
if (message.getConversation() instanceof Conversation conversation) {
- Pattern pattern = NotificationService.generateNickHighlightPattern(conversation.getMucOptions().getActualNick());
+ Pattern pattern =
+ NotificationService.generateNickHighlightPattern(
+ conversation.getMucOptions().getActualNick());
Matcher matcher = pattern.matcher(body);
while (matcher.find()) {
- body.setSpan(new StyleSpan(Typeface.BOLD), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ body.setSpan(
+ new StyleSpan(Typeface.BOLD),
+ matcher.start(),
+ matcher.end(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
Matcher matcher = Emoticons.getEmojiPattern(body).matcher(body);
while (matcher.find()) {
if (matcher.start() < matcher.end()) {
- body.setSpan(new RelativeSizeSpan(1.2f), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ body.setSpan(
+ new RelativeSizeSpan(1.2f),
+ matcher.start(),
+ matcher.end(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
@@ -473,7 +579,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
}
- private void displayDownloadableMessage(ViewHolder viewHolder, final Message message, String text, final BubbleColor bubbleColor) {
+ private void displayDownloadableMessage(
+ ViewHolder viewHolder,
+ final Message message,
+ String text,
+ final BubbleColor bubbleColor) {
toggleWhisperInfo(viewHolder, message, bubbleColor);
viewHolder.image.setVisibility(View.GONE);
viewHolder.audioPlayer.setVisibility(View.GONE);
@@ -482,22 +592,28 @@ public class MessageAdapter extends ArrayAdapter<Message> {
final var attachment = Attachment.of(message);
final @DrawableRes int imageResource = MediaAdapter.getImageDrawable(attachment);
viewHolder.download_button.setIconResource(imageResource);
- viewHolder.download_button.setOnClickListener(v -> ConversationFragment.downloadFile(activity, message));
+ viewHolder.download_button.setOnClickListener(
+ v -> ConversationFragment.downloadFile(activity, message));
}
- private void displayOpenableMessage(ViewHolder viewHolder, final Message message, final BubbleColor bubbleColor) {
+ private void displayOpenableMessage(
+ ViewHolder viewHolder, final Message message, final BubbleColor bubbleColor) {
toggleWhisperInfo(viewHolder, message, bubbleColor);
viewHolder.image.setVisibility(View.GONE);
viewHolder.audioPlayer.setVisibility(View.GONE);
viewHolder.download_button.setVisibility(View.VISIBLE);
- viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message)));
+ viewHolder.download_button.setText(
+ activity.getString(
+ R.string.open_x_file,
+ UIHelper.getFileDescriptionString(activity, message)));
final var attachment = Attachment.of(message);
final @DrawableRes int imageResource = MediaAdapter.getImageDrawable(attachment);
viewHolder.download_button.setIconResource(imageResource);
viewHolder.download_button.setOnClickListener(v -> openDownloadable(message));
}
- private void displayLocationMessage(ViewHolder viewHolder, final Message message, final BubbleColor bubbleColor) {
+ private void displayLocationMessage(
+ ViewHolder viewHolder, final Message message, final BubbleColor bubbleColor) {
toggleWhisperInfo(viewHolder, message, bubbleColor);
viewHolder.image.setVisibility(View.GONE);
viewHolder.audioPlayer.setVisibility(View.GONE);
@@ -509,7 +625,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.download_button.setOnClickListener(v -> showLocation(message));
}
- private void displayAudioMessage(ViewHolder viewHolder, Message message, final BubbleColor bubbleColor) {
+ private void displayAudioMessage(
+ ViewHolder viewHolder, Message message, final BubbleColor bubbleColor) {
toggleWhisperInfo(viewHolder, message, bubbleColor);
viewHolder.image.setVisibility(View.GONE);
viewHolder.download_button.setVisibility(View.GONE);
@@ -519,7 +636,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
this.audioPlayer.init(audioPlayer, message);
}
- private void displayMediaPreviewMessage(ViewHolder viewHolder, final Message message, final BubbleColor bubbleColor) {
+ private void displayMediaPreviewMessage(
+ ViewHolder viewHolder, final Message message, final BubbleColor bubbleColor) {
toggleWhisperInfo(viewHolder, message, bubbleColor);
viewHolder.download_button.setVisibility(View.GONE);
viewHolder.audioPlayer.setVisibility(View.GONE);
@@ -541,25 +659,39 @@ public class MessageAdapter extends ArrayAdapter<Message> {
scaledW = (int) target;
scaledH = (int) (params.height / ((double) params.width / target));
}
- final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(scaledW, scaledH);
+ final LinearLayout.LayoutParams layoutParams =
+ new LinearLayout.LayoutParams(scaledW, scaledH);
layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4));
viewHolder.image.setLayoutParams(layoutParams);
activity.loadBitmap(message, viewHolder.image);
viewHolder.image.setOnClickListener(v -> openDownloadable(message));
}
- private void toggleWhisperInfo(ViewHolder viewHolder, final Message message, final BubbleColor bubbleColor) {
+ private void toggleWhisperInfo(
+ ViewHolder viewHolder, final Message message, final BubbleColor bubbleColor) {
if (message.isPrivateMessage()) {
final String privateMarker;
if (message.getStatus() <= Message.STATUS_RECEIVED) {
privateMarker = activity.getString(R.string.private_message);
} else {
Jid cp = message.getCounterpart();
- privateMarker = activity.getString(R.string.private_message_to, Strings.nullToEmpty(cp == null ? null : cp.getResource()));
+ privateMarker =
+ activity.getString(
+ R.string.private_message_to,
+ Strings.nullToEmpty(cp == null ? null : cp.getResource()));
}
final SpannableString body = new SpannableString(privateMarker);
- body.setSpan(new ForegroundColorSpan(bubbleToOnSurfaceVariant(viewHolder.messageBody, bubbleColor)), 0, privateMarker.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- body.setSpan(new StyleSpan(Typeface.BOLD), 0, privateMarker.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ body.setSpan(
+ new ForegroundColorSpan(
+ bubbleToOnSurfaceVariant(viewHolder.messageBody, bubbleColor)),
+ 0,
+ privateMarker.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ body.setSpan(
+ new StyleSpan(Typeface.BOLD),
+ 0,
+ privateMarker.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
viewHolder.messageBody.setText(body);
viewHolder.messageBody.setVisibility(View.VISIBLE);
} else {
@@ -577,11 +709,19 @@ public class MessageAdapter extends ArrayAdapter<Message> {
timestamp = System.currentTimeMillis();
}
conversation.messagesLoaded.set(true);
- MessageArchiveService.Query query = activity.xmppConnectionService.getMessageArchiveService().query(conversation, new MamReference(0), timestamp, false);
+ MessageArchiveService.Query query =
+ activity.xmppConnectionService
+ .getMessageArchiveService()
+ .query(conversation, new MamReference(0), timestamp, false);
if (query != null) {
- Toast.makeText(activity, R.string.fetching_history_from_server, Toast.LENGTH_LONG).show();
+ Toast.makeText(activity, R.string.fetching_history_from_server, Toast.LENGTH_LONG)
+ .show();
} else {
- Toast.makeText(activity, R.string.not_fetching_history_retention_period, Toast.LENGTH_SHORT).show();
+ Toast.makeText(
+ activity,
+ R.string.not_fetching_history_retention_period,
+ Toast.LENGTH_SHORT)
+ .show();
}
}
@@ -589,7 +729,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
public View getView(final int position, View view, final @NonNull ViewGroup parent) {
final Message message = getItem(position);
final boolean omemoEncryption = message.getEncryption() == Message.ENCRYPTION_AXOLOTL;
- final boolean isInValidSession = message.isValidInSession() && (!omemoEncryption || message.isTrusted());
+ final boolean isInValidSession =
+ message.isValidInSession() && (!omemoEncryption || message.isTrusted());
final Conversational conversation = message.getConversation();
final Account account = conversation.getAccount();
final int type = getItemViewType(position);
@@ -598,18 +739,24 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder = new ViewHolder();
switch (type) {
case DATE_SEPARATOR:
- view = activity.getLayoutInflater().inflate(R.layout.item_message_date_bubble, parent, false);
+ view =
+ activity.getLayoutInflater()
+ .inflate(R.layout.item_message_date_bubble, parent, false);
viewHolder.status_message = view.findViewById(R.id.message_body);
viewHolder.message_box = view.findViewById(R.id.message_box);
break;
case RTP_SESSION:
- view = activity.getLayoutInflater().inflate(R.layout.item_message_rtp_session, parent, false);
+ view =
+ activity.getLayoutInflater()
+ .inflate(R.layout.item_message_rtp_session, parent, false);
viewHolder.status_message = view.findViewById(R.id.message_body);
viewHolder.message_box = view.findViewById(R.id.message_box);
viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received);
break;
case SENT:
- view = activity.getLayoutInflater().inflate(R.layout.item_message_sent, parent, false);
+ view =
+ activity.getLayoutInflater()
+ .inflate(R.layout.item_message_sent, parent, false);
viewHolder.message_box = view.findViewById(R.id.message_box);
viewHolder.contact_picture = view.findViewById(R.id.message_photo);
viewHolder.download_button = view.findViewById(R.id.download_button);
@@ -622,7 +769,9 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.audioPlayer = view.findViewById(R.id.audio_player);
break;
case RECEIVED:
- view = activity.getLayoutInflater().inflate(R.layout.item_message_received, parent, false);
+ view =
+ activity.getLayoutInflater()
+ .inflate(R.layout.item_message_received, parent, false);
viewHolder.message_box = view.findViewById(R.id.message_box);
viewHolder.contact_picture = view.findViewById(R.id.message_photo);
viewHolder.download_button = view.findViewById(R.id.download_button);
@@ -636,7 +785,9 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.audioPlayer = view.findViewById(R.id.audio_player);
break;
case STATUS:
- view = activity.getLayoutInflater().inflate(R.layout.item_message_status, parent, false);
+ view =
+ activity.getLayoutInflater()
+ .inflate(R.layout.item_message_status, parent, false);
viewHolder.contact_picture = view.findViewById(R.id.message_photo);
viewHolder.status_message = view.findViewById(R.id.status_message);
viewHolder.load_more_messages = view.findViewById(R.id.load_more_messages);
@@ -670,13 +821,17 @@ public class MessageAdapter extends ArrayAdapter<Message> {
} else if (UIHelper.yesterday(message.getTimeSent())) {
viewHolder.status_message.setText(R.string.yesterday);
} else {
- viewHolder.status_message.setText(DateUtils.formatDateTime(activity, message.getTimeSent(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR));
+ viewHolder.status_message.setText(
+ DateUtils.formatDateTime(
+ activity,
+ message.getTimeSent(),
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR));
}
if (colorfulBackground) {
- setBackgroundTint(viewHolder.message_box,BubbleColor.PRIMARY);
+ setBackgroundTint(viewHolder.message_box, BubbleColor.PRIMARY);
setTextColor(viewHolder.status_message, BubbleColor.PRIMARY);
} else {
- setBackgroundTint(viewHolder.message_box,BubbleColor.SURFACE);
+ setBackgroundTint(viewHolder.message_box, BubbleColor.SURFACE);
setTextColor(viewHolder.status_message, BubbleColor.SURFACE);
}
return view;
@@ -686,36 +841,56 @@ public class MessageAdapter extends ArrayAdapter<Message> {
final long duration = rtpSessionStatus.duration;
if (received) {
if (duration > 0) {
- viewHolder.status_message.setText(activity.getString(R.string.incoming_call_duration_timestamp, TimeFrameUtils.resolve(activity, duration), UIHelper.readableTimeDifferenceFull(activity, message.getTimeSent())));
+ viewHolder.status_message.setText(
+ activity.getString(
+ R.string.incoming_call_duration_timestamp,
+ TimeFrameUtils.resolve(activity, duration),
+ UIHelper.readableTimeDifferenceFull(
+ activity, message.getTimeSent())));
} else if (rtpSessionStatus.successful) {
viewHolder.status_message.setText(R.string.incoming_call);
} else {
- viewHolder.status_message.setText(activity.getString(R.string.missed_call_timestamp, UIHelper.readableTimeDifferenceFull(activity, message.getTimeSent())));
+ viewHolder.status_message.setText(
+ activity.getString(
+ R.string.missed_call_timestamp,
+ UIHelper.readableTimeDifferenceFull(
+ activity, message.getTimeSent())));
}
} else {
if (duration > 0) {
- viewHolder.status_message.setText(activity.getString(R.string.outgoing_call_duration_timestamp, TimeFrameUtils.resolve(activity, duration), UIHelper.readableTimeDifferenceFull(activity, message.getTimeSent())));
+ viewHolder.status_message.setText(
+ activity.getString(
+ R.string.outgoing_call_duration_timestamp,
+ TimeFrameUtils.resolve(activity, duration),
+ UIHelper.readableTimeDifferenceFull(
+ activity, message.getTimeSent())));
} else {
- viewHolder.status_message.setText(activity.getString(R.string.outgoing_call_timestamp, UIHelper.readableTimeDifferenceFull(activity, message.getTimeSent())));
+ viewHolder.status_message.setText(
+ activity.getString(
+ R.string.outgoing_call_timestamp,
+ UIHelper.readableTimeDifferenceFull(
+ activity, message.getTimeSent())));
}
}
if (colorfulBackground) {
- setBackgroundTint(viewHolder.message_box,BubbleColor.SECONDARY);
+ setBackgroundTint(viewHolder.message_box, BubbleColor.SECONDARY);
setTextColor(viewHolder.status_message, BubbleColor.SECONDARY);
setImageTint(viewHolder.indicatorReceived, BubbleColor.SECONDARY);
} else {
- setBackgroundTint(viewHolder.message_box,BubbleColor.SURFACE);
+ setBackgroundTint(viewHolder.message_box, BubbleColor.SURFACE);
setTextColor(viewHolder.status_message, BubbleColor.SURFACE);
setImageTint(viewHolder.indicatorReceived, BubbleColor.SURFACE);
}
- viewHolder.indicatorReceived.setImageResource(RtpSessionStatus.getDrawable(received, rtpSessionStatus.successful));
+ viewHolder.indicatorReceived.setImageResource(
+ RtpSessionStatus.getDrawable(received, rtpSessionStatus.successful));
return view;
} else if (type == STATUS) {
if ("LOAD_MORE".equals(message.getBody())) {
viewHolder.status_message.setVisibility(View.GONE);
viewHolder.contact_picture.setVisibility(View.GONE);
viewHolder.load_more_messages.setVisibility(View.VISIBLE);
- viewHolder.load_more_messages.setOnClickListener(v -> loadMoreMessages((Conversation) message.getConversation()));
+ viewHolder.load_more_messages.setOnClickListener(
+ v -> loadMoreMessages((Conversation) message.getConversation()));
} else {
viewHolder.status_message.setVisibility(View.VISIBLE);
viewHolder.load_more_messages.setVisibility(View.GONE);
@@ -723,10 +898,15 @@ public class MessageAdapter extends ArrayAdapter<Message> {
boolean showAvatar;
if (conversation.getMode() == Conversation.MODE_SINGLE) {
showAvatar = true;
- AvatarWorkerTask.loadAvatar(message, viewHolder.contact_picture, R.dimen.avatar_on_status_message);
- } else if (message.getCounterpart() != null || message.getTrueCounterpart() != null || (message.getCounterparts() != null && message.getCounterparts().size() > 0)) {
+ AvatarWorkerTask.loadAvatar(
+ message, viewHolder.contact_picture, R.dimen.avatar_on_status_message);
+ } else if (message.getCounterpart() != null
+ || message.getTrueCounterpart() != null
+ || (message.getCounterparts() != null
+ && message.getCounterparts().size() > 0)) {
showAvatar = true;
- AvatarWorkerTask.loadAvatar(message, viewHolder.contact_picture, R.dimen.avatar_on_status_message);
+ AvatarWorkerTask.loadAvatar(
+ message, viewHolder.contact_picture, R.dimen.avatar_on_status_message);
} else {
showAvatar = false;
}
@@ -744,34 +924,58 @@ public class MessageAdapter extends ArrayAdapter<Message> {
resetClickListener(viewHolder.message_box, viewHolder.messageBody);
- viewHolder.contact_picture.setOnClickListener(v -> {
- if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
- MessageAdapter.this.mOnContactPictureClickedListener
- .onContactPictureClicked(message);
- }
-
- });
- viewHolder.contact_picture.setOnLongClickListener(v -> {
- if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
- MessageAdapter.this.mOnContactPictureLongClickedListener
- .onContactPictureLongClicked(v, message);
- return true;
- } else {
- return false;
- }
- });
+ viewHolder.contact_picture.setOnClickListener(
+ v -> {
+ if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
+ MessageAdapter.this.mOnContactPictureClickedListener
+ .onContactPictureClicked(message);
+ }
+ });
+ viewHolder.contact_picture.setOnLongClickListener(
+ v -> {
+ if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
+ MessageAdapter.this.mOnContactPictureLongClickedListener
+ .onContactPictureLongClicked(v, message);
+ return true;
+ } else {
+ return false;
+ }
+ });
final Transferable transferable = message.getTransferable();
final boolean unInitiatedButKnownSize = MessageUtils.unInitiatedButKnownSize(message);
- if (unInitiatedButKnownSize || message.isDeleted() || (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING)) {
- if (unInitiatedButKnownSize || transferable != null && transferable.getStatus() == Transferable.STATUS_OFFER) {
- displayDownloadableMessage(viewHolder, message, activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message)), bubbleColor);
- } else if (transferable != null && transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) {
- displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)), bubbleColor);
+ if (unInitiatedButKnownSize
+ || message.isDeleted()
+ || (transferable != null
+ && transferable.getStatus() != Transferable.STATUS_UPLOADING)) {
+ if (unInitiatedButKnownSize
+ || transferable != null
+ && transferable.getStatus() == Transferable.STATUS_OFFER) {
+ displayDownloadableMessage(
+ viewHolder,
+ message,
+ activity.getString(
+ R.string.download_x_file,
+ UIHelper.getFileDescriptionString(activity, message)),
+ bubbleColor);
+ } else if (transferable != null
+ && transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) {
+ displayDownloadableMessage(
+ viewHolder,
+ message,
+ activity.getString(
+ R.string.check_x_filesize,
+ UIHelper.getFileDescriptionString(activity, message)),
+ bubbleColor);
} else {
- displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first, bubbleColor);
+ displayInfoMessage(
+ viewHolder,
+ UIHelper.getMessagePreview(activity, message).first,
+ bubbleColor);
}
- } else if (message.isFileOrImage() && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
+ } else if (message.isFileOrImage()
+ && message.getEncryption() != Message.ENCRYPTION_PGP
+ && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
if (message.getFileParams().width > 0 && message.getFileParams().height > 0) {
displayMediaPreviewMessage(viewHolder, message, bubbleColor);
} else if (message.getFileParams().runtime > 0) {
@@ -781,22 +985,33 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
if (account.isPgpDecryptionServiceConnected()) {
- if (conversation instanceof Conversation && !account.hasPendingPgpIntent((Conversation) conversation)) {
- displayInfoMessage(viewHolder, activity.getString(R.string.message_decrypting), bubbleColor);
+ if (conversation instanceof Conversation
+ && !account.hasPendingPgpIntent((Conversation) conversation)) {
+ displayInfoMessage(
+ viewHolder,
+ activity.getString(R.string.message_decrypting),
+ bubbleColor);
} else {
- displayInfoMessage(viewHolder, activity.getString(R.string.pgp_message), bubbleColor);
+ displayInfoMessage(
+ viewHolder, activity.getString(R.string.pgp_message), bubbleColor);
}
} else {
- displayInfoMessage(viewHolder, activity.getString(R.string.install_openkeychain), bubbleColor);
+ displayInfoMessage(
+ viewHolder, activity.getString(R.string.install_openkeychain), bubbleColor);
viewHolder.message_box.setOnClickListener(this::promptOpenKeychainInstall);
viewHolder.messageBody.setOnClickListener(this::promptOpenKeychainInstall);
}
} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
- displayInfoMessage(viewHolder, activity.getString(R.string.decryption_failed), bubbleColor);
+ displayInfoMessage(
+ viewHolder, activity.getString(R.string.decryption_failed), bubbleColor);
} else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) {
- displayInfoMessage(viewHolder, activity.getString(R.string.not_encrypted_for_this_device), bubbleColor);
+ displayInfoMessage(
+ viewHolder,
+ activity.getString(R.string.not_encrypted_for_this_device),
+ bubbleColor);
} else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) {
- displayInfoMessage(viewHolder, activity.getString(R.string.omemo_decryption_failed), bubbleColor);
+ displayInfoMessage(
+ viewHolder, activity.getString(R.string.omemo_decryption_failed), bubbleColor);
} else {
if (message.isGeoUri()) {
displayLocationMessage(viewHolder, message, bubbleColor);
@@ -805,16 +1020,20 @@ public class MessageAdapter extends ArrayAdapter<Message> {
} else if (message.treatAsDownloadable()) {
try {
final URI uri = new URI(message.getBody());
- displayDownloadableMessage(viewHolder,
+ displayDownloadableMessage(
+ viewHolder,
message,
- activity.getString(R.string.check_x_filesize_on_host,
+ activity.getString(
+ R.string.check_x_filesize_on_host,
UIHelper.getFileDescriptionString(activity, message),
uri.getHost()),
bubbleColor);
} catch (Exception e) {
- displayDownloadableMessage(viewHolder,
+ displayDownloadableMessage(
+ viewHolder,
message,
- activity.getString(R.string.check_x_filesize,
+ activity.getString(
+ R.string.check_x_filesize,
UIHelper.getFileDescriptionString(activity, message)),
bubbleColor);
}
@@ -835,7 +1054,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
if (omemoEncryption && !message.isTrusted()) {
viewHolder.encryption.setText(R.string.not_trusted);
} else {
- viewHolder.encryption.setText(CryptoHelper.encryptionTypeToText(message.getEncryption()));
+ viewHolder.encryption.setText(
+ CryptoHelper.encryptionTypeToText(message.getEncryption()));
}
}
}
@@ -866,12 +1086,19 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
public void openDownloadable(Message message) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
+ && ContextCompat.checkSelfPermission(
+ activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
ConversationFragment.registerPendingMessage(activity, message);
- ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, ConversationsActivity.REQUEST_OPEN_MESSAGE);
+ ActivityCompat.requestPermissions(
+ activity,
+ new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ ConversationsActivity.REQUEST_OPEN_MESSAGE);
return;
}
- final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
+ final DownloadableFile file =
+ activity.xmppConnectionService.getFileBackend().getFile(message);
ViewUtil.view(activity, file);
}
@@ -882,7 +1109,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
return;
}
}
- Toast.makeText(activity, R.string.no_application_found_to_display_location, Toast.LENGTH_SHORT).show();
+ Toast.makeText(
+ activity,
+ R.string.no_application_found_to_display_location,
+ Toast.LENGTH_SHORT)
+ .show();
}
public void updatePreferences() {
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?colorControlNormal"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M160,800Q127,800 103.5,776.5Q80,753 80,720L80,160Q80,127 103.5,103.5Q127,80 160,80L360,80Q393,80 416.5,103.5Q440,127 440,160L440,360L360,360L360,280L160,280L160,600L440,600L440,720Q440,753 416.5,776.5Q393,800 360,800L160,800ZM600,880Q567,880 543.5,856.5Q520,833 520,800L520,600L600,600L600,680L800,680L800,360L520,360L520,240Q520,207 543.5,183.5Q567,160 600,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,800Q880,833 856.5,856.5Q833,880 800,880L600,880ZM320,520Q303,520 291.5,508.5Q280,497 280,480Q280,463 291.5,451.5Q303,440 320,440Q337,440 348.5,451.5Q360,463 360,480Q360,497 348.5,508.5Q337,520 320,520ZM480,520Q463,520 451.5,508.5Q440,497 440,480Q440,463 451.5,451.5Q463,440 480,440Q497,440 508.5,451.5Q520,463 520,480Q520,497 508.5,508.5Q497,520 480,520ZM640,520Q623,520 611.5,508.5Q600,497 600,480Q600,463 611.5,451.5Q623,440 640,440Q657,440 668.5,451.5Q680,463 680,480Q680,497 668.5,508.5Q657,520 640,520Z" />
+</vector>