kopia lustrzana https://github.com/ryukoposting/Signal-Android
Refactor Message Request logic to fix some GV1->GV2 bugs.
rodzic
ce44e3949c
commit
43e3ef2bee
|
@ -190,6 +190,7 @@ import org.thoughtcrime.securesms.mediasend.Media;
|
||||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivity;
|
import org.thoughtcrime.securesms.mediasend.MediaSendActivity;
|
||||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult;
|
import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult;
|
||||||
import org.thoughtcrime.securesms.messagedetails.MessageDetailsActivity;
|
import org.thoughtcrime.securesms.messagedetails.MessageDetailsActivity;
|
||||||
|
import org.thoughtcrime.securesms.messagerequests.MessageRequestState;
|
||||||
import org.thoughtcrime.securesms.messagerequests.MessageRequestViewModel;
|
import org.thoughtcrime.securesms.messagerequests.MessageRequestViewModel;
|
||||||
import org.thoughtcrime.securesms.messagerequests.MessageRequestsBottomView;
|
import org.thoughtcrime.securesms.messagerequests.MessageRequestsBottomView;
|
||||||
import org.thoughtcrime.securesms.mms.AttachmentManager;
|
import org.thoughtcrime.securesms.mms.AttachmentManager;
|
||||||
|
@ -3140,8 +3141,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||||
messageRequestBottomView.setGroupV1MigrationContinueListener(v -> GroupsV1MigrationInitiationBottomSheetDialogFragment.showForInitiation(getSupportFragmentManager(), recipient.getId()));
|
messageRequestBottomView.setGroupV1MigrationContinueListener(v -> GroupsV1MigrationInitiationBottomSheetDialogFragment.showForInitiation(getSupportFragmentManager(), recipient.getId()));
|
||||||
|
|
||||||
viewModel.getRequestReviewDisplayState().observe(this, this::presentRequestReviewBanner);
|
viewModel.getRequestReviewDisplayState().observe(this, this::presentRequestReviewBanner);
|
||||||
viewModel.getMessageData().observe(this, this::presentMessageRequestBottomViewTo);
|
viewModel.getMessageData().observe(this, this::presentMessageRequestState);
|
||||||
viewModel.getMessageRequestDisplayState().observe(this, this::presentMessageRequestDisplayState);
|
|
||||||
viewModel.getFailures().observe(this, this::showGroupChangeErrorToast);
|
viewModel.getFailures().observe(this, this::showGroupChangeErrorToast);
|
||||||
viewModel.getMessageRequestStatus().observe(this, status -> {
|
viewModel.getMessageRequestStatus().observe(this, status -> {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
|
@ -3155,7 +3155,6 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||||
break;
|
break;
|
||||||
case ACCEPTED:
|
case ACCEPTED:
|
||||||
hideMessageRequestBusy();
|
hideMessageRequestBusy();
|
||||||
messageRequestBottomView.setVisibility(View.GONE);
|
|
||||||
break;
|
break;
|
||||||
case DELETED:
|
case DELETED:
|
||||||
case BLOCKED:
|
case BLOCKED:
|
||||||
|
@ -3426,31 +3425,6 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||||
BlockUnblockDialog.showUnblockFor(this, getLifecycle(), recipient, requestModel::onUnblock);
|
BlockUnblockDialog.showUnblockFor(this, getLifecycle(), recipient, requestModel::onUnblock);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void presentMessageRequestDisplayState(@NonNull MessageRequestViewModel.DisplayState displayState) {
|
|
||||||
if ((getIntent().hasExtra(TEXT_EXTRA) && !Util.isEmpty(getIntent().getStringExtra(TEXT_EXTRA))) ||
|
|
||||||
getIntent().hasExtra(MEDIA_EXTRA) ||
|
|
||||||
getIntent().hasExtra(STICKER_EXTRA))
|
|
||||||
{
|
|
||||||
Log.d(TAG, "[presentMessageRequestDisplayState] Have extra, so ignoring provided state.");
|
|
||||||
messageRequestBottomView.setVisibility(View.GONE);
|
|
||||||
} else if (isPushGroupV1Conversation() && !isActiveGroup()) {
|
|
||||||
Log.d(TAG, "[presentMessageRequestDisplayState] Inactive push group V1, so ignoring provided state.");
|
|
||||||
messageRequestBottomView.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "[presentMessageRequestDisplayState] " + displayState);
|
|
||||||
switch (displayState) {
|
|
||||||
case DISPLAY_MESSAGE_REQUEST:
|
|
||||||
messageRequestBottomView.setVisibility(View.VISIBLE);
|
|
||||||
break;
|
|
||||||
case DISPLAY_NONE:
|
|
||||||
messageRequestBottomView.setVisibility(View.GONE);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidateOptionsMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void hideMenuItem(@NonNull Menu menu, @IdRes int menuItem) {
|
private static void hideMenuItem(@NonNull Menu menu, @IdRes int menuItem) {
|
||||||
if (menu.findItem(menuItem) != null) {
|
if (menu.findItem(menuItem) != null) {
|
||||||
menu.findItem(menuItem).setVisible(false);
|
menu.findItem(menuItem).setVisible(false);
|
||||||
|
@ -3597,10 +3571,28 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void presentMessageRequestBottomViewTo(@Nullable MessageRequestViewModel.MessageData messageData) {
|
private void presentMessageRequestState(@Nullable MessageRequestViewModel.MessageData messageData) {
|
||||||
if (messageData == null) return;
|
if ((getIntent().hasExtra(TEXT_EXTRA) && !Util.isEmpty(getIntent().getStringExtra(TEXT_EXTRA))) ||
|
||||||
|
getIntent().hasExtra(MEDIA_EXTRA) ||
|
||||||
|
getIntent().hasExtra(STICKER_EXTRA))
|
||||||
|
{
|
||||||
|
Log.d(TAG, "[presentMessageRequestState] Have extra, so ignoring provided state.");
|
||||||
|
messageRequestBottomView.setVisibility(View.GONE);
|
||||||
|
} else if (isPushGroupV1Conversation() && !isActiveGroup()) {
|
||||||
|
Log.d(TAG, "[presentMessageRequestState] Inactive push group V1, so ignoring provided state.");
|
||||||
|
messageRequestBottomView.setVisibility(View.GONE);
|
||||||
|
} else if (messageData == null) {
|
||||||
|
Log.d(TAG, "[presentMessageRequestState] Null messageData. Ignoring.");
|
||||||
|
} else if (messageData.getMessageState() == MessageRequestState.NONE) {
|
||||||
|
Log.d(TAG, "[presentMessageRequestState] No message request necessary.");
|
||||||
|
messageRequestBottomView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "[presentMessageRequestState] " + messageData.getMessageState());
|
||||||
|
messageRequestBottomView.setMessageData(messageData);
|
||||||
|
messageRequestBottomView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
messageRequestBottomView.setMessageData(messageData);
|
invalidateOptionsMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class KeyboardImageDetails {
|
private static class KeyboardImageDetails {
|
||||||
|
|
|
@ -170,6 +170,14 @@ final class ConversationGroupViewModel extends ViewModel {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!record.isV2Group()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!record.isActive() || record.isPendingMember(Recipient.self())) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
Set<RecipientId> difference = SetUtil.difference(record.getFormerV1Members(), record.getMembers());
|
Set<RecipientId> difference = SetUtil.difference(record.getFormerV1Members(), record.getMembers());
|
||||||
|
|
||||||
return Stream.of(Recipient.resolvedList(difference))
|
return Stream.of(Recipient.resolvedList(difference))
|
||||||
|
@ -180,7 +188,13 @@ final class ConversationGroupViewModel extends ViewModel {
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private static boolean mapToGroupV1MigrationReminder(@Nullable GroupRecord record) {
|
private static boolean mapToGroupV1MigrationReminder(@Nullable GroupRecord record) {
|
||||||
if (record == null || !record.isV1Group() || !record.isActive() || !FeatureFlags.groupsV1ManualMigration()) {
|
if (record == null ||
|
||||||
|
!record.isV1Group() ||
|
||||||
|
!record.isActive() ||
|
||||||
|
!FeatureFlags.groupsV1ManualMigration() ||
|
||||||
|
FeatureFlags.groupsV1ForcedMigration() ||
|
||||||
|
!Recipient.resolved(record.getRecipientId()).isProfileSharing())
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -966,7 +966,10 @@ public final class GroupDatabase extends Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isPendingMember(@NonNull Recipient recipient) {
|
/**
|
||||||
|
* Whether or not the recipient is a pending member.
|
||||||
|
*/
|
||||||
|
public boolean isPendingMember(@NonNull Recipient recipient) {
|
||||||
if (isV2Group()) {
|
if (isV2Group()) {
|
||||||
Optional<UUID> uuid = recipient.getUuid();
|
Optional<UUID> uuid = recipient.getUuid();
|
||||||
if (uuid.isPresent()) {
|
if (uuid.isPresent()) {
|
||||||
|
|
|
@ -140,17 +140,25 @@ public final class GroupsV1MigrationUtil {
|
||||||
DecryptedGroup decryptedGroup = performLocalMigration(context, gv1Id, threadId, groupRecipient);
|
DecryptedGroup decryptedGroup = performLocalMigration(context, gv1Id, threadId, groupRecipient);
|
||||||
|
|
||||||
if (newlyCreated && decryptedGroup != null && !SignalStore.internalValues().disableGv1AutoMigrateNotification()) {
|
if (newlyCreated && decryptedGroup != null && !SignalStore.internalValues().disableGv1AutoMigrateNotification()) {
|
||||||
|
Log.i(TAG, "Sending no-op update to notify others.");
|
||||||
GroupManager.sendNoopUpdate(context, gv2MasterKey, decryptedGroup);
|
GroupManager.sendNoopUpdate(context, gv2MasterKey, decryptedGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void performLocalMigration(@NonNull Context context, @NonNull GroupId.V1 gv1Id) throws IOException
|
public static void performLocalMigration(@NonNull Context context, @NonNull GroupId.V1 gv1Id) throws IOException
|
||||||
{
|
{
|
||||||
|
Log.i(TAG, "Beginning local migration!", new Throwable());
|
||||||
try (Closeable ignored = GroupsV2ProcessingLock.acquireGroupProcessingLock()) {
|
try (Closeable ignored = GroupsV2ProcessingLock.acquireGroupProcessingLock()) {
|
||||||
|
if (DatabaseFactory.getGroupDatabase(context).groupExists(gv1Id.deriveV2MigrationGroupId())) {
|
||||||
|
Log.w(TAG, "Group was already migrated! Could have been waiting for the lock.", new Throwable());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Recipient recipient = Recipient.externalGroupExact(context, gv1Id);
|
Recipient recipient = Recipient.externalGroupExact(context, gv1Id);
|
||||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
|
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
|
||||||
|
|
||||||
performLocalMigration(context, gv1Id, threadId, recipient);
|
performLocalMigration(context, gv1Id, threadId, recipient);
|
||||||
|
Log.i(TAG, "Migration complete!", new Throwable());
|
||||||
} catch (GroupChangeBusyException e) {
|
} catch (GroupChangeBusyException e) {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,44 +72,57 @@ final class MessageRequestRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void getMessageRequestState(@NonNull Recipient recipient, long threadId, @NonNull Consumer<MessageRequestState> state) {
|
|
||||||
executor.execute(() -> state.accept(findMessageRequestState(recipient, threadId)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private MessageRequestState findMessageRequestState(@NonNull Recipient recipient, long threadId) {
|
@NonNull MessageRequestState getMessageRequestState(@NonNull Recipient recipient, long threadId) {
|
||||||
if (recipient.isGroup() && recipient.isPushV2Group()) {
|
if (recipient.isBlocked()) {
|
||||||
GroupDatabase.MemberLevel memberLevel = DatabaseFactory.getGroupDatabase(context)
|
|
||||||
.getGroup(recipient.getId())
|
|
||||||
.transform(g -> g.memberLevel(Recipient.self()))
|
|
||||||
.or(GroupDatabase.MemberLevel.NOT_A_MEMBER);
|
|
||||||
|
|
||||||
if (memberLevel == GroupDatabase.MemberLevel.PENDING_MEMBER) {
|
|
||||||
return MessageRequestState.REQUIRED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recipient.isPushV1Group() && FeatureFlags.groupsV1ForcedMigration()) {
|
|
||||||
return MessageRequestState.REQUIRED;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!RecipientUtil.isMessageRequestAccepted(context, threadId)) {
|
|
||||||
if (recipient.isGroup()) {
|
if (recipient.isGroup()) {
|
||||||
GroupDatabase.MemberLevel memberLevel = DatabaseFactory.getGroupDatabase(context)
|
return MessageRequestState.BLOCKED_GROUP;
|
||||||
.getGroup(recipient.getId())
|
} else {
|
||||||
.transform(g -> g.memberLevel(Recipient.self()))
|
return MessageRequestState.BLOCKED_INDIVIDUAL;
|
||||||
.or(GroupDatabase.MemberLevel.NOT_A_MEMBER);
|
}
|
||||||
|
} else if (threadId <= 0) {
|
||||||
if (memberLevel == GroupDatabase.MemberLevel.NOT_A_MEMBER) {
|
return MessageRequestState.NONE;
|
||||||
return MessageRequestState.NOT_REQUIRED;
|
} else if (recipient.isPushV2Group()) {
|
||||||
}
|
switch (getGroupMemberLevel(recipient.getId())) {
|
||||||
|
case NOT_A_MEMBER:
|
||||||
|
return MessageRequestState.NONE;
|
||||||
|
case PENDING_MEMBER:
|
||||||
|
return MessageRequestState.GROUP_V2_INVITE;
|
||||||
|
default:
|
||||||
|
if (RecipientUtil.isMessageRequestAccepted(context, threadId)) {
|
||||||
|
return MessageRequestState.NONE;
|
||||||
|
} else {
|
||||||
|
return MessageRequestState.GROUP_V2_ADD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!RecipientUtil.isLegacyProfileSharingAccepted(recipient) && isLegacyThread(recipient)) {
|
||||||
|
if (recipient.isGroup()) {
|
||||||
|
return MessageRequestState.LEGACY_GROUP_V1;
|
||||||
|
} else {
|
||||||
|
return MessageRequestState.LEGACY_INDIVIDUAL;
|
||||||
|
}
|
||||||
|
} else if (recipient.isPushV1Group()) {
|
||||||
|
if (RecipientUtil.isMessageRequestAccepted(context, threadId)) {
|
||||||
|
if (FeatureFlags.groupsV1ForcedMigration()) {
|
||||||
|
if (recipient.getParticipants().size() > FeatureFlags.groupLimits().getHardLimit()) {
|
||||||
|
return MessageRequestState.DEPRECATED_GROUP_V1_TOO_LARGE;
|
||||||
|
} else {
|
||||||
|
return MessageRequestState.DEPRECATED_GROUP_V1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return MessageRequestState.NONE;
|
||||||
|
}
|
||||||
|
} else if (!recipient.isActiveGroup()) {
|
||||||
|
return MessageRequestState.NONE;
|
||||||
|
} else {
|
||||||
|
return MessageRequestState.GROUP_V1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return MessageRequestState.REQUIRED;
|
|
||||||
} else if (!RecipientUtil.isLegacyProfileSharingAccepted(recipient) && threadId > 0) {
|
|
||||||
return MessageRequestState.REQUIRED;
|
|
||||||
} else {
|
} else {
|
||||||
return MessageRequestState.NOT_REQUIRED;
|
if (RecipientUtil.isMessageRequestAccepted(context, threadId)) {
|
||||||
|
return MessageRequestState.NONE;
|
||||||
|
} else {
|
||||||
|
return MessageRequestState.INDIVIDUAL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,23 +272,20 @@ final class MessageRequestRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
private GroupDatabase.MemberLevel getGroupMemberLevel(@NonNull RecipientId recipientId) {
|
||||||
boolean isPendingMember(@NonNull GroupId.V2 groupId) {
|
return DatabaseFactory.getGroupDatabase(context)
|
||||||
return DatabaseFactory.getGroupDatabase(context).isPendingMember(groupId, Recipient.self());
|
.getGroup(recipientId)
|
||||||
|
.transform(g -> g.memberLevel(Recipient.self()))
|
||||||
|
.or(GroupDatabase.MemberLevel.NOT_A_MEMBER);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MessageRequestState {
|
|
||||||
/**
|
|
||||||
* Message request permission does not need to be gained at this time.
|
|
||||||
* <p>
|
|
||||||
* Either:
|
|
||||||
* - Explicit message request has been accepted, or;
|
|
||||||
* - Did not need to be shown because they are a contact etc, or;
|
|
||||||
* - It's a group that they are no longer in or invited to.
|
|
||||||
*/
|
|
||||||
NOT_REQUIRED,
|
|
||||||
|
|
||||||
/** Explicit message request permission is required. */
|
@WorkerThread
|
||||||
REQUIRED
|
private boolean isLegacyThread(@NonNull Recipient recipient) {
|
||||||
|
Context context = ApplicationDependencies.getApplication();
|
||||||
|
Long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient.getId());
|
||||||
|
|
||||||
|
return threadId != null &&
|
||||||
|
(RecipientUtil.hasSentMessageInThread(context, threadId) || RecipientUtil.isPreMessageRequestThread(context, threadId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package org.thoughtcrime.securesms.messagerequests;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enum representing the possible message request states a user can be in.
|
||||||
|
*/
|
||||||
|
public enum MessageRequestState {
|
||||||
|
/** No message request necessary */
|
||||||
|
NONE,
|
||||||
|
|
||||||
|
/** A user is blocked */
|
||||||
|
BLOCKED_INDIVIDUAL,
|
||||||
|
|
||||||
|
/** A group is blocked */
|
||||||
|
BLOCKED_GROUP,
|
||||||
|
|
||||||
|
/** An individual conversation that existed pre-message-requests but doesn't have profile sharing enabled */
|
||||||
|
LEGACY_INDIVIDUAL,
|
||||||
|
|
||||||
|
/** A V1 group conversation that existed pre-message-requests but doesn't have profile sharing enabled */
|
||||||
|
LEGACY_GROUP_V1,
|
||||||
|
|
||||||
|
/** A V1 group conversation that is no longer allowed, because we've forced GV2 on. */
|
||||||
|
DEPRECATED_GROUP_V1,
|
||||||
|
|
||||||
|
/** A V1 group conversation that is no longer allowed, because we've forced GV2 on, but it's also too large to migrate. Nothing we can do. */
|
||||||
|
DEPRECATED_GROUP_V1_TOO_LARGE,
|
||||||
|
|
||||||
|
/** A message request is needed for a V1 group */
|
||||||
|
GROUP_V1,
|
||||||
|
|
||||||
|
/** An invite response is needed for a V2 group */
|
||||||
|
GROUP_V2_INVITE,
|
||||||
|
|
||||||
|
/** A message request is needed for a V2 group */
|
||||||
|
GROUP_V2_ADD,
|
||||||
|
|
||||||
|
/** A message request is needed for an individual */
|
||||||
|
INDIVIDUAL
|
||||||
|
}
|
|
@ -26,7 +26,6 @@ import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
import org.thoughtcrime.securesms.util.livedata.LiveDataTriple;
|
import org.thoughtcrime.securesms.util.livedata.LiveDataTriple;
|
||||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||||
import org.whispersystems.libsignal.util.Pair;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -39,7 +38,6 @@ public class MessageRequestViewModel extends ViewModel {
|
||||||
private final LiveData<MessageData> messageData;
|
private final LiveData<MessageData> messageData;
|
||||||
private final MutableLiveData<List<String>> groups = new MutableLiveData<>(Collections.emptyList());
|
private final MutableLiveData<List<String>> groups = new MutableLiveData<>(Collections.emptyList());
|
||||||
private final MutableLiveData<GroupMemberCount> memberCount = new MutableLiveData<>(GroupMemberCount.ZERO);
|
private final MutableLiveData<GroupMemberCount> memberCount = new MutableLiveData<>(GroupMemberCount.ZERO);
|
||||||
private final MutableLiveData<DisplayState> displayState = new MutableLiveData<>();
|
|
||||||
private final LiveData<RequestReviewDisplayState> requestReviewDisplayState;
|
private final LiveData<RequestReviewDisplayState> requestReviewDisplayState;
|
||||||
private final LiveData<RecipientInfo> recipientInfo = Transformations.map(new LiveDataTriple<>(recipient, memberCount, groups),
|
private final LiveData<RecipientInfo> recipientInfo = Transformations.map(new LiveDataTriple<>(recipient, memberCount, groups),
|
||||||
triple -> new RecipientInfo(triple.first(), triple.second(), triple.third()));
|
triple -> new RecipientInfo(triple.first(), triple.second(), triple.third()));
|
||||||
|
@ -50,7 +48,6 @@ public class MessageRequestViewModel extends ViewModel {
|
||||||
private long threadId;
|
private long threadId;
|
||||||
|
|
||||||
private final RecipientForeverObserver recipientObserver = recipient -> {
|
private final RecipientForeverObserver recipientObserver = recipient -> {
|
||||||
loadMessageRequestAccepted(recipient);
|
|
||||||
loadMemberCount();
|
loadMemberCount();
|
||||||
this.recipient.setValue(recipient);
|
this.recipient.setValue(recipient);
|
||||||
};
|
};
|
||||||
|
@ -58,8 +55,7 @@ public class MessageRequestViewModel extends ViewModel {
|
||||||
private MessageRequestViewModel(MessageRequestRepository repository) {
|
private MessageRequestViewModel(MessageRequestRepository repository) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.messageData = LiveDataUtil.mapAsync(recipient, this::createMessageDataForRecipient);
|
this.messageData = LiveDataUtil.mapAsync(recipient, this::createMessageDataForRecipient);
|
||||||
this.requestReviewDisplayState = LiveDataUtil.mapAsync(LiveDataUtil.combineLatest(messageData, displayState, MessageDataDisplayStateHolder::new),
|
this.requestReviewDisplayState = LiveDataUtil.mapAsync(messageData, MessageRequestViewModel::transformHolderToReviewDisplayState);
|
||||||
MessageRequestViewModel::transformHolderToReviewDisplayState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setConversationInfo(@NonNull RecipientId recipientId, long threadId) {
|
public void setConversationInfo(@NonNull RecipientId recipientId, long threadId) {
|
||||||
|
@ -82,10 +78,6 @@ public class MessageRequestViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<DisplayState> getMessageRequestDisplayState() {
|
|
||||||
return displayState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<RequestReviewDisplayState> getRequestReviewDisplayState() {
|
public LiveData<RequestReviewDisplayState> getRequestReviewDisplayState() {
|
||||||
return requestReviewDisplayState;
|
return requestReviewDisplayState;
|
||||||
}
|
}
|
||||||
|
@ -111,7 +103,8 @@ public class MessageRequestViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean shouldShowMessageRequest() {
|
public boolean shouldShowMessageRequest() {
|
||||||
return displayState.getValue() == DisplayState.DISPLAY_MESSAGE_REQUEST;
|
MessageData data = messageData.getValue();
|
||||||
|
return data != null && data.getMessageState() != MessageRequestState.NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
|
@ -173,11 +166,10 @@ public class MessageRequestViewModel extends ViewModel {
|
||||||
repository.getMemberCount(liveRecipient.getId(), memberCount::postValue);
|
repository.getMemberCount(liveRecipient.getId(), memberCount::postValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RequestReviewDisplayState transformHolderToReviewDisplayState(@NonNull MessageDataDisplayStateHolder holder) {
|
private static RequestReviewDisplayState transformHolderToReviewDisplayState(@NonNull MessageData holder) {
|
||||||
if (holder.messageData.messageClass == MessageClass.INDIVIDUAL && holder.displayState == DisplayState.DISPLAY_MESSAGE_REQUEST) {
|
if (holder.getMessageState() == MessageRequestState.INDIVIDUAL) {
|
||||||
return ReviewUtil.isRecipientReviewSuggested(holder.messageData.getRecipient().getId())
|
return ReviewUtil.isRecipientReviewSuggested(holder.getRecipient().getId()) ? RequestReviewDisplayState.SHOWN
|
||||||
? RequestReviewDisplayState.SHOWN
|
: RequestReviewDisplayState.HIDDEN;
|
||||||
: RequestReviewDisplayState.HIDDEN;
|
|
||||||
} else {
|
} else {
|
||||||
return RequestReviewDisplayState.NONE;
|
return RequestReviewDisplayState.NONE;
|
||||||
}
|
}
|
||||||
|
@ -185,63 +177,8 @@ public class MessageRequestViewModel extends ViewModel {
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private @NonNull MessageData createMessageDataForRecipient(@NonNull Recipient recipient) {
|
private @NonNull MessageData createMessageDataForRecipient(@NonNull Recipient recipient) {
|
||||||
if (recipient.isBlocked()) {
|
MessageRequestState state = repository.getMessageRequestState(recipient, threadId);
|
||||||
if (recipient.isGroup()) {
|
return new MessageData(recipient, state);
|
||||||
return new MessageData(recipient, MessageClass.BLOCKED_GROUP);
|
|
||||||
} else {
|
|
||||||
return new MessageData(recipient, MessageClass.BLOCKED_INDIVIDUAL);
|
|
||||||
}
|
|
||||||
} else if (recipient.isPushV2Group()) {
|
|
||||||
if (repository.isPendingMember(recipient.requireGroupId().requireV2())) {
|
|
||||||
return new MessageData(recipient, MessageClass.GROUP_V2_INVITE);
|
|
||||||
} else {
|
|
||||||
return new MessageData(recipient, MessageClass.GROUP_V2_ADD);
|
|
||||||
}
|
|
||||||
} else if (recipient.isPushV1Group() && FeatureFlags.groupsV1ForcedMigration()) {
|
|
||||||
if (recipient.getParticipants().size() > FeatureFlags.groupLimits().getHardLimit()) {
|
|
||||||
return new MessageData(recipient, MessageClass.DEPRECATED_GROUP_V1_TOO_LARGE);
|
|
||||||
} else {
|
|
||||||
return new MessageData(recipient, MessageClass.DEPRECATED_GROUP_V1);
|
|
||||||
}
|
|
||||||
} else if (isLegacyThread(recipient)) {
|
|
||||||
if (recipient.isGroup()) {
|
|
||||||
return new MessageData(recipient, MessageClass.LEGACY_GROUP_V1);
|
|
||||||
} else {
|
|
||||||
return new MessageData(recipient, MessageClass.LEGACY_INDIVIDUAL);
|
|
||||||
}
|
|
||||||
} else if (recipient.isGroup()) {
|
|
||||||
return new MessageData(recipient, MessageClass.GROUP_V1);
|
|
||||||
} else {
|
|
||||||
return new MessageData(recipient, MessageClass.INDIVIDUAL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
|
||||||
private void loadMessageRequestAccepted(@NonNull Recipient recipient) {
|
|
||||||
if (recipient.isBlocked()) {
|
|
||||||
displayState.postValue(DisplayState.DISPLAY_MESSAGE_REQUEST);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
repository.getMessageRequestState(recipient, threadId, accepted -> {
|
|
||||||
switch (accepted) {
|
|
||||||
case NOT_REQUIRED:
|
|
||||||
displayState.postValue(DisplayState.DISPLAY_NONE);
|
|
||||||
break;
|
|
||||||
case REQUIRED:
|
|
||||||
displayState.postValue(DisplayState.DISPLAY_MESSAGE_REQUEST);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@WorkerThread
|
|
||||||
private boolean isLegacyThread(@NonNull Recipient recipient) {
|
|
||||||
Context context = ApplicationDependencies.getApplication();
|
|
||||||
Long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient.getId());
|
|
||||||
|
|
||||||
return threadId != null &&
|
|
||||||
(RecipientUtil.hasSentMessageInThread(context, threadId) || RecipientUtil.isPreMessageRequestThread(context, threadId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class RecipientInfo {
|
public static class RecipientInfo {
|
||||||
|
@ -284,27 +221,6 @@ public class MessageRequestViewModel extends ViewModel {
|
||||||
ACCEPTED
|
ACCEPTED
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum DisplayState {
|
|
||||||
DISPLAY_MESSAGE_REQUEST, DISPLAY_NONE
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum MessageClass {
|
|
||||||
BLOCKED_INDIVIDUAL,
|
|
||||||
BLOCKED_GROUP,
|
|
||||||
/** An individual conversation that existed pre-message-requests but doesn't have profile sharing enabled */
|
|
||||||
LEGACY_INDIVIDUAL,
|
|
||||||
/** A V1 group conversation that existed pre-message-requests but doesn't have profile sharing enabled */
|
|
||||||
LEGACY_GROUP_V1,
|
|
||||||
/** A V1 group conversation that is no longer allowed, because we've forced GV2 on. */
|
|
||||||
DEPRECATED_GROUP_V1,
|
|
||||||
/** A V1 group conversation that is no longer allowed, because we've forced GV2 on, but it's also too large to migrate. Nothing we can do. */
|
|
||||||
DEPRECATED_GROUP_V1_TOO_LARGE,
|
|
||||||
GROUP_V1,
|
|
||||||
GROUP_V2_INVITE,
|
|
||||||
GROUP_V2_ADD,
|
|
||||||
INDIVIDUAL
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum RequestReviewDisplayState {
|
public enum RequestReviewDisplayState {
|
||||||
HIDDEN,
|
HIDDEN,
|
||||||
SHOWN,
|
SHOWN,
|
||||||
|
@ -313,29 +229,19 @@ public class MessageRequestViewModel extends ViewModel {
|
||||||
|
|
||||||
public static final class MessageData {
|
public static final class MessageData {
|
||||||
private final Recipient recipient;
|
private final Recipient recipient;
|
||||||
private final MessageClass messageClass;
|
private final MessageRequestState messageState;
|
||||||
|
|
||||||
public MessageData(@NonNull Recipient recipient, @NonNull MessageClass messageClass) {
|
public MessageData(@NonNull Recipient recipient, @NonNull MessageRequestState messageState) {
|
||||||
this.recipient = recipient;
|
this.recipient = recipient;
|
||||||
this.messageClass = messageClass;
|
this.messageState = messageState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull Recipient getRecipient() {
|
public @NonNull Recipient getRecipient() {
|
||||||
return recipient;
|
return recipient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull MessageClass getMessageClass() {
|
public @NonNull MessageRequestState getMessageState() {
|
||||||
return messageClass;
|
return messageState;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class MessageDataDisplayStateHolder {
|
|
||||||
private final MessageData messageData;
|
|
||||||
private final DisplayState displayState;
|
|
||||||
|
|
||||||
private MessageDataDisplayStateHolder(@NonNull MessageData messageData, @NonNull DisplayState displayState) {
|
|
||||||
this.messageData = messageData;
|
|
||||||
this.displayState = displayState;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
@ -74,7 +73,7 @@ public class MessageRequestsBottomView extends ConstraintLayout {
|
||||||
question.setLearnMoreVisible(false);
|
question.setLearnMoreVisible(false);
|
||||||
question.setOnLinkClickListener(null);
|
question.setOnLinkClickListener(null);
|
||||||
|
|
||||||
switch (messageData.getMessageClass()) {
|
switch (messageData.getMessageState()) {
|
||||||
case BLOCKED_INDIVIDUAL:
|
case BLOCKED_INDIVIDUAL:
|
||||||
question.setText(HtmlCompat.fromHtml(getContext().getString(R.string.MessageRequestBottomView_do_you_want_to_let_s_message_you_wont_receive_any_messages_until_you_unblock_them,
|
question.setText(HtmlCompat.fromHtml(getContext().getString(R.string.MessageRequestBottomView_do_you_want_to_let_s_message_you_wont_receive_any_messages_until_you_unblock_them,
|
||||||
HtmlUtil.bold(recipient.getShortDisplayName(getContext()))), 0));
|
HtmlUtil.bold(recipient.getShortDisplayName(getContext()))), 0));
|
||||||
|
|
|
@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||||
import org.thoughtcrime.securesms.components.ThreadPhotoRailView;
|
import org.thoughtcrime.securesms.components.ThreadPhotoRailView;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto80dp;
|
import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto80dp;
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
|
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity;
|
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity;
|
||||||
|
@ -52,6 +53,7 @@ import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.thoughtcrime.securesms.util.LifecycleCursorWrapper;
|
import org.thoughtcrime.securesms.util.LifecycleCursorWrapper;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
@ -69,7 +71,9 @@ public class ManageRecipientFragment extends LoggingFragment {
|
||||||
private Toolbar toolbar;
|
private Toolbar toolbar;
|
||||||
private TextView title;
|
private TextView title;
|
||||||
private TextView subtitle;
|
private TextView subtitle;
|
||||||
private TextView internalDetails;
|
private ViewGroup internalDetails;
|
||||||
|
private TextView internalDetailsText;
|
||||||
|
private View disableProfileSharingButton;
|
||||||
private View contactRow;
|
private View contactRow;
|
||||||
private TextView contactText;
|
private TextView contactText;
|
||||||
private ImageView contactIcon;
|
private ImageView contactIcon;
|
||||||
|
@ -128,6 +132,8 @@ public class ManageRecipientFragment extends LoggingFragment {
|
||||||
title = view.findViewById(R.id.name);
|
title = view.findViewById(R.id.name);
|
||||||
subtitle = view.findViewById(R.id.username_number);
|
subtitle = view.findViewById(R.id.username_number);
|
||||||
internalDetails = view.findViewById(R.id.recipient_internal_details);
|
internalDetails = view.findViewById(R.id.recipient_internal_details);
|
||||||
|
internalDetailsText = view.findViewById(R.id.recipient_internal_details_text);
|
||||||
|
disableProfileSharingButton = view.findViewById(R.id.recipient_internal_details_disable_profile_sharing_button);
|
||||||
sharedGroupList = view.findViewById(R.id.shared_group_list);
|
sharedGroupList = view.findViewById(R.id.shared_group_list);
|
||||||
groupsInCommonCount = view.findViewById(R.id.groups_in_common_count);
|
groupsInCommonCount = view.findViewById(R.id.groups_in_common_count);
|
||||||
threadPhotoRailView = view.findViewById(R.id.recent_photos);
|
threadPhotoRailView = view.findViewById(R.id.recent_photos);
|
||||||
|
@ -218,7 +224,10 @@ public class ManageRecipientFragment extends LoggingFragment {
|
||||||
viewModel.getCanAddToAGroup().observe(getViewLifecycleOwner(), canAdd -> addToAGroup.setVisibility(canAdd ? View.VISIBLE : View.GONE));
|
viewModel.getCanAddToAGroup().observe(getViewLifecycleOwner(), canAdd -> addToAGroup.setVisibility(canAdd ? View.VISIBLE : View.GONE));
|
||||||
|
|
||||||
if (SignalStore.internalValues().recipientDetails()) {
|
if (SignalStore.internalValues().recipientDetails()) {
|
||||||
viewModel.getInternalDetails().observe(getViewLifecycleOwner(), internalDetails::setText);
|
viewModel.getInternalDetails().observe(getViewLifecycleOwner(), internalDetailsText::setText);
|
||||||
|
disableProfileSharingButton.setOnClickListener(v -> {
|
||||||
|
SignalExecutors.BOUNDED.execute(() -> DatabaseFactory.getRecipientDatabase(requireContext()).setProfileSharing(recipientId, false));
|
||||||
|
});
|
||||||
internalDetails.setVisibility(View.VISIBLE);
|
internalDetails.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
internalDetails.setVisibility(View.GONE);
|
internalDetails.setVisibility(View.GONE);
|
||||||
|
|
|
@ -55,17 +55,35 @@
|
||||||
android:textColor="@color/signal_text_secondary"
|
android:textColor="@color/signal_text_secondary"
|
||||||
tools:text="\@spidergwen +1 555-654-6657" />
|
tools:text="\@spidergwen +1 555-654-6657" />
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/recipient_internal_details"
|
android:id="@+id/recipient_internal_details"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
android:orientation="vertical"
|
||||||
android:layout_marginTop="14dp"
|
|
||||||
android:textAppearance="@style/Signal.Text.Caption"
|
|
||||||
android:textColor="@color/signal_text_secondary"
|
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:textIsSelectable="true"
|
android:visibility="gone"
|
||||||
android:visibility="gone" />
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/recipient_internal_details_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginTop="14dp"
|
||||||
|
android:textAppearance="@style/Signal.Text.Caption"
|
||||||
|
android:textColor="@color/signal_text_secondary"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
tools:text="Internal Details" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/recipient_internal_details_disable_profile_sharing_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/Signal.Widget.Button.Medium.Secondary"
|
||||||
|
android:text="@string/preferences__internal_disable_profile_sharing"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
|
@ -2411,9 +2411,10 @@
|
||||||
<string name="preferences__internal_network" translatable="false">Network</string>
|
<string name="preferences__internal_network" translatable="false">Network</string>
|
||||||
<string name="preferences__internal_force_censorship" translatable="false">Force censorship</string>
|
<string name="preferences__internal_force_censorship" translatable="false">Force censorship</string>
|
||||||
<string name="preferences__internal_force_censorship_description" translatable="false">Force the app to behave as if it is in a country where Signal is censored.</string>
|
<string name="preferences__internal_force_censorship_description" translatable="false">Force the app to behave as if it is in a country where Signal is censored.</string>
|
||||||
<string name="preferences__internal_conversations_and_shortcuts">Conversations and Shortcuts</string>
|
<string name="preferences__internal_conversations_and_shortcuts" translatable="false">Conversations and Shortcuts</string>
|
||||||
<string name="preferences__internal_delete_all_dynamic_shortcuts">Delete all dynamic shortcuts</string>
|
<string name="preferences__internal_delete_all_dynamic_shortcuts" translatable="false">Delete all dynamic shortcuts</string>
|
||||||
<string name="preferences__internal_click_to_delete_all_dynamic_shortcuts">Click to delete all dynamic shortcuts</string>
|
<string name="preferences__internal_click_to_delete_all_dynamic_shortcuts" translatable="false">Click to delete all dynamic shortcuts</string>
|
||||||
|
<string name="preferences__internal_disable_profile_sharing" translatable="false">Disable Profile Sharing</string>
|
||||||
|
|
||||||
<!-- **************************************** -->
|
<!-- **************************************** -->
|
||||||
<!-- menus -->
|
<!-- menus -->
|
||||||
|
|
Ładowanie…
Reference in New Issue