Add block request action button to collapsed join request events.

fork-5.53.8
Cody Henthorne 2022-03-15 20:41:48 -04:00
rodzic 130d5a8945
commit d3049a3433
11 zmienionych plików z 188 dodań i 5 usunięć

Wyświetl plik

@ -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);

Wyświetl plik

@ -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))
}
)

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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);

Wyświetl plik

@ -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);

Wyświetl plik

@ -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();

Wyświetl plik

@ -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));
}

Wyświetl plik

@ -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()
}
}

Wyświetl plik

@ -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.");

Wyświetl plik

@ -367,6 +367,14 @@
<string name="ConversationFragment_your_safety_number_with_s_changed_likey_because_they_reinstalled_signal">Your safety number with %s changed, likely because they reinstalled Signal or changed devices. Tap Verify to confirm the new safety number. This is optional.</string>
<!-- Message shown to indicate which notification profile is on/active -->
<string name="ConversationFragment__s_on">%1$s on</string>
<!-- Dialog title for block group link join requests -->
<string name="ConversationFragment__block_request">Block request?</string>
<!-- Dialog message for block group link join requests -->
<string name="ConversationFragment__s_will_not_be_able_to_join_or_request_to_join_this_group_via_the_group_link">%1$s will not be able to join or request to join this group via the group link. They can still be added to the group manually.</string>
<!-- Dialog confirm block request button -->
<string name="ConversationFragment__block_request_button">Block request</string>
<!-- Dialog cancel block request button -->
<string name="ConversationFragment__cancel">Cancel</string>
<plurals name="ConversationListFragment_delete_selected_conversations">
<item quantity="one">Delete selected conversation?</item>
@ -2047,6 +2055,8 @@
<string name="ConversationUpdateItem_invite_friends">Invite friends</string>
<string name="ConversationUpdateItem_enable_call_notifications">Enable Call Notifications</string>
<string name="ConversationUpdateItem_update_contact">Update contact</string>
<!-- Update item button text to show to block a recipient from requesting to join via group link -->
<string name="ConversationUpdateItem_block_request">Block request</string>
<string name="ConversationUpdateItem_no_groups_in_common_review_requests_carefully">No groups in common. Review requests carefully.</string>
<string name="ConversationUpdateItem_no_contacts_in_this_group_review_requests_carefully">No contacts in this group. Review requests carefully.</string>
<string name="ConversationUpdateItem_view">View</string>

Wyświetl plik

@ -627,6 +627,9 @@ public final class DecryptedGroupUtil {
changeIsEmptyExceptForProfileKeyChanges(change);
}
/*
* When updating this, update {@link #changeIsEmptyExceptForBanChangesAndOptionalProfileKeyChanges(DecryptedGroupChange)}
*/
public static boolean changeIsEmptyExceptForProfileKeyChanges(DecryptedGroupChange change) {
return change.getNewMembersCount() == 0 && // field 3
change.getDeleteMembersCount() == 0 && // field 4
@ -650,6 +653,28 @@ public final class DecryptedGroupUtil {
change.getDeleteBannedMembersCount() == 0; // field 23
}
public static boolean changeIsEmptyExceptForBanChangesAndOptionalProfileKeyChanges(DecryptedGroupChange change) {
return (change.getNewBannedMembersCount() != 0 || change.getDeleteBannedMembersCount() != 0) &&
change.getNewMembersCount() == 0 && // field 3
change.getDeleteMembersCount() == 0 && // field 4
change.getModifyMemberRolesCount() == 0 && // field 5
change.getNewPendingMembersCount() == 0 && // field 7
change.getDeletePendingMembersCount() == 0 && // field 8
change.getPromotePendingMembersCount() == 0 && // field 9
!change.hasNewTitle() && // field 10
!change.hasNewAvatar() && // field 11
!change.hasNewTimer() && // field 12
isEmpty(change.getNewAttributeAccess()) && // field 13
isEmpty(change.getNewMemberAccess()) && // field 14
isEmpty(change.getNewInviteLinkAccess()) && // field 15
change.getNewRequestingMembersCount() == 0 && // field 16
change.getDeleteRequestingMembersCount() == 0 && // field 17
change.getPromoteRequestingMembersCount() == 0 && // field 18
change.getNewInviteLinkPassword().size() == 0 && // field 19
!change.hasNewDescription() && // field 20
isEmpty(change.getNewIsAnnouncementGroup()); // field 21
}
static boolean isEmpty(AccessControl.AccessRequired newAttributeAccess) {
return newAttributeAccess == AccessControl.AccessRequired.UNKNOWN;
}
@ -657,4 +682,8 @@ public final class DecryptedGroupUtil {
static boolean isEmpty(EnabledState enabledState) {
return enabledState == EnabledState.UNKNOWN;
}
public static boolean changeIsSilent(DecryptedGroupChange plainGroupChange) {
return changeIsEmptyExceptForProfileKeyChanges(plainGroupChange) || changeIsEmptyExceptForBanChangesAndOptionalProfileKeyChanges(plainGroupChange);
}
}