Add mentions for v2 group chats.

This commit is contained in:
Cody Henthorne
2020-08-05 16:45:52 -04:00
committed by Greyson Parrelli
parent 0bb9c1d650
commit b2d4c5d14b
90 changed files with 2279 additions and 372 deletions

View File

@@ -30,6 +30,9 @@ import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.TransportOption;
@@ -42,7 +45,9 @@ import org.thoughtcrime.securesms.components.emoji.EmojiEditText;
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider;
import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher;
import org.thoughtcrime.securesms.conversation.ui.mentions.MentionsPickerViewModel;
import org.thoughtcrime.securesms.imageeditor.model.EditorModel;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter;
@@ -55,6 +60,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.scribbles.ImageEditorFragment;
import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.Function3;
import org.thoughtcrime.securesms.util.IOFunction;
import org.thoughtcrime.securesms.util.MediaUtil;
@@ -76,6 +82,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
/**
* Encompasses the entire flow of sending media, starting from the selection process to the actual
@@ -130,6 +137,7 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
private EmojiEditText captionText;
private EmojiToggle emojiToggle;
private Stub<MediaKeyboard> emojiDrawer;
private Stub<View> mentionSuggestions;
private TextView charactersLeft;
private RecyclerView mediaRail;
private MediaRailAdapter mediaRailAdapter;
@@ -142,7 +150,7 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
/**
* Get an intent to launch the media send flow starting with the picker.
*/
public static Intent buildGalleryIntent(@NonNull Context context, @NonNull Recipient recipient, @Nullable String body, @NonNull TransportOption transport) {
public static Intent buildGalleryIntent(@NonNull Context context, @NonNull Recipient recipient, @Nullable CharSequence body, @NonNull TransportOption transport) {
Intent intent = new Intent(context, MediaSendActivity.class);
intent.putExtra(KEY_RECIPIENT, recipient.getId());
intent.putExtra(KEY_TRANSPORT, transport);
@@ -174,7 +182,7 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
public static Intent buildEditorIntent(@NonNull Context context,
@NonNull List<Media> media,
@NonNull Recipient recipient,
@NonNull String body,
@NonNull CharSequence body,
@NonNull TransportOption transport)
{
Intent intent = buildGalleryIntent(context, recipient, body, transport);
@@ -207,6 +215,7 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
charactersLeft = findViewById(R.id.mediasend_characters_left);
mediaRail = findViewById(R.id.mediasend_media_rail);
emojiDrawer = new Stub<>(findViewById(R.id.mediasend_emoji_drawer_stub));
mentionSuggestions = new Stub<>(findViewById(R.id.mediasend_mention_suggestions_stub));
RecipientId recipientId = getIntent().getParcelableExtra(KEY_RECIPIENT);
if (recipientId != null) {
@@ -222,7 +231,7 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
viewModel.setTransport(transport);
viewModel.setRecipient(recipient != null ? recipient.get() : null);
viewModel.onBodyChanged(getIntent().getStringExtra(KEY_BODY));
viewModel.onBodyChanged(getIntent().getCharSequenceExtra(KEY_BODY));
List<Media> media = getIntent().getParcelableArrayListExtra(KEY_MEDIA);
boolean isCamera = getIntent().getBooleanExtra(KEY_IS_CAMERA, false);
@@ -309,6 +318,7 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
}
initViewModel();
if (FeatureFlags.mentions()) initializeMentionsViewModel();
revealButton.setOnClickListener(v -> viewModel.onRevealButtonToggled());
continueButton.setOnClickListener(v -> navigateToContactSelect());
@@ -525,7 +535,7 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
MediaSendFragment fragment = getMediaSendFragment();
if (fragment != null) {
viewModel.onSendClicked(buildModelsToTransform(fragment), recipients).observe(this, result -> {
viewModel.onSendClicked(buildModelsToTransform(fragment), recipients, composeText.getMentions()).observe(this, result -> {
finish();
});
} else {
@@ -546,7 +556,7 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
sendButton.setEnabled(false);
viewModel.onSendClicked(buildModelsToTransform(fragment), Collections.emptyList()).observe(this, this::setActivityResultAndFinish);
viewModel.onSendClicked(buildModelsToTransform(fragment), Collections.emptyList(), composeText.getMentions()).observe(this, this::setActivityResultAndFinish);
}
private static Map<Media, MediaTransform> buildModelsToTransform(@NonNull MediaSendFragment fragment) {
@@ -751,6 +761,46 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
});
}
private void initializeMentionsViewModel() {
if (recipient == null) {
return;
}
MentionsPickerViewModel mentionsViewModel = ViewModelProviders.of(this, new MentionsPickerViewModel.Factory()).get(MentionsPickerViewModel.class);
recipient.observe(this, mentionsViewModel::onRecipientChange);
composeText.setMentionQueryChangedListener(query -> {
if (recipient.get().isPushV2Group()) {
if (!mentionSuggestions.resolved()) {
mentionSuggestions.get();
}
mentionsViewModel.onQueryChange(query);
}
});
composeText.setMentionValidator(annotations -> {
if (!recipient.get().isPushV2Group()) {
return annotations;
}
Set<String> validRecipientIds = Stream.of(recipient.get().getParticipants())
.map(r -> MentionAnnotation.idToMentionAnnotationValue(r.getId()))
.collect(Collectors.toSet());
return Stream.of(annotations)
.filter(a -> !validRecipientIds.contains(a.getValue()))
.toList();
});
mentionsViewModel.getSelectedRecipient().observe(this, recipient -> {
String replacementDisplayName = recipient.getDisplayName(this);
if (replacementDisplayName.equals(recipient.getDisplayUsername())) {
replacementDisplayName = recipient.getUsername().or(replacementDisplayName);
}
composeText.replaceTextWithMention(replacementDisplayName, recipient.getId());
});
}
private void presentRecipient(@Nullable Recipient recipient) {
if (recipient == null) {
composeText.setHint(R.string.MediaSendActivity_message);
@@ -836,9 +886,9 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
private void presentCharactersRemaining() {
String messageBody = composeText.getTextTrimmed();
String messageBody = composeText.getTextTrimmed().toString();
TransportOption transportOption = sendButton.getSelectedTransport();
CharacterState characterState = transportOption.calculateCharacters(messageBody);
CharacterState characterState = transportOption.calculateCharacters(messageBody);
if (characterState.charactersRemaining <= 15 || characterState.messagesSpent > 1) {
charactersLeft.setText(String.format(Locale.getDefault(),

View File

@@ -7,6 +7,7 @@ import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.TransportOption;
import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.database.model.Mention;
import org.thoughtcrime.securesms.sms.MessageSender.PreUploadResult;
import org.thoughtcrime.securesms.util.ParcelUtil;
import org.whispersystems.libsignal.util.guava.Preconditions;
@@ -24,36 +25,41 @@ public class MediaSendActivityResult implements Parcelable {
private final String body;
private final TransportOption transport;
private final boolean viewOnce;
private final Collection<Mention> mentions;
static @NonNull MediaSendActivityResult forPreUpload(@NonNull Collection<PreUploadResult> uploadResults,
@NonNull String body,
@NonNull TransportOption transport,
boolean viewOnce)
boolean viewOnce,
@NonNull List<Mention> mentions)
{
Preconditions.checkArgument(uploadResults.size() > 0, "Must supply uploadResults!");
return new MediaSendActivityResult(uploadResults, Collections.emptyList(), body, transport, viewOnce);
return new MediaSendActivityResult(uploadResults, Collections.emptyList(), body, transport, viewOnce, mentions);
}
static @NonNull MediaSendActivityResult forTraditionalSend(@NonNull List<Media> nonUploadedMedia,
@NonNull String body,
@NonNull TransportOption transport,
boolean viewOnce)
boolean viewOnce,
@NonNull List<Mention> mentions)
{
Preconditions.checkArgument(nonUploadedMedia.size() > 0, "Must supply media!");
return new MediaSendActivityResult(Collections.emptyList(), nonUploadedMedia, body, transport, viewOnce);
return new MediaSendActivityResult(Collections.emptyList(), nonUploadedMedia, body, transport, viewOnce, mentions);
}
private MediaSendActivityResult(@NonNull Collection<PreUploadResult> uploadResults,
@NonNull List<Media> nonUploadedMedia,
@NonNull String body,
@NonNull TransportOption transport,
boolean viewOnce)
boolean viewOnce,
@NonNull List<Mention> mentions)
{
this.uploadResults = uploadResults;
this.nonUploadedMedia = nonUploadedMedia;
this.body = body;
this.transport = transport;
this.viewOnce = viewOnce;
this.mentions = mentions;
}
private MediaSendActivityResult(Parcel in) {
@@ -62,6 +68,7 @@ public class MediaSendActivityResult implements Parcelable {
this.body = in.readString();
this.transport = in.readParcelable(TransportOption.class.getClassLoader());
this.viewOnce = ParcelUtil.readBoolean(in);
this.mentions = ParcelUtil.readParcelableCollection(in, Mention.class);
}
public boolean isPushPreUpload() {
@@ -88,6 +95,10 @@ public class MediaSendActivityResult implements Parcelable {
return viewOnce;
}
public @NonNull Collection<Mention> getMentions() {
return mentions;
}
public static final Creator<MediaSendActivityResult> CREATOR = new Creator<MediaSendActivityResult>() {
@Override
public MediaSendActivityResult createFromParcel(Parcel in) {
@@ -112,5 +123,6 @@ public class MediaSendActivityResult implements Parcelable {
dest.writeString(body);
dest.writeParcelable(transport, 0);
ParcelUtil.writeBoolean(dest, viewOnce);
ParcelUtil.writeParcelableCollection(dest, mentions);
}
}

View File

@@ -17,6 +17,7 @@ import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.TransportOption;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.Mention;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
@@ -453,7 +454,7 @@ class MediaSendViewModel extends ViewModel {
savedDrawState.putAll(state);
}
@NonNull LiveData<MediaSendActivityResult> onSendClicked(Map<Media, MediaTransform> modelsToTransform, @NonNull List<Recipient> recipients) {
@NonNull LiveData<MediaSendActivityResult> onSendClicked(Map<Media, MediaTransform> modelsToTransform, @NonNull List<Recipient> recipients, @NonNull List<Mention> mentions) {
if (isSms && recipients.size() > 0) {
throw new IllegalStateException("Provided recipients to send to, but this is SMS!");
}
@@ -476,7 +477,7 @@ class MediaSendViewModel extends ViewModel {
if (isSms || MessageSender.isLocalSelfSend(application, recipient, isSms)) {
Log.i(TAG, "SMS or local self-send. Skipping pre-upload.");
result.postValue(MediaSendActivityResult.forTraditionalSend(updatedMedia, trimmedBody, transport, isViewOnce()));
result.postValue(MediaSendActivityResult.forTraditionalSend(updatedMedia, trimmedBody, transport, isViewOnce(), mentions));
return;
}
@@ -493,12 +494,12 @@ class MediaSendViewModel extends ViewModel {
uploadRepository.updateDisplayOrder(updatedMedia);
uploadRepository.getPreUploadResults(uploadResults -> {
if (recipients.size() > 0) {
sendMessages(recipients, splitBody, uploadResults);
sendMessages(recipients, splitBody, uploadResults, mentions);
uploadRepository.deleteAbandonedAttachments();
}
Util.cancelRunnableOnMain(dialogRunnable);
result.postValue(MediaSendActivityResult.forPreUpload(uploadResults, splitBody, transport, isViewOnce()));
result.postValue(MediaSendActivityResult.forPreUpload(uploadResults, splitBody, transport, isViewOnce(), mentions));
});
});
@@ -632,7 +633,7 @@ class MediaSendViewModel extends ViewModel {
}
@WorkerThread
private void sendMessages(@NonNull List<Recipient> recipients, @NonNull String body, @NonNull Collection<PreUploadResult> preUploadResults) {
private void sendMessages(@NonNull List<Recipient> recipients, @NonNull String body, @NonNull Collection<PreUploadResult> preUploadResults, @NonNull List<Mention> mentions) {
List<OutgoingSecureMediaMessage> messages = new ArrayList<>(recipients.size());
for (Recipient recipient : recipients) {
@@ -647,6 +648,7 @@ class MediaSendViewModel extends ViewModel {
null,
Collections.emptyList(),
Collections.emptyList(),
mentions,
Collections.emptyList(),
Collections.emptyList());