Federated Mentions

- Federated mention chip
- Federated message avatars
- Helper functions

Signed-off-by: Julius Linus <julius.linus@nextcloud.com>
This commit is contained in:
Julius Linus 2024-03-08 11:21:09 -06:00 committed by Andy Scherzinger
parent 22de77896a
commit 870ef03d61
16 changed files with 181 additions and 36 deletions

View File

@ -58,8 +58,10 @@ public class MentionAutocompleteItem extends AbstractFlexibleItem<ParticipantIte
public static final String SOURCE_GUESTS = "guests"; public static final String SOURCE_GUESTS = "guests";
public static final String SOURCE_GROUPS = "groups"; public static final String SOURCE_GROUPS = "groups";
public static final String SOURCE_FEDERATION = "federated_users";
private String source; private String source;
private final String mentionId;
private final String objectId; private final String objectId;
private final String displayName; private final String displayName;
private final String status; private final String status;
@ -67,12 +69,14 @@ public class MentionAutocompleteItem extends AbstractFlexibleItem<ParticipantIte
private final String statusMessage; private final String statusMessage;
private final User currentUser; private final User currentUser;
private final Context context; private final Context context;
private final String roomToken;
private final ViewThemeUtils viewThemeUtils; private final ViewThemeUtils viewThemeUtils;
public MentionAutocompleteItem( public MentionAutocompleteItem(
Mention mention, Mention mention,
User currentUser, User currentUser,
Context activityContext, ViewThemeUtils viewThemeUtils) { Context activityContext, String roomToken, ViewThemeUtils viewThemeUtils) {
this.mentionId = mention.getMentionId();
this.objectId = mention.getId(); this.objectId = mention.getId();
this.displayName = mention.getLabel(); this.displayName = mention.getLabel();
this.source = mention.getSource(); this.source = mention.getSource();
@ -82,6 +86,7 @@ public class MentionAutocompleteItem extends AbstractFlexibleItem<ParticipantIte
this.currentUser = currentUser; this.currentUser = currentUser;
this.context = activityContext; this.context = activityContext;
this.viewThemeUtils = viewThemeUtils; this.viewThemeUtils = viewThemeUtils;
this.roomToken = roomToken;
} }
public String getSource() { public String getSource() {
@ -92,6 +97,10 @@ public class MentionAutocompleteItem extends AbstractFlexibleItem<ParticipantIte
this.source = source; this.source = source;
} }
public String getMentionId() {
return mentionId;
}
public String getObjectId() { public String getObjectId() {
return objectId; return objectId;
} }
@ -100,6 +109,10 @@ public class MentionAutocompleteItem extends AbstractFlexibleItem<ParticipantIte
return displayName; return displayName;
} }
public String getRoomToken() {
return roomToken;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (o instanceof MentionAutocompleteItem inItem) { if (o instanceof MentionAutocompleteItem inItem) {
@ -147,25 +160,40 @@ public class MentionAutocompleteItem extends AbstractFlexibleItem<ParticipantIte
holder.binding.secondaryText.setText("@" + objectId); holder.binding.secondaryText.setText("@" + objectId);
} }
if (SOURCE_CALLS.equals(source) || SOURCE_GROUPS.equals(source)) { String avatarId = objectId;
switch (source) {
case SOURCE_CALLS: {}
case SOURCE_GROUPS: {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ImageViewExtensionsKt.loadUserAvatar( ImageViewExtensionsKt.loadUserAvatar(
holder.binding.avatarView, holder.binding.avatarView,
viewThemeUtils.talk.themePlaceholderAvatar( viewThemeUtils.talk.themePlaceholderAvatar(
holder.binding.avatarView, holder.binding.avatarView,
R.drawable.ic_avatar_group R.drawable.ic_avatar_group));
)
);
} else { } else {
ImageViewExtensionsKt.loadUserAvatar(holder.binding.avatarView, R.drawable.ic_circular_group); ImageViewExtensionsKt.loadUserAvatar(holder.binding.avatarView, R.drawable.ic_circular_group);
} }
} else { break;
String avatarId = objectId; }
if (SOURCE_GUESTS.equals(source)) { case SOURCE_FEDERATION: {
int darkTheme = (DisplayUtils.isDarkModeOn(this.context))? 1 : 0;
ImageViewExtensionsKt.loadFederatedUserAvatar(holder.binding.avatarView,
currentUser,
Objects.requireNonNull(currentUser.getBaseUrl()),
roomToken,
avatarId,
darkTheme,
true,
false);
break;
}
case SOURCE_GUESTS: {
avatarId = displayName; avatarId = displayName;
} }
default: {
ImageViewExtensionsKt.loadUserAvatar(holder.binding.avatarView, currentUser, avatarId, true, false); ImageViewExtensionsKt.loadUserAvatar(holder.binding.avatarView, currentUser, avatarId, true, false);
} }
}
drawStatus(holder); drawStatus(holder);
} }

View File

@ -37,6 +37,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedA
import com.nextcloud.talk.databinding.ItemCustomIncomingLinkPreviewMessageBinding import com.nextcloud.talk.databinding.ItemCustomIncomingLinkPreviewMessageBinding
import com.nextcloud.talk.extensions.loadBotsAvatar import com.nextcloud.talk.extensions.loadBotsAvatar
import com.nextcloud.talk.extensions.loadChangelogBotAvatar import com.nextcloud.talk.extensions.loadChangelogBotAvatar
import com.nextcloud.talk.extensions.loadFederatedUserAvatar
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
@ -172,6 +173,8 @@ class IncomingLinkPreviewMessageViewHolder(incomingView: View, payload: Any) :
binding.messageUserAvatar.loadChangelogBotAvatar() binding.messageUserAvatar.loadChangelogBotAvatar()
} else if (message.actorType == "bots") { } else if (message.actorType == "bots") {
binding.messageUserAvatar.loadBotsAvatar() binding.messageUserAvatar.loadBotsAvatar()
} else if (message.actorType == "federated_users" && message.messageParameters?.get("actor") != null) {
binding.messageUserAvatar.loadFederatedUserAvatar(message)
} }
} }

View File

@ -47,6 +47,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedA
import com.nextcloud.talk.databinding.ItemCustomIncomingLocationMessageBinding import com.nextcloud.talk.databinding.ItemCustomIncomingLocationMessageBinding
import com.nextcloud.talk.extensions.loadBotsAvatar import com.nextcloud.talk.extensions.loadBotsAvatar
import com.nextcloud.talk.extensions.loadChangelogBotAvatar import com.nextcloud.talk.extensions.loadChangelogBotAvatar
import com.nextcloud.talk.extensions.loadFederatedUserAvatar
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
@ -148,6 +149,8 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) :
binding.messageUserAvatar.loadChangelogBotAvatar() binding.messageUserAvatar.loadChangelogBotAvatar()
} else if (message.actorType == "bots") { } else if (message.actorType == "bots") {
binding.messageUserAvatar.loadBotsAvatar() binding.messageUserAvatar.loadBotsAvatar()
} else if (message.actorType == "federated_users" && message.messageParameters?.get("actor") != null) {
binding.messageUserAvatar.loadFederatedUserAvatar(message)
} }
} else { } else {
if (message.isOneToOneConversation || message.isFormerOneToOneConversation) { if (message.isOneToOneConversation || message.isFormerOneToOneConversation) {

View File

@ -36,6 +36,7 @@ import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.databinding.ItemCustomIncomingPollMessageBinding import com.nextcloud.talk.databinding.ItemCustomIncomingPollMessageBinding
import com.nextcloud.talk.extensions.loadBotsAvatar import com.nextcloud.talk.extensions.loadBotsAvatar
import com.nextcloud.talk.extensions.loadChangelogBotAvatar import com.nextcloud.talk.extensions.loadChangelogBotAvatar
import com.nextcloud.talk.extensions.loadFederatedUserAvatar
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.polls.ui.PollMainDialogFragment import com.nextcloud.talk.polls.ui.PollMainDialogFragment
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
@ -179,6 +180,8 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) :
binding.messageUserAvatar.loadChangelogBotAvatar() binding.messageUserAvatar.loadChangelogBotAvatar()
} else if (message.actorType == "bots") { } else if (message.actorType == "bots") {
binding.messageUserAvatar.loadBotsAvatar() binding.messageUserAvatar.loadBotsAvatar()
} else if (message.actorType == "federated_users" && message.messageParameters?.get("actor") != null) {
binding.messageUserAvatar.loadFederatedUserAvatar(message)
} }
} }

View File

@ -41,6 +41,7 @@ import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.databinding.ItemCustomIncomingTextMessageBinding import com.nextcloud.talk.databinding.ItemCustomIncomingTextMessageBinding
import com.nextcloud.talk.extensions.loadBotsAvatar import com.nextcloud.talk.extensions.loadBotsAvatar
import com.nextcloud.talk.extensions.loadChangelogBotAvatar import com.nextcloud.talk.extensions.loadChangelogBotAvatar
import com.nextcloud.talk.extensions.loadFederatedUserAvatar
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
@ -182,6 +183,8 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
binding.messageUserAvatar.loadChangelogBotAvatar() binding.messageUserAvatar.loadChangelogBotAvatar()
} else if (message.actorType == "bots") { } else if (message.actorType == "bots") {
binding.messageUserAvatar.loadBotsAvatar() binding.messageUserAvatar.loadBotsAvatar()
} else if (message.actorType == "federated_users" && message.messageParameters?.get("actor") != null) {
binding.messageUserAvatar.loadFederatedUserAvatar(message)
} }
} }

View File

@ -45,6 +45,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.databinding.ItemCustomIncomingVoiceMessageBinding import com.nextcloud.talk.databinding.ItemCustomIncomingVoiceMessageBinding
import com.nextcloud.talk.extensions.loadChangelogBotAvatar import com.nextcloud.talk.extensions.loadChangelogBotAvatar
import com.nextcloud.talk.extensions.loadFederatedUserAvatar
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
@ -285,6 +286,8 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
) )
binding.messageUserAvatar.visibility = View.VISIBLE binding.messageUserAvatar.visibility = View.VISIBLE
binding.messageUserAvatar.setImageDrawable(drawable) binding.messageUserAvatar.setImageDrawable(drawable)
} else if (message.actorType == "federated_users" && message.messageParameters?.get("actor") != null) {
binding.messageUserAvatar.loadFederatedUserAvatar(message)
} }
} }

View File

@ -49,6 +49,7 @@ import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding
import com.nextcloud.talk.extensions.loadChangelogBotAvatar import com.nextcloud.talk.extensions.loadChangelogBotAvatar
import com.nextcloud.talk.extensions.loadFederatedUserAvatar
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.users.UserManager
@ -194,6 +195,8 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
} }
if (ACTOR_TYPE_BOTS == message.actorType && ACTOR_ID_CHANGELOG == message.actorId) { if (ACTOR_TYPE_BOTS == message.actorType && ACTOR_ID_CHANGELOG == message.actorId) {
userAvatar.loadChangelogBotAvatar() userAvatar.loadChangelogBotAvatar()
} else if (message.actorType == "federated_users" && message.messageParameters?.get("actor") != null) {
userAvatar.loadFederatedUserAvatar(message)
} }
} }
} }

View File

@ -27,7 +27,6 @@ import android.text.Editable;
import android.text.Spanned; import android.text.Spanned;
import android.widget.EditText; import android.widget.EditText;
import third.parties.fresco.BetterImageSpan;
import com.nextcloud.talk.R; import com.nextcloud.talk.R;
import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.models.json.mention.Mention; import com.nextcloud.talk.models.json.mention.Mention;
@ -39,7 +38,10 @@ import com.otaliastudios.autocomplete.AutocompleteCallback;
import com.vanniktech.emoji.EmojiRange; import com.vanniktech.emoji.EmojiRange;
import com.vanniktech.emoji.Emojis; import com.vanniktech.emoji.Emojis;
import java.util.Objects;
import kotlin.OptIn; import kotlin.OptIn;
import third.parties.fresco.BetterImageSpan;
public class MentionAutocompleteCallback implements AutocompleteCallback<Mention> { public class MentionAutocompleteCallback implements AutocompleteCallback<Mention> {
private final ViewThemeUtils viewThemeUtils; private final ViewThemeUtils viewThemeUtils;
@ -66,26 +68,31 @@ public class MentionAutocompleteCallback implements AutocompleteCallback<Mention
} }
String replacement = item.getLabel(); String replacement = item.getLabel();
StringBuilder replacementStringBuilder = new StringBuilder(item.getLabel()); StringBuilder replacementStringBuilder = new StringBuilder(Objects.requireNonNull(item.getLabel()));
for (EmojiRange emojiRange : Emojis.emojis(replacement)) { for (EmojiRange emojiRange : Emojis.emojis(replacement)) {
replacementStringBuilder.delete(emojiRange.range.getStart(), emojiRange.range.getEndInclusive()); replacementStringBuilder.delete(emojiRange.range.getStart(), emojiRange.range.getEndInclusive());
} }
editable.replace(range.getStart(), range.getEnd(), replacementStringBuilder + " "); String charSequence = " ";
editable.replace(range.getStart(), range.getEnd(), charSequence + replacementStringBuilder + " ");
String id;
if (item.getMentionId() != null) id = item.getMentionId(); else id = item.getId();
Spans.MentionChipSpan mentionChipSpan = Spans.MentionChipSpan mentionChipSpan =
new Spans.MentionChipSpan(DisplayUtils.getDrawableForMentionChipSpan(context, new Spans.MentionChipSpan(DisplayUtils.getDrawableForMentionChipSpan(context,
item.getId(), item.getId(),
item.getRoomToken(),
item.getLabel(), item.getLabel(),
conversationUser, conversationUser,
item.getSource(), item.getSource(),
R.xml.chip_you, R.xml.chip_you,
editText, editText,
viewThemeUtils), viewThemeUtils,
"federated_users".equals(item.getSource())),
BetterImageSpan.ALIGN_CENTER, BetterImageSpan.ALIGN_CENTER,
item.getId(), item.getLabel()); id, item.getLabel());
editable.setSpan(mentionChipSpan, editable.setSpan(mentionChipSpan,
range.getStart(), range.getStart() + charSequence.length(),
range.getStart() + replacementStringBuilder.length(), range.getStart() + replacementStringBuilder.length() + charSequence.length(),
Spanned.SPAN_INCLUSIVE_INCLUSIVE); Spanned.SPAN_INCLUSIVE_INCLUSIVE);

View File

@ -3584,6 +3584,7 @@ class ChatActivity :
mentionSpan = mentionSpans[i] mentionSpan = mentionSpans[i]
var mentionId = mentionSpan.id var mentionId = mentionSpan.id
if (mentionId.contains(" ") || if (mentionId.contains(" ") ||
mentionId.contains("@") ||
mentionId.startsWith("guest/") || mentionId.startsWith("guest/") ||
mentionId.startsWith("group/") mentionId.startsWith("group/")
) { ) {
@ -3798,6 +3799,7 @@ class ChatActivity :
chatMessage.isFormerOneToOneConversation = chatMessage.isFormerOneToOneConversation =
(currentConversation?.type == ConversationType.FORMER_ONE_TO_ONE) (currentConversation?.type == ConversationType.FORMER_ONE_TO_ONE)
chatMessage.activeUser = conversationUser chatMessage.activeUser = conversationUser
chatMessage.roomToken = roomToken
} }
if (adapter != null) { if (adapter != null) {

View File

@ -46,6 +46,7 @@ import com.nextcloud.talk.R
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.domain.ConversationType import com.nextcloud.talk.models.domain.ConversationType
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
@ -126,6 +127,47 @@ fun ImageView.loadUserAvatar(
return loadAvatarInternal(user, imageRequestUri, ignoreCache, null) return loadAvatarInternal(user, imageRequestUri, ignoreCache, null)
} }
fun ImageView.loadFederatedUserAvatar(message: ChatMessage): io.reactivex.disposables.Disposable {
val map = message.messageParameters?.get("actor")
val url = map?.get("server")!!
val id = map["id"]
val cloudId = "$id@$url"
val darkTheme = if (DisplayUtils.isDarkModeOn(context)) 1 else 0
val ignoreCache = false
val requestBigSize = true
return loadFederatedUserAvatar(
message.activeUser!!,
message.activeUser!!.baseUrl!!,
message.roomToken,
cloudId,
darkTheme,
requestBigSize,
ignoreCache
)
}
@Suppress("LongParameterList")
fun ImageView.loadFederatedUserAvatar(
user: User,
baseUrl: String,
token: String,
cloudId: String,
darkTheme: Int,
requestBigSize: Boolean = true,
ignoreCache: Boolean
): io.reactivex.disposables.Disposable {
val imageRequestUri = ApiUtils.getUrlForFederatedAvatar(
baseUrl,
token,
cloudId,
darkTheme,
requestBigSize
)
Log.d("Julius", "URL::$imageRequestUri")
return loadAvatarInternal(user, imageRequestUri, ignoreCache, null)
}
@OptIn(ExperimentalCoilApi::class) @OptIn(ExperimentalCoilApi::class)
private fun ImageView.loadAvatarInternal( private fun ImageView.loadAvatarInternal(
user: User?, user: User?,

View File

@ -159,7 +159,9 @@ data class ChatMessage(
var hiddenByCollapse: Boolean = false, var hiddenByCollapse: Boolean = false,
var openWhenDownloaded: Boolean = true var openWhenDownloaded: Boolean = true,
var roomToken: String = ""
) : Parcelable, MessageContentType, MessageContentType.Image { ) : Parcelable, MessageContentType, MessageContentType.Image {

View File

@ -23,12 +23,15 @@ package com.nextcloud.talk.models.json.mention
import android.os.Parcelable import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonIgnore
import com.bluelinelabs.logansquare.annotation.JsonObject import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
@JsonObject @JsonObject
data class Mention( data class Mention(
@JsonField(name = ["mentionId"])
var mentionId: String?,
@JsonField(name = ["id"]) @JsonField(name = ["id"])
var id: String?, var id: String?,
@JsonField(name = ["label"]) @JsonField(name = ["label"])
@ -41,8 +44,10 @@ data class Mention(
@JsonField(name = ["statusIcon"]) @JsonField(name = ["statusIcon"])
var statusIcon: String?, var statusIcon: String?,
@JsonField(name = ["statusMessage"]) @JsonField(name = ["statusMessage"])
var statusMessage: String? var statusMessage: String?,
@JsonIgnore
var roomToken: String?
) : Parcelable { ) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null, null, null, null, null) constructor() : this(null, null, null, null, null, null, null, null)
} }

View File

@ -155,6 +155,7 @@ public class MentionAutocompletePresenter extends RecyclerViewPresenter<Mention>
mention, mention,
currentUser, currentUser,
context, context,
roomToken,
viewThemeUtils)); viewThemeUtils));
} }
@ -185,9 +186,14 @@ public class MentionAutocompletePresenter extends RecyclerViewPresenter<Mention>
Mention mention = new Mention(); Mention mention = new Mention();
MentionAutocompleteItem mentionAutocompleteItem = (MentionAutocompleteItem) adapter.getItem(position); MentionAutocompleteItem mentionAutocompleteItem = (MentionAutocompleteItem) adapter.getItem(position);
if (mentionAutocompleteItem != null) { if (mentionAutocompleteItem != null) {
String mentionId = mentionAutocompleteItem.getMentionId();
if(mentionId != null) {
mention.setMentionId(mentionId);
}
mention.setId(mentionAutocompleteItem.getObjectId()); mention.setId(mentionAutocompleteItem.getObjectId());
mention.setLabel(mentionAutocompleteItem.getDisplayName()); mention.setLabel(mentionAutocompleteItem.getDisplayName());
mention.setSource(mentionAutocompleteItem.getSource()); mention.setSource(mentionAutocompleteItem.getSource());
mention.setRoomToken(mentionAutocompleteItem.getRoomToken());
dispatchClick(mention); dispatchClick(mention);
} }
return true; return true;

View File

@ -383,6 +383,19 @@ object ApiUtils {
return baseUrl + "/index.php/avatar/" + Uri.encode(name) + "/" + avatarSize return baseUrl + "/index.php/avatar/" + Uri.encode(name) + "/" + avatarSize
} }
@JvmStatic
fun getUrlForFederatedAvatar(
baseUrl: String,
token: String,
cloudId: String,
darkTheme: Int,
requestBigSize: Boolean
): String {
val avatarSize = if (requestBigSize) AVATAR_SIZE_BIG else AVATAR_SIZE_SMALL
val url = "$baseUrl$OCS_API_VERSION$SPREED_API_VERSION/proxy/$token/user-avatar/$avatarSize"
return "$url?cloudId=$cloudId&darkTheme=$darkTheme"
}
@JvmStatic @JvmStatic
fun getUrlForGuestAvatar(baseUrl: String?, name: String?, requestBigSize: Boolean): String { fun getUrlForGuestAvatar(baseUrl: String?, name: String?, requestBigSize: Boolean): String {
val avatarSize = if (requestBigSize) AVATAR_SIZE_BIG else AVATAR_SIZE_SMALL val avatarSize = if (requestBigSize) AVATAR_SIZE_BIG else AVATAR_SIZE_SMALL

View File

@ -68,6 +68,7 @@ import org.greenrobot.eventbus.EventBus;
import java.text.DateFormat; import java.text.DateFormat;
import java.util.Date; import java.util.Date;
import java.util.Objects;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -168,12 +169,14 @@ public class DisplayUtils {
public static Drawable getDrawableForMentionChipSpan(Context context, public static Drawable getDrawableForMentionChipSpan(Context context,
String id, String id,
String roomToken,
CharSequence label, CharSequence label,
User conversationUser, User conversationUser,
String type, String type,
@XmlRes int chipResource, @XmlRes int chipResource,
@Nullable EditText emojiEditText, @Nullable EditText emojiEditText,
ViewThemeUtils viewThemeUtils) { ViewThemeUtils viewThemeUtils,
Boolean isFederated) {
ChipDrawable chip = ChipDrawable.createFromResource(context, chipResource); ChipDrawable chip = ChipDrawable.createFromResource(context, chipResource);
chip.setText(EmojiCompat.get().process(label)); chip.setText(EmojiCompat.get().process(label));
chip.setEllipsize(TextUtils.TruncateAt.MIDDLE); chip.setEllipsize(TextUtils.TruncateAt.MIDDLE);
@ -205,13 +208,21 @@ public class DisplayUtils {
chip.setBounds(0, 0, chip.getIntrinsicWidth(), chip.getIntrinsicHeight()); chip.setBounds(0, 0, chip.getIntrinsicWidth(), chip.getIntrinsicHeight());
if (!isCallOrGroup) { if (!isCallOrGroup) {
String url = ApiUtils.getUrlForAvatar(conversationUser.getBaseUrl(), id, true); String url = ApiUtils.getUrlForAvatar(conversationUser.getBaseUrl(), id, false);
if ("guests".equals(type) || "guest".equals(type)) { if ("guests".equals(type) || "guest".equals(type)) {
url = ApiUtils.getUrlForGuestAvatar( url = ApiUtils.getUrlForGuestAvatar(
conversationUser.getBaseUrl(), conversationUser.getBaseUrl(),
String.valueOf(label), true); String.valueOf(label), true);
} }
if (isFederated) {
int darkTheme = (DisplayUtils.isDarkModeOn(context))? 1 : 0;
url = ApiUtils.getUrlForFederatedAvatar(Objects.requireNonNull(conversationUser.getBaseUrl()),
roomToken, id,
darkTheme, false);
}
ImageRequest imageRequest = new ImageRequest.Builder(context) ImageRequest imageRequest = new ImageRequest.Builder(context)
.data(url) .data(url)
.crossfade(true) .crossfade(true)
@ -224,13 +235,12 @@ public class DisplayUtils {
@Override @Override
public void onError(@Nullable Drawable drawable) { public void onError(@Nullable Drawable drawable) {
chip.setChipIcon(drawable);
} }
@Override @Override
public void onSuccess(@NonNull Drawable drawable) { public void onSuccess(@NonNull Drawable drawable) {
chip.setChipIcon(drawable); chip.setChipIcon(drawable);
// A hack to refresh the chip icon // A hack to refresh the chip icon
if (emojiEditText != null) { if (emojiEditText != null) {
emojiEditText.post(() -> emojiEditText.setTextKeepState( emojiEditText.post(() -> emojiEditText.setTextKeepState(
@ -248,10 +258,12 @@ public class DisplayUtils {
} }
public static Spannable searchAndReplaceWithMentionSpan(String key, Context context, Spanned text, public static Spannable searchAndReplaceWithMentionSpan(String key, Context context, Spanned text,
String id, String label, String type, String id, String roomToken,
String label, String type,
User conversationUser, User conversationUser,
@XmlRes int chipXmlRes, @XmlRes int chipXmlRes,
ViewThemeUtils viewThemeUtils) { ViewThemeUtils viewThemeUtils,
Boolean isFederated) {
Spannable spannableString = new SpannableString(text); Spannable spannableString = new SpannableString(text);
String stringText = text.toString(); String stringText = text.toString();
@ -267,7 +279,7 @@ public class DisplayUtils {
} }
}; };
int lastStartIndex = -1; int lastStartIndex = 0;
Spans.MentionChipSpan mentionChipSpan; Spans.MentionChipSpan mentionChipSpan;
while (m.find()) { while (m.find()) {
int start = stringText.indexOf(m.group(), lastStartIndex); int start = stringText.indexOf(m.group(), lastStartIndex);
@ -276,13 +288,14 @@ public class DisplayUtils {
Drawable drawableForChip = DisplayUtils.getDrawableForMentionChipSpan(context, Drawable drawableForChip = DisplayUtils.getDrawableForMentionChipSpan(context,
id, id,
roomToken,
label, label,
conversationUser, conversationUser,
type, type,
chipXmlRes, chipXmlRes,
null, null,
viewThemeUtils); viewThemeUtils,
isFederated);
mentionChipSpan = new Spans.MentionChipSpan(drawableForChip, mentionChipSpan = new Spans.MentionChipSpan(drawableForChip,
BetterImageSpan.ALIGN_CENTER, BetterImageSpan.ALIGN_CENTER,
id, id,

View File

@ -127,16 +127,24 @@ class MessageUtils(val context: Context) {
} else { } else {
R.xml.chip_others R.xml.chip_others
} }
val id = if (individualHashMap["server"] != null) {
individualHashMap["id"] + "@" + individualHashMap["server"]
} else {
individualHashMap["id"]
}
messageStringInternal = DisplayUtils.searchAndReplaceWithMentionSpan( messageStringInternal = DisplayUtils.searchAndReplaceWithMentionSpan(
key, key,
themingContext, themingContext,
messageStringInternal, messageStringInternal,
individualHashMap["id"]!!, id,
message.roomToken,
individualHashMap["name"]!!, individualHashMap["name"]!!,
individualHashMap["type"]!!, individualHashMap["type"]!!,
message.activeUser!!, message.activeUser!!,
chip, chip,
viewThemeUtils viewThemeUtils,
individualHashMap["server"] != null
) )
} }
@ -174,5 +182,6 @@ class MessageUtils(val context: Context) {
companion object { companion object {
private const val TAG = "MessageUtils" private const val TAG = "MessageUtils"
const val MAX_REPLY_LENGTH = 250 const val MAX_REPLY_LENGTH = 250
const val HTTPS_PROTOCOL = "https://"
} }
} }