Skip to content

Commit

Permalink
Add support for scheduled message sends.
Browse files Browse the repository at this point in the history
  • Loading branch information
clark-signal authored and greyson-signal committed Jan 27, 2023
1 parent df695f7 commit f3e715e
Show file tree
Hide file tree
Showing 59 changed files with 1,948 additions and 90 deletions.
2 changes: 2 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,8 @@

<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />

<receiver android:name=".service.ScheduledMessageManager$ScheduledMessagesAlarm" />

<receiver android:name=".service.PendingRetryReceiptManager$PendingRetryReceiptAlarm" />

<receiver android:name=".service.TrimThreadsByDateManager$TrimThreadsByDateAlarm" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ public void onCreate() {
.addNonBlocking(this::cleanAvatarStorage)
.addNonBlocking(this::initializeRevealableMessageManager)
.addNonBlocking(this::initializePendingRetryReceiptManager)
.addNonBlocking(this::initializeScheduledMessageManager)
.addNonBlocking(this::initializeFcmCheck)
.addNonBlocking(PreKeysSyncJob::enqueueIfNeeded)
.addNonBlocking(this::initializePeriodicTasks)
Expand Down Expand Up @@ -390,6 +391,10 @@ private void initializePendingRetryReceiptManager() {
ApplicationDependencies.getPendingRetryReceiptManager().scheduleIfNecessary();
}

private void initializeScheduledMessageManager() {
ApplicationDependencies.getScheduledMessageManager().scheduleIfNecessary();
}

private void initializeTrimThreadsByDateManager() {
KeepMessagesDuration keepMessagesDuration = SignalStore.settings().getKeepMessagesDuration();
if (keepMessagesDuration != KeepMessagesDuration.FOREVER) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ interface EventListener {
void onInviteToSignalClicked();
void onActivatePaymentsClicked();
void onSendPaymentClicked(@NonNull RecipientId recipientId);
void onScheduledIndicatorClicked(@NonNull View view, @NonNull MessageRecord messageRecord);

/** @return true if handled, false if you want to let the normal url handling continue */
boolean onUrlClicked(@NonNull String url);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.MessageRecordUtil;
import org.thoughtcrime.securesms.util.Projection;
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.thoughtcrime.securesms.util.ViewUtil;
Expand Down Expand Up @@ -315,6 +317,8 @@ private void presentDate(@NonNull MessageRecord messageRecord, @NonNull Locale l
dateView.setText(R.string.ConversationItem_click_to_approve_unencrypted);
} else if (messageRecord.isRateLimited()) {
dateView.setText(R.string.ConversationItem_send_paused);
} else if (MessageRecordUtil.isScheduled(messageRecord)) {
dateView.setText(DateUtils.getOnlyTimeString(getContext(), locale, ((MediaMmsMessageRecord) messageRecord).getScheduledDate()));
} else {
dateView.setText(DateUtils.getSimpleRelativeTimeSpanString(getContext(), locale, messageRecord.getTimestamp()));
}
Expand Down Expand Up @@ -392,7 +396,7 @@ private void presentDeliveryStatus(@NonNull MessageRecord messageRecord) {
previousMessageId = newMessageId;


if (messageRecord.isFailed() || messageRecord.isPendingInsecureSmsFallback()) {
if (messageRecord.isFailed() || messageRecord.isPendingInsecureSmsFallback() || MessageRecordUtil.isScheduled(messageRecord)) {
deliveryStatusView.setNone();
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class SendButton(context: Context, attributeSet: AttributeSet?) : AppCompatImage
}

private val listeners: MutableList<SendTypeChangedListener> = CopyOnWriteArrayList()
private var scheduledSendListener: ScheduledSendListener? = null

private var availableSendTypes: List<MessageSendType> = MessageSendType.getAllAvailable(context, false)
private var activeMessageSendType: MessageSendType? = null
Expand Down Expand Up @@ -98,6 +99,10 @@ class SendButton(context: Context, attributeSet: AttributeSet?) : AppCompatImage
onSelectionChanged(newType = selectedSendType, isManualSelection = false)
}

fun setScheduledSendListener(listener: ScheduledSendListener?) {
this.scheduledSendListener = listener
}

fun resetAvailableTransports(isMediaMessage: Boolean) {
availableSendTypes = MessageSendType.getAllAvailable(context, isMediaMessage)
activeMessageSendType = null
Expand Down Expand Up @@ -150,22 +155,44 @@ class SendButton(context: Context, attributeSet: AttributeSet?) : AppCompatImage
}
}

fun showSendTypeMenu(): Boolean {
return if (availableSendTypes.size == 1) {
if (scheduledSendListener == null && !SignalStore.misc().smsExportPhase.allowSmsFeatures()) {
Snackbar.make(snackbarContainer, R.string.InputPanel__sms_messaging_is_no_longer_supported_in_signal, Snackbar.LENGTH_SHORT).show()
}
false
} else {
showSendTypeContextMenu(false)
true
}
}

override fun onLongClick(v: View): Boolean {
if (!isEnabled) {
return false
}

val scheduleListener = scheduledSendListener
if (availableSendTypes.size == 1) {
return if (!SignalStore.misc().smsExportPhase.allowSmsFeatures()) {
return if (scheduleListener != null) {
scheduleListener.onSendScheduled()
true
} else if (!SignalStore.misc().smsExportPhase.allowSmsFeatures()) {
Snackbar.make(snackbarContainer, R.string.InputPanel__sms_messaging_is_no_longer_supported_in_signal, Snackbar.LENGTH_SHORT).show()
true
} else {
false
}
}

val currentlySelected: MessageSendType = selectedSendType
showSendTypeContextMenu(true)

return true
}

private fun showSendTypeContextMenu(allowScheduling: Boolean) {
val currentlySelected: MessageSendType = selectedSendType
val listener = scheduledSendListener
val items = availableSendTypes
.filterNot { it == currentlySelected }
.map { option ->
Expand All @@ -174,17 +201,26 @@ class SendButton(context: Context, attributeSet: AttributeSet?) : AppCompatImage
title = option.getTitle(context),
action = { setSendType(option) }
)
}
}.toMutableList()
if (allowScheduling && listener != null) {
items += ActionItem(
iconRes = R.drawable.ic_calendar_24,
title = context.getString(R.string.conversation_activity__option_schedule_message),
action = { listener.onSendScheduled() }
)
}

SignalContextMenu.Builder((parent as View), popupContainer!!)
.preferredVerticalPosition(SignalContextMenu.VerticalPosition.ABOVE)
.offsetY(ViewUtil.dpToPx(8))
.show(items)

return true
}

interface SendTypeChangedListener {
fun onSendTypeChanged(newType: MessageSendType, manuallySelected: Boolean)
}

interface ScheduledSendListener {
fun onSendScheduled()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.thoughtcrime.securesms.conversation.colors.Colorizable;
import org.thoughtcrime.securesms.conversation.colors.Colorizer;
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Playable;
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicyEnforcer;
Expand Down Expand Up @@ -120,6 +121,7 @@ public class ConversationAdapter
private Colorizer colorizer;
private boolean isTypingViewEnabled;
private boolean condensedMode;
private boolean scheduledMessagesMode;
private PulseRequest pulseRequest;

public ConversationAdapter(@NonNull Context context,
Expand Down Expand Up @@ -261,6 +263,11 @@ public void setCondensedMode(boolean condensedMode) {
notifyDataSetChanged();
}

public void setScheduledMessagesMode(boolean scheduledMessagesMode) {
this.scheduledMessagesMode = scheduledMessagesMode;
notifyDataSetChanged();
}

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
switch (getItemViewType(position)) {
Expand Down Expand Up @@ -331,7 +338,11 @@ public long getHeaderId(int position) {

if (conversationMessage == null) return -1;

calendar.setTimeInMillis(conversationMessage.getMessageRecord().getDateSent());
if (scheduledMessagesMode) {
calendar.setTimeInMillis(((MediaMmsMessageRecord) conversationMessage.getMessageRecord()).getScheduledDate());
} else {
calendar.setTimeInMillis(conversationMessage.getMessageRecord().getDateSent());
}
return calendar.get(Calendar.YEAR) * 1000L + calendar.get(Calendar.DAY_OF_YEAR);
}

Expand All @@ -345,7 +356,11 @@ public void onBindHeaderViewHolder(StickyHeaderViewHolder viewHolder, int positi
Context context = viewHolder.itemView.getContext();
ConversationMessage conversationMessage = Objects.requireNonNull(getItem(position));

viewHolder.setText(DateUtils.getConversationDateHeaderString(viewHolder.itemView.getContext(), locale, conversationMessage.getMessageRecord().getDateSent()));
if (scheduledMessagesMode) {
viewHolder.setText(DateUtils.getScheduledMessagesDateHeaderString(viewHolder.itemView.getContext(), locale, ((MediaMmsMessageRecord) conversationMessage.getMessageRecord()).getScheduledDate()));
} else {
viewHolder.setText(DateUtils.getConversationDateHeaderString(viewHolder.itemView.getContext(), locale, conversationMessage.getMessageRecord().getDateSent()));
}

if (type == HEADER_TYPE_POPOVER_DATE) {
if (hasWallpaper) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ private int getSizeInternal() {
return null;
}

if (record instanceof MediaMmsMessageRecord && ((MediaMmsMessageRecord) record).getScheduledDate() != -1) {
return null;
}

stopwatch.split("message");

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1188,15 +1188,16 @@ private void performSave(final MediaMmsMessageRecord message) {
Toast.LENGTH_LONG).show();
}

public long stageOutgoingMessage(OutgoingMessage message) {
public void stageOutgoingMessage(OutgoingMessage message) {
if (message.getScheduledDate() != -1) {
return;
}
MessageRecord messageRecord = MessageTable.readerFor(message, threadId).getCurrent();

if (getListAdapter() != null) {
setLastSeen(0);
list.post(() -> list.scrollToPosition(0));
}

return messageRecord.getId();
}

private void presentConversationMetadata(@NonNull ConversationData conversation) {
Expand Down Expand Up @@ -2114,6 +2115,11 @@ public void onActivatePaymentsClicked() {
public void onSendPaymentClicked(@NonNull RecipientId recipientId) {
AttachmentManager.selectPayment(ConversationFragment.this, recipient.get());
}

@Override
public void onScheduledIndicatorClicked(@NonNull View view, @NonNull MessageRecord messageRecord) {

}
}

private boolean isUnopenedGift(View itemView, MessageRecord messageRecord) {
Expand Down

0 comments on commit f3e715e

Please sign in to comment.