diff --git a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java index e2f4a30360..b9ca20d545 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java @@ -8,6 +8,7 @@ import androidx.annotation.Nullable; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.Observer; +import org.signal.ringrtc.CallLinkRootKey; import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState; import org.thoughtcrime.securesms.contactshare.Contact; import org.thoughtcrime.securesms.conversation.ConversationItem; @@ -116,5 +117,6 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable, void goToMediaPreview(ConversationItem parent, View sharedElement, MediaIntentFactory.MediaPreviewArgs args); void onEditedIndicatorClicked(@NonNull MessageRecord messageRecord); void onShowGroupDescriptionClicked(@NonNull String groupName, @NonNull String description, boolean shouldLinkifyWebLinks); + void onJoinCallLink(@NonNull CallLinkRootKey callLinkRootKey); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/links/CallLinkJoinButton.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/links/CallLinkJoinButton.kt new file mode 100644 index 0000000000..4c8e765d60 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/links/CallLinkJoinButton.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.calls.links + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.LinearLayoutCompat +import com.google.android.material.button.MaterialButton +import org.thoughtcrime.securesms.R + +class CallLinkJoinButton @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : LinearLayoutCompat(context, attrs) { + init { + orientation = VERTICAL + inflate(context, R.layout.call_link_join_button, this) + } + + private val joinButton: MaterialButton = findViewById(R.id.join_button) + + fun setJoinClickListener(onClickListener: OnClickListener) { + joinButton.setOnClickListener(onClickListener) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/links/CallLinks.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/links/CallLinks.kt index 89d1db83c3..01e3d14786 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/links/CallLinks.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/links/CallLinks.kt @@ -21,10 +21,11 @@ import java.net.URLDecoder */ object CallLinks { private const val ROOT_KEY = "key" + private const val LINK_PREFIX = "https://signal.link/call/#key=" private val TAG = Log.tag(CallLinks::class.java) - fun url(linkKeyBytes: ByteArray) = "https://signal.link/call/#key=${Hex.dump(linkKeyBytes)}" + fun url(linkKeyBytes: ByteArray) = "$LINK_PREFIX${Hex.dump(linkKeyBytes)}" fun watchCallLink(roomId: CallLinkRoomId): Observable { return Observable.create { emitter -> @@ -51,6 +52,10 @@ object CallLinks { @JvmStatic fun parseUrl(url: String): CallLinkRootKey? { + if (!url.startsWith(LINK_PREFIX)) { + return null + } + val parts = url.split("#") if (parts.size != 2) { Log.w(TAG, "Invalid fragment delimiter count in url.") diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java b/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java index 31049be219..13afe57ada 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java @@ -16,12 +16,16 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; +import org.signal.ringrtc.CallLinkRootKey; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.calls.links.CallLinks; +import org.thoughtcrime.securesms.conversation.colors.AvatarColorHash; import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.ImageSlide; import org.thoughtcrime.securesms.mms.SlidesClickedListener; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.views.Stub; @@ -202,11 +206,23 @@ public class LinkPreviewView extends FrameLayout { site.setVisibility(GONE); } + CallLinkRootKey callLinkRootKey = CallLinks.parseUrl(linkPreview.getUrl()); if (showThumbnail && linkPreview.getThumbnail().isPresent()) { thumbnail.setVisibility(VISIBLE); thumbnailState.applyState(thumbnail); thumbnail.get().setImageResource(glideRequests, new ImageSlide(linkPreview.getThumbnail().get()), type == TYPE_CONVERSATION, false); thumbnail.get().showDownloadText(false); + } else if (callLinkRootKey != null) { + thumbnail.setVisibility(VISIBLE); + thumbnailState.applyState(thumbnail); + thumbnail.get().setImageDrawable( + glideRequests, + Recipient.DEFAULT_FALLBACK_PHOTO_PROVIDER + .getPhotoForCallLink() + .asDrawable(getContext(), + AvatarColorHash.forCallLink(callLinkRootKey.getKeyBytes())) + ); + thumbnail.get().showDownloadText(false); } else { thumbnail.setVisibility(GONE); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index fc15970046..1f002d1d17 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -76,6 +76,7 @@ import org.signal.core.util.concurrent.LifecycleDisposable; import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.concurrent.SimpleTask; import org.signal.core.util.logging.Log; +import org.signal.ringrtc.CallLinkRootKey; import org.thoughtcrime.securesms.BindableConversationItem; import org.thoughtcrime.securesms.LoggingFragment; import org.thoughtcrime.securesms.R; @@ -2088,6 +2089,11 @@ public class ConversationFragment extends LoggingFragment implements Multiselect GroupDescriptionDialog.show(getChildFragmentManager(), groupName, description, shouldLinkifyWebLinks); } + @Override + public void onJoinCallLink(@NonNull CallLinkRootKey callLinkRootKey) { + CommunicationActions.startVideoCall(ConversationFragment.this, callLinkRootKey); + } + @Override public void onActivatePaymentsClicked() { Intent intent = new Intent(requireContext(), PaymentsActivity.class); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index 9328eaf64a..eb934e9a0d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -69,12 +69,15 @@ import com.google.common.collect.Sets; import org.signal.core.util.DimensionUnit; import org.signal.core.util.StringUtil; import org.signal.core.util.logging.Log; +import org.signal.ringrtc.CallLinkRootKey; import org.thoughtcrime.securesms.BindableConversationItem; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.badges.BadgeImageView; import org.thoughtcrime.securesms.badges.gifts.GiftMessageView; import org.thoughtcrime.securesms.badges.gifts.OpenableGift; +import org.thoughtcrime.securesms.calls.links.CallLinkJoinButton; +import org.thoughtcrime.securesms.calls.links.CallLinks; import org.thoughtcrime.securesms.components.AlertView; import org.thoughtcrime.securesms.components.AudioView; import org.thoughtcrime.securesms.components.AvatarImageView; @@ -130,6 +133,7 @@ import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.revealable.ViewOnceMessageView; import org.thoughtcrime.securesms.util.DateUtils; +import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.InterceptableLongClickCopyLinkSpan; import org.thoughtcrime.securesms.util.LinkUtil; import org.thoughtcrime.securesms.util.LongClickMovementMethod; @@ -224,6 +228,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo private Stub linkPreviewStub; private Stub stickerStub; private Stub revealableStub; + private Stub joinCallLinkStub; private Stub