Add block request action button to collapsed join request events.

This commit is contained in:
Cody Henthorne
2022-03-15 20:41:48 -04:00
parent 130d5a8945
commit d3049a3433
11 changed files with 188 additions and 5 deletions

View File

@@ -94,6 +94,7 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
void onChangeNumberUpdateContact(@NonNull Recipient recipient);
void onCallToAction(@NonNull String action);
void onDonateClicked();
void onBlockJoinRequest(@NonNull Recipient recipient);
/** @return true if handled, false if you want to let the normal url handling continue */
boolean onUrlClicked(@NonNull String url);

View File

@@ -177,6 +177,7 @@ class ConversationSettingsRepository(
val groupActionResult = GroupManager.addMembers(context, groupId.requirePush(), selected)
GroupAddMembersResult.Success(groupActionResult.addedMemberCount, Recipient.resolvedList(groupActionResult.invitedMembers))
} catch (e: Exception) {
Log.d(TAG, "Failure to add member", e)
GroupAddMembersResult.Failure(GroupChangeFailureReason.fromException(e))
}
)

View File

@@ -115,10 +115,12 @@ import org.thoughtcrime.securesms.giph.mp4.GiphyMp4ProjectionPlayerHolder;
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4ProjectionRecycler;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
import org.thoughtcrime.securesms.groups.ui.GroupErrors;
import org.thoughtcrime.securesms.groups.ui.invitesandrequests.invite.GroupLinkInviteFriendsBottomSheetDialogFragment;
import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupDescriptionDialog;
import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationInfoBottomSheetDialogFragment;
import org.thoughtcrime.securesms.groups.v2.GroupDescriptionUtil;
import org.thoughtcrime.securesms.groups.v2.GroupManagementRepository;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceViewOnceOpenJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
@@ -151,6 +153,7 @@ import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity;
import org.thoughtcrime.securesms.util.CachedInflater;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.HtmlUtil;
import org.thoughtcrime.securesms.util.LifecycleDisposable;
import org.thoughtcrime.securesms.util.MessageRecordUtil;
import org.thoughtcrime.securesms.util.RemoteDeleteUtil;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
@@ -236,6 +239,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
private Colorizer colorizer;
private ConversationUpdateTick conversationUpdateTick;
private MultiselectItemDecoration multiselectItemDecoration;
private LifecycleDisposable lifecycleDisposable;
public static void prepare(@NonNull Context context) {
FrameLayout parent = new FrameLayout(context);
@@ -322,6 +326,9 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
giphyMp4ProjectionRecycler = initializeGiphyMp4();
lifecycleDisposable = new LifecycleDisposable();
lifecycleDisposable.bindTo(getViewLifecycleOwner());
this.groupViewModel = new ViewModelProvider(getParentFragment(), new ConversationGroupViewModel.Factory()).get(ConversationGroupViewModel.class);
this.messageCountsViewModel = new ViewModelProvider(getParentFragment()).get(MessageCountsViewModel.class);
this.conversationViewModel = new ViewModelProvider(getParentFragment(), new ConversationViewModel.Factory()).get(ConversationViewModel.class);
@@ -1862,6 +1869,15 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
startActivity(AppSettingsActivity.subscriptions(requireContext()));
}
}
@Override
public void onBlockJoinRequest(@NonNull Recipient recipient) {
new MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.ConversationFragment__block_request)
.setMessage(getString(R.string.ConversationFragment__s_will_not_be_able_to_join_or_request_to_join_this_group_via_the_group_link, recipient.getDisplayName(requireContext())))
.setNegativeButton(R.string.ConversationFragment__cancel, null)
.setPositiveButton(R.string.ConversationFragment__block_request_button, (d, w) -> handleBlockJoinRequest(recipient))
.show();
}
}
public void refreshList() {
@@ -1892,6 +1908,18 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback);
}
private void handleBlockJoinRequest(@NonNull Recipient recipient) {
lifecycleDisposable.add(
groupViewModel.blockJoinRequests(ConversationFragment.this.recipient.get(), recipient)
.subscribe(result -> {
if (result instanceof GroupManagementRepository.GroupManagementResult.Failure) {
int failureReason = GroupErrors.getUserDisplayMessage(((GroupManagementRepository.GroupManagementResult.Failure) result).getReason());
Toast.makeText(requireContext(), failureReason, Toast.LENGTH_SHORT).show();
}
})
);
}
private final class CheckExpirationDataObserver extends RecyclerView.AdapterDataObserver {
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {

View File

@@ -26,6 +26,8 @@ import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.groups.GroupsV1MigrationUtil;
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
import org.thoughtcrime.securesms.groups.ui.invitesandrequests.invite.GroupLinkInviteFriendsBottomSheetDialogFragment;
import org.thoughtcrime.securesms.groups.v2.GroupManagementRepository;
import org.thoughtcrime.securesms.groups.v2.GroupManagementRepository.GroupManagementResult;
import org.thoughtcrime.securesms.profiles.spoofing.ReviewRecipient;
import org.thoughtcrime.securesms.profiles.spoofing.ReviewUtil;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -38,6 +40,9 @@ import java.io.IOException;
import java.util.Collections;
import java.util.List;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Single;
final class ConversationGroupViewModel extends ViewModel {
private final MutableLiveData<Recipient> liveRecipient;
@@ -46,11 +51,13 @@ final class ConversationGroupViewModel extends ViewModel {
private final LiveData<Integer> actionableRequestingMembers;
private final LiveData<ReviewState> reviewState;
private final LiveData<List<RecipientId>> gv1MigrationSuggestions;
private final GroupManagementRepository groupManagementRepository;
private boolean firstTimeInviteFriendsTriggered;
private ConversationGroupViewModel() {
this.liveRecipient = new MutableLiveData<>();
this.liveRecipient = new MutableLiveData<>();
this.groupManagementRepository = new GroupManagementRepository();
LiveData<GroupRecord> groupRecord = LiveDataUtil.mapAsync(liveRecipient, ConversationGroupViewModel::getGroupRecordForRecipient);
LiveData<List<Recipient>> duplicates = LiveDataUtil.mapAsync(groupRecord, record -> {
@@ -213,6 +220,11 @@ final class ConversationGroupViewModel extends ViewModel {
GroupLinkInviteFriendsBottomSheetDialogFragment.show(supportFragmentManager, groupId);
}
public Single<GroupManagementResult> blockJoinRequests(@NonNull Recipient groupRecipient, @NonNull Recipient recipient) {
return groupManagementRepository.blockJoinRequests(groupRecipient.requireGroupId().requireV2(), recipient)
.observeOn(AndroidSchedulers.mainThread());
}
static final class ReviewState {
private static final ReviewState EMPTY = new ReviewState(null, Recipient.UNKNOWN, 0);

View File

@@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord;
import org.thoughtcrime.securesms.database.model.LiveUpdateMessage;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.UpdateDescription;
import org.thoughtcrime.securesms.groups.LiveGroup;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
@@ -85,6 +86,7 @@ public final class ConversationUpdateItem extends FrameLayout
private final PresentOnChange presentOnChange = new PresentOnChange();
private final RecipientObserverManager senderObserver = new RecipientObserverManager(presentOnChange);
private final RecipientObserverManager groupObserver = new RecipientObserverManager(presentOnChange);
private final GroupDataManager groupData = new GroupDataManager(presentOnChange);
public ConversationUpdateItem(Context context) {
super(context);
@@ -153,8 +155,9 @@ public final class ConversationUpdateItem extends FrameLayout
senderObserver.observe(lifecycleOwner, messageRecord.getIndividualRecipient());
if (conversationRecipient.isActiveGroup() && conversationMessage.getMessageRecord().isGroupCall()) {
if (conversationRecipient.isActiveGroup() && (conversationMessage.getMessageRecord().isGroupCall() || conversationMessage.getMessageRecord().isCollapsedGroupV2JoinUpdate())) {
groupObserver.observe(lifecycleOwner, conversationRecipient);
groupData.observe(lifecycleOwner, conversationRecipient);
} else {
groupObserver.observe(lifecycleOwner, null);
}
@@ -269,6 +272,47 @@ public final class ConversationUpdateItem extends FrameLayout
}
}
static final class GroupDataManager {
private final Observer<Recipient> recipientObserver;
private final Observer<Boolean> isSelfAdminSetter;
private LiveGroup liveGroup;
private LiveData<Boolean> liveIsSelfAdmin;
private boolean isSelfAdmin;
private Recipient conversationRecipient;
GroupDataManager(@NonNull Observer<Recipient> observer) {
this.recipientObserver = observer;
this.isSelfAdminSetter = isSelfAdmin -> {
this.isSelfAdmin = isSelfAdmin;
recipientObserver.onChanged(conversationRecipient);
};
}
public void observe(@NonNull LifecycleOwner lifecycleOwner, @Nullable Recipient recipient) {
if (liveGroup != null) {
liveIsSelfAdmin.removeObserver(isSelfAdminSetter);
liveIsSelfAdmin = null;
}
if (recipient != null) {
conversationRecipient = recipient;
liveGroup = new LiveGroup(recipient.requireGroupId());
liveIsSelfAdmin = liveGroup.isSelfAdmin();
liveIsSelfAdmin.observe(lifecycleOwner, isSelfAdminSetter);
} else {
conversationRecipient = null;
liveGroup = null;
}
}
public boolean isSelfAdmin() {
return isSelfAdmin;
}
}
@Override
public @NonNull MultiselectPart getMultiselectPartForLatestTouch() {
return conversationMessage.getMultiselectCollection().asSingle().getSinglePart();
@@ -427,6 +471,14 @@ public final class ConversationUpdateItem extends FrameLayout
eventListener.onChangeNumberUpdateContact(conversationMessage.getMessageRecord().getIndividualRecipient());
}
});
} else if (conversationMessage.getMessageRecord().isCollapsedGroupV2JoinUpdate() && groupData.isSelfAdmin()) {
actionButton.setText(R.string.ConversationUpdateItem_block_request);
actionButton.setVisibility(VISIBLE);
actionButton.setOnClickListener(v -> {
if (batchSelected.isEmpty() && eventListener != null) {
eventListener.onBlockJoinRequest(conversationMessage.getMessageRecord().getIndividualRecipient());
}
});
} else {
actionButton.setVisibility(GONE);
actionButton.setOnClickListener(null);

View File

@@ -73,8 +73,6 @@ import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import kotlin.collections.CollectionsKt;
/**
* The base class for message record models that are displayed in
* conversations, as opposed to models that are displayed in a thread list.
@@ -423,6 +421,15 @@ public abstract class MessageRecord extends DisplayRecord {
return false;
}
public boolean isCollapsedGroupV2JoinUpdate() {
DecryptedGroupV2Context decryptedGroupV2Context = getDecryptedGroupV2Context();
if (decryptedGroupV2Context != null && decryptedGroupV2Context.hasChange()) {
DecryptedGroupChange change = decryptedGroupV2Context.getChange();
return change.getNewRequestingMembersCount() > 0 && change.getDeleteRequestingMembersCount() > 0;
}
return false;
}
public static @NonNull String createNewContextWithAppendedDeleteJoinRequest(@NonNull MessageRecord messageRecord, int revision, @NonNull ByteString id) {
DecryptedGroupV2Context decryptedGroupV2Context = messageRecord.getDecryptedGroupV2Context();

View File

@@ -1207,7 +1207,7 @@ final class GroupManagerV2 {
DecryptedGroupChange plainGroupChange = groupMutation.getGroupChange();
if (plainGroupChange != null && DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(plainGroupChange)) {
if (plainGroupChange != null && DecryptedGroupUtil.changeIsSilent(plainGroupChange)) {
if (sendToMembers) {
ApplicationDependencies.getJobManager().add(PushGroupSilentUpdateSendJob.create(context, groupId, groupMutation.getNewGroupState(), outgoingMessage));
}

View File

@@ -0,0 +1,41 @@
package org.thoughtcrime.securesms.groups.v2
import android.content.Context
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.groups.GroupChangeException
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason
import org.thoughtcrime.securesms.recipients.Recipient
import java.io.IOException
private val TAG: String = Log.tag(GroupManagementRepository::class.java)
/**
* Single source repository for managing groups.
*/
class GroupManagementRepository @JvmOverloads constructor(private val context: Context = ApplicationDependencies.getApplication()) {
fun blockJoinRequests(groupId: GroupId.V2, recipient: Recipient): Single<GroupManagementResult> {
return Single.fromCallable {
try {
GroupManager.ban(context, groupId, recipient.id)
GroupManagementResult.Success
} catch (e: GroupChangeException) {
Log.w(TAG, e)
GroupManagementResult.Failure(GroupChangeFailureReason.fromException(e))
} catch (e: IOException) {
Log.w(TAG, e)
GroupManagementResult.Failure(GroupChangeFailureReason.fromException(e))
}
}.subscribeOn(Schedulers.io())
}
sealed class GroupManagementResult {
object Success : GroupManagementResult()
data class Failure(val reason: GroupChangeFailureReason) : GroupManagementResult()
}
}

View File

@@ -612,6 +612,8 @@ public class GroupsV2StateProcessor {
for (LocalGroupLogEntry entry : processedLogEntries) {
if (entry.getChange() != null && DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(entry.getChange()) && !DecryptedGroupUtil.changeIsEmpty(entry.getChange())) {
Log.d(TAG, "Skipping profile key changes only update message");
} if (entry.getChange() != null && DecryptedGroupUtil.changeIsEmptyExceptForBanChangesAndOptionalProfileKeyChanges(entry.getChange())) {
Log.d(TAG, "Skipping ban changes only update message");
} else {
if (entry.getChange() != null && DecryptedGroupUtil.changeIsEmpty(entry.getChange()) && previousGroupState != null) {
Log.w(TAG, "Empty group update message seen. Not inserting.");