Implement additional message request improvements.

This commit is contained in:
Greyson Parrelli
2020-02-21 13:52:27 -05:00
parent 81c7887d47
commit 1faf196f82
43 changed files with 1523 additions and 361 deletions

View File

@@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.messagerequests;
import android.content.Context;
import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.core.util.Consumer;
@@ -9,56 +8,62 @@ import androidx.core.util.Consumer;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.MessagingDatabase;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.MultiDeviceMessageRequestResponseJob;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.List;
import java.util.concurrent.Executor;
public class MessageRequestRepository {
private final Context context;
private final Context context;
private final Executor executor;
public MessageRequestRepository(@NonNull Context context) {
this.context = context.getApplicationContext();
MessageRequestRepository(@NonNull Context context) {
this.context = context.getApplicationContext();
this.executor = SignalExecutors.BOUNDED;
}
public LiveRecipient getLiveRecipient(@NonNull RecipientId recipientId) {
return Recipient.live(recipientId);
}
public void getGroups(@NonNull RecipientId recipientId, @NonNull Consumer<List<String>> onGroupsLoaded) {
SimpleTask.run(() -> {
void getGroups(@NonNull RecipientId recipientId, @NonNull Consumer<List<String>> onGroupsLoaded) {
executor.execute(() -> {
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
return groupDatabase.getGroupNamesContainingMember(recipientId);
}, onGroupsLoaded::accept);
onGroupsLoaded.accept(groupDatabase.getGroupNamesContainingMember(recipientId));
});
}
public void getMemberCount(@NonNull RecipientId recipientId, @NonNull Consumer<Integer> onMemberCountLoaded) {
SimpleTask.run(() -> {
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
Optional<GroupDatabase.GroupRecord> groupRecord = groupDatabase.getGroup(recipientId);
return groupRecord.transform(record -> record.getMembers().size()).or(0);
}, onMemberCountLoaded::accept);
void getMemberCount(@NonNull RecipientId recipientId, @NonNull Consumer<Integer> onMemberCountLoaded) {
executor.execute(() -> {
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
Optional<GroupDatabase.GroupRecord> groupRecord = groupDatabase.getGroup(recipientId);
onMemberCountLoaded.accept(groupRecord.transform(record -> record.getMembers().size()).or(0));
});
}
public void getMessageRequestAccepted(long threadId, @NonNull Consumer<Boolean> recipientRequestAccepted) {
SimpleTask.run(() -> RecipientUtil.isThreadMessageRequestAccepted(context, threadId),
recipientRequestAccepted::accept);
void getMessageRequestState(@NonNull Recipient recipient, long threadId, @NonNull Consumer<MessageRequestState> state) {
executor.execute(() -> {
if (!RecipientUtil.isMessageRequestAccepted(context, threadId)) {
state.accept(MessageRequestState.UNACCEPTED);
} else if (RecipientUtil.isPreMessageRequestThread(context, threadId) && !RecipientUtil.isLegacyProfileSharingAccepted(recipient)) {
state.accept(MessageRequestState.LEGACY);
} else {
state.accept(MessageRequestState.ACCEPTED);
}
});
}
public void acceptMessageRequest(@NonNull LiveRecipient liveRecipient, long threadId, @NonNull Runnable onMessageRequestAccepted) {
SimpleTask.run(() -> {
void acceptMessageRequest(@NonNull LiveRecipient liveRecipient, long threadId, @NonNull Runnable onMessageRequestAccepted) {
executor.execute(()-> {
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
recipientDatabase.setProfileSharing(liveRecipient.getId(), true);
liveRecipient.refresh();
@@ -68,24 +73,84 @@ public class MessageRequestRepository {
MessageNotifier.updateNotification(context);
MarkReadReceiver.process(context, messageIds);
return null;
}, v -> onMessageRequestAccepted.run());
if (TextSecurePreferences.isMultiDevice(context)) {
ApplicationDependencies.getJobManager().add(MultiDeviceMessageRequestResponseJob.forAccept(liveRecipient.getId()));
}
onMessageRequestAccepted.run();
});
}
public void deleteMessageRequest(long threadId, @NonNull Runnable onMessageRequestDeleted) {
SimpleTask.run(() -> {
void deleteMessageRequest(@NonNull LiveRecipient recipient, long threadId, @NonNull Runnable onMessageRequestDeleted) {
executor.execute(() -> {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
threadDatabase.deleteConversation(threadId);
return null;
}, v -> onMessageRequestDeleted.run());
if (recipient.resolve().isGroup()) {
RecipientUtil.leaveGroup(context, recipient.get());
}
if (TextSecurePreferences.isMultiDevice(context)) {
ApplicationDependencies.getJobManager().add(MultiDeviceMessageRequestResponseJob.forDelete(recipient.getId()));
}
onMessageRequestDeleted.run();
});
}
public void blockMessageRequest(@NonNull LiveRecipient liveRecipient, @NonNull Runnable onMessageRequestBlocked) {
SimpleTask.run(() -> {
void blockMessageRequest(@NonNull LiveRecipient liveRecipient, @NonNull Runnable onMessageRequestBlocked) {
executor.execute(() -> {
Recipient recipient = liveRecipient.resolve();
RecipientUtil.block(context, recipient);
liveRecipient.refresh();
return null;
}, v -> onMessageRequestBlocked.run());
if (TextSecurePreferences.isMultiDevice(context)) {
ApplicationDependencies.getJobManager().add(MultiDeviceMessageRequestResponseJob.forBlock(liveRecipient.getId()));
}
onMessageRequestBlocked.run();
});
}
void blockAndDeleteMessageRequest(@NonNull LiveRecipient liveRecipient, long threadId, @NonNull Runnable onMessageRequestBlocked) {
executor.execute(() -> {
Recipient recipient = liveRecipient.resolve();
RecipientUtil.block(context, recipient);
liveRecipient.refresh();
DatabaseFactory.getThreadDatabase(context).deleteConversation(threadId);
if (TextSecurePreferences.isMultiDevice(context)) {
ApplicationDependencies.getJobManager().add(MultiDeviceMessageRequestResponseJob.forBlockAndDelete(liveRecipient.getId()));
}
onMessageRequestBlocked.run();
});
}
void unblockAndAccept(@NonNull LiveRecipient liveRecipient, long threadId, @NonNull Runnable onMessageRequestUnblocked) {
executor.execute(() -> {
Recipient recipient = liveRecipient.resolve();
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
RecipientUtil.unblock(context, recipient);
recipientDatabase.setProfileSharing(liveRecipient.getId(), true);
liveRecipient.refresh();
List<MessagingDatabase.MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context)
.setEntireThreadRead(threadId);
MessageNotifier.updateNotification(context);
MarkReadReceiver.process(context, messageIds);
if (TextSecurePreferences.isMultiDevice(context)) {
ApplicationDependencies.getJobManager().add(MultiDeviceMessageRequestResponseJob.forAccept(liveRecipient.getId()));
}
onMessageRequestUnblocked.run();
});
}
enum MessageRequestState {
ACCEPTED, UNACCEPTED, LEGACY
}
}

View File

@@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.util.livedata.LiveDataTriple;
@@ -24,13 +25,13 @@ import java.util.List;
public class MessageRequestViewModel extends ViewModel {
private final SingleLiveEvent<Status> status = new SingleLiveEvent<>();
private final MutableLiveData<Recipient> recipient = new MutableLiveData<>();
private final MutableLiveData<List<String>> groups = new MutableLiveData<>(Collections.emptyList());
private final MutableLiveData<Integer> memberCount = new MutableLiveData<>(0);
private final MutableLiveData<Boolean> shouldDisplayMessageRequest = new MutableLiveData<>();
private final LiveData<RecipientInfo> recipientInfo = Transformations.map(new LiveDataTriple<>(recipient, memberCount, groups),
triple -> new RecipientInfo(triple.first(), triple.second(), triple.third()));
private final SingleLiveEvent<Status> status = new SingleLiveEvent<>();
private final MutableLiveData<Recipient> recipient = new MutableLiveData<>();
private final MutableLiveData<List<String>> groups = new MutableLiveData<>(Collections.emptyList());
private final MutableLiveData<Integer> memberCount = new MutableLiveData<>(0);
private final MutableLiveData<DisplayState> displayState = new MutableLiveData<>();
private final LiveData<RecipientInfo> recipientInfo = Transformations.map(new LiveDataTriple<>(recipient, memberCount, groups),
triple -> new RecipientInfo(triple.first(), triple.second(), triple.third()));
private final MessageRequestRepository repository;
@@ -39,11 +40,7 @@ public class MessageRequestViewModel extends ViewModel {
@SuppressWarnings("CodeBlock2Expr")
private final RecipientForeverObserver recipientObserver = recipient -> {
if (Recipient.self().equals(recipient) || recipient.isBlocked() || recipient.isForceSmsSelection() || !recipient.isRegistered()) {
shouldDisplayMessageRequest.setValue(false);
} else {
loadMessageRequestAccepted();
}
loadMessageRequestAccepted(recipient);
this.recipient.setValue(recipient);
};
@@ -71,8 +68,8 @@ public class MessageRequestViewModel extends ViewModel {
}
}
public LiveData<Boolean> getShouldDisplayMessageRequest() {
return shouldDisplayMessageRequest;
public LiveData<DisplayState> getMessageRequestDisplayState() {
return displayState;
}
public LiveData<Recipient> getRecipient() {
@@ -83,28 +80,46 @@ public class MessageRequestViewModel extends ViewModel {
return recipientInfo;
}
public LiveData<Status> getMesasgeRequestStatus() {
public LiveData<Status> getMessageRequestStatus() {
return status;
}
public boolean shouldShowMessageRequest() {
return displayState.getValue() == DisplayState.DISPLAY_MESSAGE_REQUEST;
}
@MainThread
public void accept() {
public void onAccept() {
repository.acceptMessageRequest(liveRecipient, threadId, () -> {
status.setValue(Status.ACCEPTED);
status.postValue(Status.ACCEPTED);
});
}
@MainThread
public void delete() {
repository.deleteMessageRequest(threadId, () -> {
status.setValue(Status.DELETED);
public void onDelete() {
repository.deleteMessageRequest(liveRecipient, threadId, () -> {
status.postValue(Status.DELETED);
});
}
@MainThread
public void block() {
public void onBlock() {
repository.blockMessageRequest(liveRecipient, () -> {
status.setValue(Status.BLOCKED);
status.postValue(Status.BLOCKED);
});
}
@MainThread
public void onUnblock() {
repository.unblockAndAccept(liveRecipient, threadId, () -> {
status.postValue(Status.ACCEPTED);
});
}
@MainThread
public void onBlockAndDelete() {
repository.blockAndDeleteMessageRequest(liveRecipient, threadId, () -> {
status.postValue(Status.BLOCKED);
});
}
@@ -114,33 +129,35 @@ public class MessageRequestViewModel extends ViewModel {
}
private void loadGroups() {
repository.getGroups(liveRecipient.getId(), this.groups::setValue);
repository.getGroups(liveRecipient.getId(), this.groups::postValue);
}
private void loadMemberCount() {
repository.getMemberCount(liveRecipient.getId(), memberCount -> {
this.memberCount.setValue(memberCount == null ? 0 : memberCount);
this.memberCount.postValue(memberCount == null ? 0 : memberCount);
});
}
@SuppressWarnings("ConstantConditions")
private void loadMessageRequestAccepted() {
repository.getMessageRequestAccepted(threadId, accepted -> shouldDisplayMessageRequest.setValue(!accepted));
}
public static class Factory implements ViewModelProvider.Factory {
private final Context context;
public Factory(Context context) {
this.context = context;
private void loadMessageRequestAccepted(@NonNull Recipient recipient) {
if (FeatureFlags.messageRequests() && recipient.isBlocked()) {
displayState.postValue(DisplayState.DISPLAY_MESSAGE_REQUEST);
return;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new MessageRequestViewModel(new MessageRequestRepository(context.getApplicationContext()));
}
repository.getMessageRequestState(recipient, threadId, accepted -> {
switch (accepted) {
case ACCEPTED:
displayState.postValue(DisplayState.DISPLAY_NONE);
break;
case UNACCEPTED:
displayState.postValue(DisplayState.DISPLAY_MESSAGE_REQUEST);
break;
case LEGACY:
displayState.postValue(DisplayState.DISPLAY_LEGACY);
break;
}
});
}
public static class RecipientInfo {
@@ -174,4 +191,24 @@ public class MessageRequestViewModel extends ViewModel {
DELETED,
ACCEPTED
}
public enum DisplayState {
DISPLAY_MESSAGE_REQUEST, DISPLAY_LEGACY, DISPLAY_NONE
}
public static class Factory implements ViewModelProvider.Factory {
private final Context context;
public Factory(Context context) {
this.context = context;
}
@Override
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection unchecked
return (T) new MessageRequestViewModel(new MessageRequestRepository(context.getApplicationContext()));
}
}
}

View File

@@ -5,9 +5,14 @@ import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.Group;
import androidx.core.text.HtmlCompat;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.HtmlUtil;
public class MessageRequestsBottomView extends ConstraintLayout {
@@ -15,6 +20,11 @@ public class MessageRequestsBottomView extends ConstraintLayout {
private View accept;
private View block;
private View delete;
private View bigDelete;
private View bigUnblock;
private Group normalButtons;
private Group blockedButtons;
public MessageRequestsBottomView(Context context) {
super(context);
@@ -34,14 +44,34 @@ public class MessageRequestsBottomView extends ConstraintLayout {
inflate(getContext(), R.layout.message_request_bottom_bar, this);
question = findViewById(R.id.message_request_question);
accept = findViewById(R.id.message_request_accept);
block = findViewById(R.id.message_request_block);
delete = findViewById(R.id.message_request_delete);
question = findViewById(R.id.message_request_question);
accept = findViewById(R.id.message_request_accept);
block = findViewById(R.id.message_request_block);
delete = findViewById(R.id.message_request_delete);
bigDelete = findViewById(R.id.message_request_big_delete);
bigUnblock = findViewById(R.id.message_request_big_unblock);
normalButtons = findViewById(R.id.message_request_normal_buttons);
blockedButtons = findViewById(R.id.message_request_blocked_buttons);
}
public void setQuestionText(CharSequence questionText) {
question.setText(questionText);
public void setRecipient(@NonNull Recipient recipient) {
if (recipient.isBlocked()) {
if (recipient.isGroup()) {
question.setText(R.string.MessageRequestBottomView_unblock_to_allow_group_members_to_add_you_to_this_group_again);
} else {
question.setText(HtmlCompat.fromHtml(getContext().getString(R.string.MessageRequestBottomView_unblock_s_to_message_and_call_each_other, HtmlUtil.bold(recipient.getDisplayName(getContext()))), 0));
}
normalButtons.setVisibility(GONE);
blockedButtons.setVisibility(VISIBLE);
} else {
if (recipient.isGroup()) {
question.setText(HtmlCompat.fromHtml(getContext().getString(R.string.MessageRequestBottomView_do_you_want_to_join_the_group_s_they_wont_know_youve_seen_their_messages_until_you_accept, HtmlUtil.bold(recipient.getDisplayName(getContext()))), 0));
} else {
question.setText(HtmlCompat.fromHtml(getContext().getString(R.string.MessageRequestBottomView_do_you_want_to_let_s_message_you_they_wont_know_youve_seen_their_messages_until_you_accept, HtmlUtil.bold(recipient.getDisplayName(getContext()))), 0));
}
normalButtons.setVisibility(VISIBLE);
blockedButtons.setVisibility(GONE);
}
}
public void setAcceptOnClickListener(OnClickListener acceptOnClickListener) {
@@ -50,9 +80,14 @@ public class MessageRequestsBottomView extends ConstraintLayout {
public void setDeleteOnClickListener(OnClickListener deleteOnClickListener) {
delete.setOnClickListener(deleteOnClickListener);
bigDelete.setOnClickListener(deleteOnClickListener);
}
public void setBlockOnClickListener(OnClickListener blockOnClickListener) {
block.setOnClickListener(blockOnClickListener);
}
public void setUnblockOnClickListener(OnClickListener unblockOnClickListener) {
bigUnblock.setOnClickListener(unblockOnClickListener);
}
}