mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-23 02:10:44 +01:00
Group link preview and info display bottom sheet.
This commit is contained in:
committed by
Greyson Parrelli
parent
477bb45df7
commit
09d167c16d
@@ -6,16 +6,20 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
|
||||
import org.signal.zkgroup.VerificationFailedException;
|
||||
import org.signal.zkgroup.groups.GroupMasterKey;
|
||||
import org.signal.zkgroup.groups.UuidCiphertext;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.groups.v2.GroupLinkPassword;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
@@ -254,6 +258,20 @@ public final class GroupManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use to get a group's details direct from server bypassing the database.
|
||||
* <p>
|
||||
* Useful when you don't yet have the group in the database locally.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static @NonNull DecryptedGroupJoinInfo getGroupJoinInfoFromServer(@NonNull Context context,
|
||||
@NonNull GroupMasterKey groupMasterKey,
|
||||
@NonNull GroupLinkPassword groupLinkPassword)
|
||||
throws IOException, VerificationFailedException, GroupLinkNotActiveException
|
||||
{
|
||||
return new GroupManagerV2(context).getGroupJoinInfoFromServer(groupMasterKey, groupLinkPassword);
|
||||
}
|
||||
|
||||
public static class GroupActionResult {
|
||||
private final Recipient groupRecipient;
|
||||
private final long threadId;
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.signal.storageservice.protos.groups.GroupChange;
|
||||
import org.signal.storageservice.protos.groups.Member;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
|
||||
import org.signal.zkgroup.InvalidInputException;
|
||||
@@ -28,6 +29,7 @@ import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.v2.GroupCandidateHelper;
|
||||
import org.thoughtcrime.securesms.groups.v2.GroupLinkPassword;
|
||||
import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor;
|
||||
import org.thoughtcrime.securesms.jobs.PushGroupSilentUpdateSendJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
@@ -41,6 +43,7 @@ import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupCandidate;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupChangeUtil;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||
import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException;
|
||||
@@ -86,6 +89,14 @@ final class GroupManagerV2 {
|
||||
this.groupCandidateHelper = new GroupCandidateHelper(context);
|
||||
}
|
||||
|
||||
@NonNull DecryptedGroupJoinInfo getGroupJoinInfoFromServer(@NonNull GroupMasterKey groupMasterKey, @NonNull GroupLinkPassword password)
|
||||
throws IOException, VerificationFailedException, GroupLinkNotActiveException
|
||||
{
|
||||
GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
|
||||
|
||||
return groupsV2Api.getGroupJoinInfo(groupSecretParams, password.serialize(), authorization.getAuthorizationForToday(Recipient.self().requireUuid(), groupSecretParams));
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
GroupCreator create() throws GroupChangeBusyException {
|
||||
return new GroupCreator(GroupsV2ProcessingLock.acquireGroupProcessingLock());
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.invitesandrequests.joining;
|
||||
|
||||
enum FetchGroupDetailsError {
|
||||
GroupLinkNotActive,
|
||||
NetworkError
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.invitesandrequests.joining;
|
||||
|
||||
public final class GroupDetails {
|
||||
private final String groupName;
|
||||
private final byte[] avatarBytes;
|
||||
private final int groupMembershipCount;
|
||||
private final boolean requiresAdminApproval;
|
||||
private final int groupRevision;
|
||||
|
||||
public GroupDetails(String groupName,
|
||||
byte[] avatarBytes,
|
||||
int groupMembershipCount,
|
||||
boolean requiresAdminApproval,
|
||||
int groupRevision)
|
||||
{
|
||||
this.groupName = groupName;
|
||||
this.avatarBytes = avatarBytes;
|
||||
this.groupMembershipCount = groupMembershipCount;
|
||||
this.requiresAdminApproval = requiresAdminApproval;
|
||||
this.groupRevision = groupRevision;
|
||||
}
|
||||
|
||||
public String getGroupName() {
|
||||
return groupName;
|
||||
}
|
||||
|
||||
public byte[] getAvatarBytes() {
|
||||
return avatarBytes;
|
||||
}
|
||||
|
||||
public int getGroupMembershipCount() {
|
||||
return groupMembershipCount;
|
||||
}
|
||||
|
||||
public boolean joinRequiresAdminApproval() {
|
||||
return requiresAdminApproval;
|
||||
}
|
||||
|
||||
public int getGroupRevision() {
|
||||
return groupRevision;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.invitesandrequests.joining;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
||||
import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil;
|
||||
import org.thoughtcrime.securesms.util.PlayStoreUtil;
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
|
||||
public final class GroupJoinBottomSheetDialogFragment extends BottomSheetDialogFragment {
|
||||
|
||||
private static final String ARG_GROUP_INVITE_LINK_URL = "group_invite_url";
|
||||
|
||||
private ProgressBar busy;
|
||||
private AvatarImageView avatar;
|
||||
private TextView groupName;
|
||||
private TextView groupDetails;
|
||||
private TextView groupJoinExplain;
|
||||
private Button groupJoinButton;
|
||||
private Button groupCancelButton;
|
||||
|
||||
public static void show(@NonNull FragmentManager manager,
|
||||
@NonNull GroupInviteLinkUrl groupInviteLinkUrl)
|
||||
{
|
||||
GroupJoinBottomSheetDialogFragment fragment = new GroupJoinBottomSheetDialogFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_GROUP_INVITE_LINK_URL, groupInviteLinkUrl.getUrl());
|
||||
fragment.setArguments(args);
|
||||
|
||||
fragment.show(manager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
setStyle(DialogFragment.STYLE_NORMAL,
|
||||
ThemeUtil.isDarkTheme(requireContext()) ? R.style.Theme_Signal_RoundedBottomSheet
|
||||
: R.style.Theme_Signal_RoundedBottomSheet_Light);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.group_join_bottom_sheet, container, false);
|
||||
|
||||
groupCancelButton = view.findViewById(R.id.group_join_cancel_button);
|
||||
groupJoinButton = view.findViewById(R.id.group_join_button);
|
||||
busy = view.findViewById(R.id.group_join_busy);
|
||||
avatar = view.findViewById(R.id.group_join_recipient_avatar);
|
||||
groupName = view.findViewById(R.id.group_join_group_name);
|
||||
groupDetails = view.findViewById(R.id.group_join_group_details);
|
||||
groupJoinExplain = view.findViewById(R.id.group_join_explain);
|
||||
|
||||
groupCancelButton.setOnClickListener(v -> dismiss());
|
||||
|
||||
avatar.setImageBytesForGroup(null, new FallbackPhotoProvider(), MaterialColor.STEEL);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
GroupJoinViewModel.Factory factory = new GroupJoinViewModel.Factory(requireContext().getApplicationContext(), getGroupInviteLinkUrl());
|
||||
|
||||
GroupJoinViewModel viewModel = ViewModelProviders.of(this, factory).get(GroupJoinViewModel.class);
|
||||
|
||||
viewModel.getGroupDetails().observe(getViewLifecycleOwner(), details -> {
|
||||
groupName.setText(details.getGroupName());
|
||||
groupDetails.setText(requireContext().getResources().getQuantityString(R.plurals.GroupJoinBottomSheetDialogFragment_group_dot_d_members, details.getGroupMembershipCount(), details.getGroupMembershipCount()));
|
||||
groupJoinButton.setText(R.string.GroupJoinUpdateRequiredBottomSheetDialogFragment_update_signal);
|
||||
groupJoinButton.setOnClickListener(v -> {
|
||||
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext());
|
||||
dismiss();
|
||||
});
|
||||
groupJoinExplain.setText(R.string.GroupJoinUpdateRequiredBottomSheetDialogFragment_update_message);
|
||||
avatar.setImageBytesForGroup(details.getAvatarBytes(), new FallbackPhotoProvider(), MaterialColor.STEEL);
|
||||
|
||||
groupJoinButton.setVisibility(View.VISIBLE);
|
||||
groupCancelButton.setVisibility(View.VISIBLE);
|
||||
});
|
||||
|
||||
viewModel.isBusy().observe(getViewLifecycleOwner(), isBusy -> busy.setVisibility(isBusy ? View.VISIBLE : View.GONE));
|
||||
viewModel.getErrors().observe(getViewLifecycleOwner(), error -> {
|
||||
Toast.makeText(requireContext(), errorToMessage(error), Toast.LENGTH_SHORT).show();
|
||||
dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
protected @NonNull String errorToMessage(FetchGroupDetailsError error) {
|
||||
if (error == FetchGroupDetailsError.GroupLinkNotActive) {
|
||||
return getString(R.string.GroupJoinBottomSheetDialogFragment_this_group_link_is_not_active);
|
||||
}
|
||||
return getString(R.string.GroupJoinBottomSheetDialogFragment_unable_to_get_group_information_please_try_again_later);
|
||||
}
|
||||
|
||||
private GroupInviteLinkUrl getGroupInviteLinkUrl() {
|
||||
try {
|
||||
//noinspection ConstantConditions
|
||||
return GroupInviteLinkUrl.fromUrl(requireArguments().getString(ARG_GROUP_INVITE_LINK_URL));
|
||||
} catch (GroupInviteLinkUrl.InvalidGroupLinkException | GroupInviteLinkUrl.UnknownGroupLinkVersionException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(@NonNull FragmentManager manager, @Nullable String tag) {
|
||||
BottomSheetUtil.show(manager, tag, this);
|
||||
}
|
||||
|
||||
private static final class FallbackPhotoProvider extends Recipient.FallbackPhotoProvider {
|
||||
@Override
|
||||
public @NonNull FallbackContactPhoto getPhotoForGroup() {
|
||||
return new ResourceContactPhoto(R.drawable.ic_group_outline_48);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.invitesandrequests.joining;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
import org.signal.storageservice.protos.groups.AccessControl;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
|
||||
import org.signal.zkgroup.VerificationFailedException;
|
||||
import org.thoughtcrime.securesms.groups.GroupManager;
|
||||
import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl;
|
||||
import org.thoughtcrime.securesms.jobs.AvatarGroupsV2DownloadJob;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
final class GroupJoinRepository {
|
||||
|
||||
private static final String TAG = Log.tag(GroupJoinRepository.class);
|
||||
|
||||
private final Context context;
|
||||
private final GroupInviteLinkUrl groupInviteLinkUrl;
|
||||
|
||||
GroupJoinRepository(@NonNull Context context, @NonNull GroupInviteLinkUrl groupInviteLinkUrl) {
|
||||
this.context = context;
|
||||
this.groupInviteLinkUrl = groupInviteLinkUrl;
|
||||
}
|
||||
|
||||
void getGroupDetails(@NonNull GetGroupDetailsCallback callback) {
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
try {
|
||||
callback.onComplete(getGroupDetails());
|
||||
} catch (IOException e) {
|
||||
callback.onError(FetchGroupDetailsError.NetworkError);
|
||||
} catch (VerificationFailedException | GroupLinkNotActiveException e) {
|
||||
callback.onError(FetchGroupDetailsError.GroupLinkNotActive);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private @NonNull GroupDetails getGroupDetails()
|
||||
throws VerificationFailedException, IOException, GroupLinkNotActiveException
|
||||
{
|
||||
DecryptedGroupJoinInfo joinInfo = GroupManager.getGroupJoinInfoFromServer(context,
|
||||
groupInviteLinkUrl.getGroupMasterKey(),
|
||||
groupInviteLinkUrl.getPassword());
|
||||
|
||||
byte[] avatarBytes = tryGetAvatarBytes(joinInfo);
|
||||
boolean requiresAdminApproval = joinInfo.getAddFromInviteLink() == AccessControl.AccessRequired.ADMINISTRATOR;
|
||||
|
||||
return new GroupDetails(joinInfo.getTitle(),
|
||||
avatarBytes,
|
||||
joinInfo.getMemberCount(),
|
||||
requiresAdminApproval,
|
||||
joinInfo.getRevision());
|
||||
}
|
||||
|
||||
private @Nullable byte[] tryGetAvatarBytes(@NonNull DecryptedGroupJoinInfo joinInfo) {
|
||||
try {
|
||||
return AvatarGroupsV2DownloadJob.downloadGroupAvatarBytes(context, groupInviteLinkUrl.getGroupMasterKey(), joinInfo.getAvatar());
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to get group avatar", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
interface GetGroupDetailsCallback {
|
||||
void onComplete(@NonNull GroupDetails groupDetails);
|
||||
void onError(@NonNull FetchGroupDetailsError error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.invitesandrequests.joining;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MediatorLiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl;
|
||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||
|
||||
public class GroupJoinViewModel extends ViewModel {
|
||||
|
||||
private final MutableLiveData<GroupDetails> groupDetails = new MutableLiveData<>();
|
||||
private final MutableLiveData<FetchGroupDetailsError> errors = new SingleLiveEvent<>();
|
||||
private final MutableLiveData<Boolean> busy = new MediatorLiveData<>();
|
||||
|
||||
private GroupJoinViewModel(@NonNull GroupJoinRepository repository) {
|
||||
busy.setValue(true);
|
||||
repository.getGroupDetails(new GroupJoinRepository.GetGroupDetailsCallback() {
|
||||
@Override
|
||||
public void onComplete(@NonNull GroupDetails details) {
|
||||
busy.postValue(false);
|
||||
groupDetails.postValue(details);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull FetchGroupDetailsError error) {
|
||||
busy.postValue(false);
|
||||
errors.postValue(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
LiveData<GroupDetails> getGroupDetails() {
|
||||
return groupDetails;
|
||||
}
|
||||
|
||||
LiveData<Boolean> isBusy() {
|
||||
return busy;
|
||||
}
|
||||
|
||||
LiveData<FetchGroupDetailsError> getErrors() {
|
||||
return errors;
|
||||
}
|
||||
|
||||
public static class Factory implements ViewModelProvider.Factory {
|
||||
|
||||
private final Context context;
|
||||
private final GroupInviteLinkUrl groupInviteLinkUrl;
|
||||
|
||||
public Factory(@NonNull Context context, @NonNull GroupInviteLinkUrl groupInviteLinkUrl) {
|
||||
this.context = context;
|
||||
this.groupInviteLinkUrl = groupInviteLinkUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||
//noinspection unchecked
|
||||
return (T) new GroupJoinViewModel(new GroupJoinRepository(context.getApplicationContext(), groupInviteLinkUrl));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import androidx.annotation.Nullable;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.storageservice.protos.groups.GroupInviteLink;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.zkgroup.InvalidInputException;
|
||||
import org.signal.zkgroup.groups.GroupMasterKey;
|
||||
import org.whispersystems.util.Base64UrlSafe;
|
||||
@@ -23,21 +24,31 @@ public final class GroupInviteLinkUrl {
|
||||
private final GroupLinkPassword password;
|
||||
private final String url;
|
||||
|
||||
public static GroupInviteLinkUrl forGroup(@NonNull GroupMasterKey groupMasterKey,
|
||||
@NonNull DecryptedGroup group)
|
||||
throws GroupLinkPassword.InvalidLengthException
|
||||
{
|
||||
return new GroupInviteLinkUrl(groupMasterKey, GroupLinkPassword.fromBytes(group.getInviteLinkPassword().toByteArray()));
|
||||
}
|
||||
|
||||
public static boolean isGroupLink(@NonNull String urlString) {
|
||||
return getGroupUrl(urlString) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null iff not a group url.
|
||||
* @throws InvalidGroupLinkException If group url, but cannot be parsed.
|
||||
*/
|
||||
public static @Nullable GroupInviteLinkUrl fromUrl(@NonNull String urlString)
|
||||
throws InvalidGroupLinkException, UnknownGroupLinkVersionException
|
||||
{
|
||||
URL url;
|
||||
try {
|
||||
url = new URL(urlString);
|
||||
} catch (MalformedURLException e) {
|
||||
URL url = getGroupUrl(urlString);
|
||||
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!GROUP_URL_HOST.equalsIgnoreCase(url.getHost())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!"/".equals(url.getPath()) && url.getPath().length() > 0) {
|
||||
throw new InvalidGroupLinkException("No path was expected in url");
|
||||
}
|
||||
@@ -67,6 +78,21 @@ public final class GroupInviteLinkUrl {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link URL} if the host name matches.
|
||||
*/
|
||||
private static URL getGroupUrl(@NonNull String urlString) {
|
||||
try {
|
||||
URL url = new URL(urlString);
|
||||
|
||||
return GROUP_URL_HOST.equalsIgnoreCase(url.getHost())
|
||||
? url
|
||||
: null;
|
||||
} catch (MalformedURLException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private GroupInviteLinkUrl(@NonNull GroupMasterKey groupMasterKey, @NonNull GroupLinkPassword password) {
|
||||
this.groupMasterKey = groupMasterKey;
|
||||
this.password = password;
|
||||
|
||||
Reference in New Issue
Block a user