Connect migrated expiry to settings ui

pull/1014/head
charles 2 years ago
parent b3e9708649
commit 3bb19e4df8

@ -1,88 +0,0 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import org.session.libsession.utilities.ExpirationUtil;
import cn.carbswang.android.numberpickerview.library.NumberPickerView;
import network.loki.messenger.R;
public class ExpirationDialog extends AlertDialog {
protected ExpirationDialog(Context context) {
super(context);
}
protected ExpirationDialog(Context context, int theme) {
super(context, theme);
}
protected ExpirationDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
super(context, cancelable, cancelListener);
}
public static void show(final Context context,
final int currentExpiration,
final @NonNull OnClickListener listener)
{
final View view = createNumberPickerView(context, currentExpiration);
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(context.getString(R.string.ExpirationDialog_disappearing_messages));
builder.setView(view);
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
int selected = ((NumberPickerView)view.findViewById(R.id.expiration_number_picker)).getValue();
listener.onClick(context.getResources().getIntArray(R.array.expiration_times)[selected]);
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
}
private static View createNumberPickerView(final Context context, final int currentExpiration) {
final LayoutInflater inflater = LayoutInflater.from(context);
final View view = inflater.inflate(R.layout.expiration_dialog, null);
final NumberPickerView numberPickerView = view.findViewById(R.id.expiration_number_picker);
final TextView textView = view.findViewById(R.id.expiration_details);
final int[] expirationTimes = context.getResources().getIntArray(R.array.expiration_times);
final String[] expirationDisplayValues = new String[expirationTimes.length];
int selectedIndex = expirationTimes.length - 1;
for (int i=0;i<expirationTimes.length;i++) {
expirationDisplayValues[i] = ExpirationUtil.getExpirationDisplayValue(context, expirationTimes[i]);
if ((currentExpiration >= expirationTimes[i]) &&
(i == expirationTimes.length -1 || currentExpiration < expirationTimes[i+1])) {
selectedIndex = i;
}
}
numberPickerView.setDisplayedValues(expirationDisplayValues);
numberPickerView.setMinValue(0);
numberPickerView.setMaxValue(expirationTimes.length-1);
NumberPickerView.OnValueChangeListener listener = (picker, oldVal, newVal) -> {
if (newVal == 0) {
textView.setText(R.string.ExpirationDialog_your_messages_will_not_expire);
} else {
textView.setText(context.getString(R.string.ExpirationDialog_your_messages_will_disappear_s_after_they_have_been_seen, picker.getDisplayedValues()[newVal]));
}
};
numberPickerView.setOnValueChangedListener(listener);
numberPickerView.setValue(selectedIndex);
listener.onValueChange(numberPickerView, selectedIndex, selectedIndex);
return view;
}
public interface OnClickListener {
public void onClick(int expirationTime);
}
}

@ -112,6 +112,9 @@ class ExpirationSettingsActivity: PassphraseRequiredActionBarActivity() {
} }
}) })
} }
binding.buttonSet.setOnClickListener {
viewModel.onSetClick()
}
lifecycleScope.launchWhenStarted { lifecycleScope.launchWhenStarted {
launch { launch {
viewModel.selectedExpirationType.collect { type -> viewModel.selectedExpirationType.collect { type ->
@ -119,6 +122,12 @@ class ExpirationSettingsActivity: PassphraseRequiredActionBarActivity() {
deleteTypeOptionAdapter.setSelectedPosition(max(0, position)) deleteTypeOptionAdapter.setSelectedPosition(max(0, position))
} }
} }
launch {
viewModel.selectedExpirationTimer.collect { expirationTimer ->
val position = deleteTypeOptions.indexOfFirst { it.value.toIntOrNull() == expirationTimer }
timerOptionAdapter.setSelectedPosition(max(0, position))
}
}
launch { launch {
viewModel.expirationTimerOptions.collect { options -> viewModel.expirationTimerOptions.collect { options ->
binding.textViewTimer.isVisible = options.isNotEmpty() && viewModel.showExpirationTypeSelector binding.textViewTimer.isVisible = options.isNotEmpty() && viewModel.showExpirationTypeSelector

@ -11,8 +11,10 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.session.libsession.messaging.messages.ExpirationConfiguration
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType
import org.thoughtcrime.securesms.database.Storage
import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.preferences.RadioOption import org.thoughtcrime.securesms.preferences.RadioOption
@ -20,29 +22,37 @@ class ExpirationSettingsViewModel(
private val threadId: Long, private val threadId: Long,
private val afterReadOptions: List<RadioOption>, private val afterReadOptions: List<RadioOption>,
private val afterSendOptions: List<RadioOption>, private val afterSendOptions: List<RadioOption>,
private val threadDb: ThreadDatabase private val threadDb: ThreadDatabase,
private val storage: Storage
) : ViewModel() { ) : ViewModel() {
var showExpirationTypeSelector: Boolean = false var showExpirationTypeSelector: Boolean = false
private set private set
private var expirationConfig: ExpirationConfiguration? = null
private val _recipient = MutableStateFlow<Recipient?>(null) private val _recipient = MutableStateFlow<Recipient?>(null)
val recipient: StateFlow<Recipient?> = _recipient val recipient: StateFlow<Recipient?> = _recipient
private val _selectedExpirationType = MutableStateFlow<ExpirationType?>(null) private val _selectedExpirationType = MutableStateFlow<ExpirationType?>(null)
val selectedExpirationType: StateFlow<ExpirationType?> = _selectedExpirationType val selectedExpirationType: StateFlow<ExpirationType?> = _selectedExpirationType
private val _selectedExpirationTimer = MutableStateFlow(0)
val selectedExpirationTimer: StateFlow<Int> = _selectedExpirationTimer
private val _expirationTimerOptions = MutableStateFlow<List<RadioOption>>(emptyList()) private val _expirationTimerOptions = MutableStateFlow<List<RadioOption>>(emptyList())
val expirationTimerOptions: StateFlow<List<RadioOption>> = _expirationTimerOptions val expirationTimerOptions: StateFlow<List<RadioOption>> = _expirationTimerOptions
init { init {
viewModelScope.launch { viewModelScope.launch {
expirationConfig = storage.getExpirationConfiguration(threadId)
val recipient = threadDb.getRecipientForThreadId(threadId) val recipient = threadDb.getRecipientForThreadId(threadId)
_recipient.value = recipient _recipient.value = recipient
showExpirationTypeSelector = recipient?.isContactRecipient == true && recipient.isLocalNumber == false showExpirationTypeSelector = recipient?.isContactRecipient == true && recipient.isLocalNumber == false
} if (recipient?.isLocalNumber == true || recipient?.isClosedGroupRecipient == true) {
if (recipient.value?.isLocalNumber == true || recipient.value?.isClosedGroupRecipient == true) { _selectedExpirationType.value = ExpirationType.DELETE_AFTER_SEND
_selectedExpirationType.value = ExpirationType.DELETE_AFTER_SEND }
_selectedExpirationTimer.value = expirationConfig?.durationSeconds ?: 0
} }
selectedExpirationType.mapLatest { selectedExpirationType.mapLatest {
when (it) { when (it) {
@ -60,7 +70,14 @@ class ExpirationSettingsViewModel(
} }
fun onExpirationTimerSelected(option: RadioOption) { fun onExpirationTimerSelected(option: RadioOption) {
_selectedExpirationTimer.value = option.value.toIntOrNull() ?: 0
}
fun onSetClick() = viewModelScope.launch {
val expiresIn = _selectedExpirationTimer.value
val expiryType = _selectedExpirationType.value?.number ?: 0
val expiryChangeTimestamp = System.currentTimeMillis()
threadDb.updateExpiryConfig(threadId, expiresIn, expiryType, expiryChangeTimestamp)
} }
@dagger.assisted.AssistedFactory @dagger.assisted.AssistedFactory
@ -77,7 +94,8 @@ class ExpirationSettingsViewModel(
@Assisted private val threadId: Long, @Assisted private val threadId: Long,
@Assisted("afterRead") private val afterReadOptions: List<RadioOption>, @Assisted("afterRead") private val afterReadOptions: List<RadioOption>,
@Assisted("afterSend") private val afterSendOptions: List<RadioOption>, @Assisted("afterSend") private val afterSendOptions: List<RadioOption>,
private val threadDb: ThreadDatabase private val threadDb: ThreadDatabase,
private val storage: Storage
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
@ -85,7 +103,8 @@ class ExpirationSettingsViewModel(
threadId, threadId,
afterReadOptions, afterReadOptions,
afterSendOptions, afterSendOptions,
threadDb threadDb,
storage
) as T ) as T
} }
} }

@ -12,7 +12,7 @@ import org.session.libsession.messaging.jobs.Job
import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.jobs.MessageReceiveJob import org.session.libsession.messaging.jobs.MessageReceiveJob
import org.session.libsession.messaging.jobs.MessageSendJob import org.session.libsession.messaging.jobs.MessageSendJob
import org.session.libsession.messaging.messages.ExpirationSettingsConfiguration import org.session.libsession.messaging.messages.ExpirationConfiguration
import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.ConfigurationMessage
import org.session.libsession.messaging.messages.control.MessageRequestResponse import org.session.libsession.messaging.messages.control.MessageRequestResponse
@ -962,12 +962,31 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
return recipientDb.blockedContacts return recipientDb.blockedContacts
} }
override fun getExpirationSettingsConfiguration(threadId: Long): ExpirationSettingsConfiguration? { override fun getExpirationConfiguration(threadId: Long): ExpirationConfiguration? {
val threadDb = DatabaseComponent.get(context).threadDatabase()
threadDb.readerFor(threadDb.conversationList).use { reader ->
while (reader.next != null) {
val thread = reader.current
if (thread.recipient.isClosedGroupRecipient || thread.recipient.isContactRecipient) {
return ExpirationConfiguration(
thread.threadId,
thread.expiresIn.toInt(),
thread.expiryType,
thread.expiryChangeTimestamp
)
}
}
}
return null return null
} }
override fun addExpirationSettingsConfiguration(config: ExpirationSettingsConfiguration) { override fun updateExpirationConfiguration(config: ExpirationConfiguration) {
DatabaseComponent.get(context).threadDatabase().updateExpiryConfig(
config.threadId,
config.durationSeconds,
config.expirationType?.number ?: 0,
config.lastChangeTimestampMs
)
} }
override fun getExpiringMessages(messageIds: LongArray): List<Pair<String, Int>> { override fun getExpiringMessages(messageIds: LongArray): List<Pair<String, Int>> {

@ -18,6 +18,7 @@
package org.thoughtcrime.securesms.database; package org.thoughtcrime.securesms.database;
import static org.session.libsession.utilities.GroupUtil.CLOSED_GROUP_PREFIX; import static org.session.libsession.utilities.GroupUtil.CLOSED_GROUP_PREFIX;
import static org.session.libsession.utilities.GroupUtil.OPEN_GROUP_INBOX_PREFIX;
import static org.session.libsession.utilities.GroupUtil.OPEN_GROUP_PREFIX; import static org.session.libsession.utilities.GroupUtil.OPEN_GROUP_PREFIX;
import static org.thoughtcrime.securesms.database.GroupDatabase.GROUP_ID; import static org.thoughtcrime.securesms.database.GroupDatabase.GROUP_ID;
@ -96,6 +97,8 @@ public class ThreadDatabase extends Database {
public static final String DELIVERY_RECEIPT_COUNT = "delivery_receipt_count"; public static final String DELIVERY_RECEIPT_COUNT = "delivery_receipt_count";
public static final String READ_RECEIPT_COUNT = "read_receipt_count"; public static final String READ_RECEIPT_COUNT = "read_receipt_count";
public static final String EXPIRES_IN = "expires_in"; public static final String EXPIRES_IN = "expires_in";
public static final String EXPIRY_TYPE = "expiry_type";
public static final String EXPIRY_CHANGE_TIMESTAMP= "expiry_change_timestamp";
public static final String LAST_SEEN = "last_seen"; public static final String LAST_SEEN = "last_seen";
public static final String HAS_SENT = "has_sent"; public static final String HAS_SENT = "has_sent";
public static final String IS_PINNED = "is_pinned"; public static final String IS_PINNED = "is_pinned";
@ -118,7 +121,8 @@ public class ThreadDatabase extends Database {
private static final String[] THREAD_PROJECTION = { private static final String[] THREAD_PROJECTION = {
ID, DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, TYPE, ERROR, SNIPPET_TYPE, ID, DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, TYPE, ERROR, SNIPPET_TYPE,
SNIPPET_URI, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT, IS_PINNED SNIPPET_URI, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT, IS_PINNED,
EXPIRY_TYPE, EXPIRY_CHANGE_TIMESTAMP
}; };
private static final List<String> TYPED_THREAD_PROJECTION = Stream.of(THREAD_PROJECTION) private static final List<String> TYPED_THREAD_PROJECTION = Stream.of(THREAD_PROJECTION)
@ -135,6 +139,30 @@ public class ThreadDatabase extends Database {
"ADD COLUMN " + IS_PINNED + " INTEGER DEFAULT 0;"; "ADD COLUMN " + IS_PINNED + " INTEGER DEFAULT 0;";
} }
public static String getCreateExpiryTypeCommand() {
return "ALTER TABLE "+ TABLE_NAME + " " +
"ADD COLUMN " + EXPIRY_TYPE + " INTEGER DEFAULT 0;";
}
public static String getCreateExpiryChangeTimestampCommand() {
return "ALTER TABLE "+ TABLE_NAME + " " +
"ADD COLUMN " + EXPIRY_CHANGE_TIMESTAMP + " INTEGER DEFAULT 0;";
}
public static String getUpdateGroupConversationExpiryTypeCommand() {
return "UPDATE " + TABLE_NAME + " SET " + EXPIRY_TYPE + " = 1 " +
"WHERE " + ADDRESS + " LIKE '" + CLOSED_GROUP_PREFIX + "%'" +
"AND " + EXPIRES_IN + " > 0";
}
public static String getUpdateOneToOneConversationExpiryTypeCommand() {
return "UPDATE " + TABLE_NAME + " SET " + EXPIRY_TYPE + " = 2 " +
"WHERE " + ADDRESS + " NOT LIKE '" + CLOSED_GROUP_PREFIX + "%'" +
"AND " + ADDRESS + " NOT LIKE '" + OPEN_GROUP_PREFIX + "%'" +
"AND " + ADDRESS + " NOT LIKE '" + OPEN_GROUP_INBOX_PREFIX + "%'" +
"AND " + EXPIRES_IN + " > 0";
}
public ThreadDatabase(Context context, SQLCipherOpenHelper databaseHelper) { public ThreadDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper); super(context, databaseHelper);
} }
@ -200,6 +228,18 @@ public class ThreadDatabase extends Database {
notifyConversationListListeners(); notifyConversationListListeners();
} }
public void updateExpiryConfig(long threadId, int expiresIn, int expiryType, long expiryChangeTimestamp) {
ContentValues contentValues = new ContentValues(3);
contentValues.put(EXPIRES_IN, expiresIn);
contentValues.put(EXPIRY_TYPE, expiryType);
contentValues.put(EXPIRY_CHANGE_TIMESTAMP, expiryChangeTimestamp);
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.update(TABLE_NAME, contentValues, ID + " = ?", new String[] {threadId + ""});
notifyConversationListListeners();
}
private void deleteThread(long threadId) { private void deleteThread(long threadId) {
SQLiteDatabase db = databaseHelper.getWritableDatabase(); SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.delete(TABLE_NAME, ID_WHERE, new String[] {threadId + ""}); db.delete(TABLE_NAME, ID_WHERE, new String[] {threadId + ""});
@ -909,6 +949,8 @@ public class ThreadDatabase extends Database {
int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.DELIVERY_RECEIPT_COUNT)); int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.DELIVERY_RECEIPT_COUNT));
int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.READ_RECEIPT_COUNT)); int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.READ_RECEIPT_COUNT));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.EXPIRES_IN)); long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.EXPIRES_IN));
int expiryType = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.EXPIRY_TYPE));
long expiryChangeTimestamp = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.EXPIRY_CHANGE_TIMESTAMP));
long lastSeen = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.LAST_SEEN)); long lastSeen = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.LAST_SEEN));
Uri snippetUri = getSnippetUri(cursor); Uri snippetUri = getSnippetUri(cursor);
boolean pinned = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.IS_PINNED)) != 0; boolean pinned = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.IS_PINNED)) != 0;
@ -919,7 +961,7 @@ public class ThreadDatabase extends Database {
return new ThreadRecord(body, snippetUri, recipient, date, count, return new ThreadRecord(body, snippetUri, recipient, date, count,
unreadCount, threadId, deliveryReceiptCount, status, type, unreadCount, threadId, deliveryReceiptCount, status, type,
distributionType, archived, expiresIn, lastSeen, readReceiptCount, pinned); distributionType, archived, expiresIn, lastSeen, readReceiptCount, pinned, expiryType, expiryChangeTimestamp);
} }
private @Nullable Uri getSnippetUri(Cursor cursor) { private @Nullable Uri getSnippetUri(Cursor cursor) {

@ -75,9 +75,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int lokiV36 = 57; private static final int lokiV36 = 57;
private static final int lokiV37 = 58; private static final int lokiV37 = 58;
private static final int lokiV38 = 59; private static final int lokiV38 = 59;
private static final int lokiV39 = 60;
// Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
private static final int DATABASE_VERSION = lokiV38; private static final int DATABASE_VERSION = lokiV39;
private static final String DATABASE_NAME = "signal.db"; private static final String DATABASE_NAME = "signal.db";
private final Context context; private final Context context;
@ -180,6 +181,8 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(LokiAPIDatabase.RESET_SEQ_NO); // probably not needed but consistent with all migrations db.execSQL(LokiAPIDatabase.RESET_SEQ_NO); // probably not needed but consistent with all migrations
db.execSQL(EmojiSearchDatabase.CREATE_EMOJI_SEARCH_TABLE_COMMAND); db.execSQL(EmojiSearchDatabase.CREATE_EMOJI_SEARCH_TABLE_COMMAND);
db.execSQL(ReactionDatabase.CREATE_REACTION_TABLE_COMMAND); db.execSQL(ReactionDatabase.CREATE_REACTION_TABLE_COMMAND);
db.execSQL(ThreadDatabase.getCreateExpiryTypeCommand());
db.execSQL(ThreadDatabase.getCreateExpiryChangeTimestampCommand());
executeStatements(db, SmsDatabase.CREATE_INDEXS); executeStatements(db, SmsDatabase.CREATE_INDEXS);
executeStatements(db, MmsDatabase.CREATE_INDEXS); executeStatements(db, MmsDatabase.CREATE_INDEXS);
@ -414,6 +417,13 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(EmojiSearchDatabase.CREATE_EMOJI_SEARCH_TABLE_COMMAND); db.execSQL(EmojiSearchDatabase.CREATE_EMOJI_SEARCH_TABLE_COMMAND);
} }
if (oldVersion < lokiV39) {
db.execSQL(ThreadDatabase.getCreateExpiryTypeCommand());
db.execSQL(ThreadDatabase.getCreateExpiryChangeTimestampCommand());
db.execSQL(ThreadDatabase.getUpdateGroupConversationExpiryTypeCommand());
db.execSQL(ThreadDatabase.getUpdateOneToOneConversationExpiryTypeCommand());
}
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();

@ -29,6 +29,7 @@ import androidx.annotation.Nullable;
import org.session.libsession.utilities.ExpirationUtil; import org.session.libsession.utilities.ExpirationUtil;
import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.Recipient;
import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType;
import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase;
@ -48,6 +49,8 @@ public class ThreadRecord extends DisplayRecord {
private final int distributionType; private final int distributionType;
private final boolean archived; private final boolean archived;
private final long expiresIn; private final long expiresIn;
private final int expiryType;
private final long expiryChangeTimestamp;
private final long lastSeen; private final long lastSeen;
private final boolean pinned; private final boolean pinned;
private final int recipientHash; private final int recipientHash;
@ -56,7 +59,7 @@ public class ThreadRecord extends DisplayRecord {
@NonNull Recipient recipient, long date, long count, int unreadCount, @NonNull Recipient recipient, long date, long count, int unreadCount,
long threadId, int deliveryReceiptCount, int status, long snippetType, long threadId, int deliveryReceiptCount, int status, long snippetType,
int distributionType, boolean archived, long expiresIn, long lastSeen, int distributionType, boolean archived, long expiresIn, long lastSeen,
int readReceiptCount, boolean pinned) int readReceiptCount, boolean pinned, int expiryType, long expiryChangeTimestamp)
{ {
super(body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount); super(body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount);
this.snippetUri = snippetUri; this.snippetUri = snippetUri;
@ -65,6 +68,8 @@ public class ThreadRecord extends DisplayRecord {
this.distributionType = distributionType; this.distributionType = distributionType;
this.archived = archived; this.archived = archived;
this.expiresIn = expiresIn; this.expiresIn = expiresIn;
this.expiryType = expiryType;
this.expiryChangeTimestamp = expiryChangeTimestamp;
this.lastSeen = lastSeen; this.lastSeen = lastSeen;
this.pinned = pinned; this.pinned = pinned;
this.recipientHash = recipient.hashCode(); this.recipientHash = recipient.hashCode();
@ -169,6 +174,15 @@ public class ThreadRecord extends DisplayRecord {
return expiresIn; return expiresIn;
} }
@Nullable
public ExpirationType getExpiryType() {
return ExpirationType.valueOf(expiryType);
}
public long getExpiryChangeTimestamp() {
return expiryChangeTimestamp;
}
public long getLastSeen() { public long getLastSeen() {
return lastSeen; return lastSeen;
} }

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:background="?colorPrimary"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center">
<cn.carbswang.android.numberpickerview.library.NumberPickerView
android:id="@+id/expiration_number_picker"
android:layout_alignParentTop="true"
app:npv_WrapSelectorWheel="false"
app:npv_DividerColor="#cbc8ea"
app:npv_TextColorNormal="?android:textColorPrimary"
app:npv_TextColorSelected="?android:textColorPrimary"
app:npv_ItemPaddingVertical="20dp"
app:npv_TextColorHint="@color/grey_600"
app:npv_TextSizeNormal="16sp"
app:npv_TextSizeSelected="16sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/expiration_details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/expiration_number_picker"
android:minLines="3"
android:padding="20dp"
tools:text="Your messages will not expire."/>
</RelativeLayout>

@ -8,7 +8,7 @@ import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.AttachmentUploadJob
import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.Job
import org.session.libsession.messaging.jobs.MessageSendJob import org.session.libsession.messaging.jobs.MessageSendJob
import org.session.libsession.messaging.messages.ExpirationSettingsConfiguration import org.session.libsession.messaging.messages.ExpirationConfiguration
import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.ConfigurationMessage
import org.session.libsession.messaging.messages.control.MessageRequestResponse import org.session.libsession.messaging.messages.control.MessageRequestResponse
@ -199,7 +199,7 @@ interface StorageProtocol {
fun deleteReactions(messageId: Long, mms: Boolean) fun deleteReactions(messageId: Long, mms: Boolean)
fun unblock(toUnblock: List<Recipient>) fun unblock(toUnblock: List<Recipient>)
fun blockedContacts(): List<Recipient> fun blockedContacts(): List<Recipient>
fun getExpirationSettingsConfiguration(threadId: Long): ExpirationSettingsConfiguration? fun getExpirationConfiguration(threadId: Long): ExpirationConfiguration?
fun addExpirationSettingsConfiguration(config: ExpirationSettingsConfiguration) fun updateExpirationConfiguration(config: ExpirationConfiguration)
fun getExpiringMessages(messageIds: LongArray): List<Pair<String, Int>> fun getExpiringMessages(messageIds: LongArray): List<Pair<String, Int>>
} }

@ -2,10 +2,11 @@ package org.session.libsession.messaging.messages
import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType
class ExpirationSettingsConfiguration( class ExpirationConfiguration(
val threadId: Long = -1, val threadId: Long = -1,
val isEnabled: Boolean = false,
val durationSeconds: Int = 0, val durationSeconds: Int = 0,
val expirationType: ExpirationType? = null, val expirationType: ExpirationType? = null,
val lastChangeTimestampMs: Long = 0 val lastChangeTimestampMs: Long = 0
) ) {
val isEnabled = durationSeconds > 0
}

@ -41,7 +41,7 @@ abstract class Message {
fun setExpirationSettingsConfigIfNeeded(builder: SignalServiceProtos.Content.Builder) { fun setExpirationSettingsConfigIfNeeded(builder: SignalServiceProtos.Content.Builder) {
val threadId = threadID ?: return val threadId = threadID ?: return
val config = MessagingModuleConfiguration.shared.storage.getExpirationSettingsConfiguration(threadId) ?: return val config = MessagingModuleConfiguration.shared.storage.getExpirationConfiguration(threadId) ?: return
builder.expirationTimer = config.durationSeconds builder.expirationTimer = config.durationSeconds
if (config.isEnabled) { if (config.isEnabled) {
builder.expirationType = config.expirationType builder.expirationType = config.expirationType

@ -227,7 +227,7 @@ object MessageSender {
val address = if (isSyncMessage && message is VisibleMessage) message.syncTarget else message.recipient val address = if (isSyncMessage && message is VisibleMessage) message.syncTarget else message.recipient
storage.getOrCreateThreadIdFor(address!!) storage.getOrCreateThreadIdFor(address!!)
} }
val config = storage.getExpirationSettingsConfiguration(threadId) ?: return null val config = storage.getExpirationConfiguration(threadId) ?: return null
return if (config.isEnabled && (config.expirationType == ExpirationType.DELETE_AFTER_SEND || isSyncMessage)) { return if (config.isEnabled && (config.expirationType == ExpirationType.DELETE_AFTER_SEND || isSyncMessage)) {
config.durationSeconds * 1000L config.durationSeconds * 1000L
} else null } else null

@ -5,7 +5,7 @@ import org.session.libsession.avatars.AvatarHelper
import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.jobs.BackgroundGroupAddJob import org.session.libsession.messaging.jobs.BackgroundGroupAddJob
import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.messages.ExpirationSettingsConfiguration import org.session.libsession.messaging.messages.ExpirationConfiguration
import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.messages.control.CallMessage import org.session.libsession.messaging.messages.control.CallMessage
import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage
@ -60,7 +60,7 @@ internal fun MessageReceiver.isBlocked(publicKey: String): Boolean {
} }
fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content, openGroupID: String?) { fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content, openGroupID: String?) {
updateExpirationSettingsConfigIfNeeded(message, proto, openGroupID) updateExpirationConfigurationIfNeeded(message, proto, openGroupID)
when (message) { when (message) {
is ReadReceipt -> handleReadReceipt(message) is ReadReceipt -> handleReadReceipt(message)
is TypingIndicator -> handleTypingIndicator(message) is TypingIndicator -> handleTypingIndicator(message)
@ -80,24 +80,22 @@ fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content,
} }
} }
fun updateExpirationSettingsConfigIfNeeded(message: Message, proto: SignalServiceProtos.Content, openGroupID: String?) { fun updateExpirationConfigurationIfNeeded(message: Message, proto: SignalServiceProtos.Content, openGroupID: String?) {
if (!proto.hasLastDisappearingMessageChangeTimestamp()) return if (!proto.hasLastDisappearingMessageChangeTimestamp()) return
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val threadID = storage.getOrCreateThreadIdFor(message.sender!!, message.groupPublicKey, openGroupID) val threadID = storage.getOrCreateThreadIdFor(message.sender!!, message.groupPublicKey, openGroupID)
if (threadID <= 0) return if (threadID <= 0) return
val localConfig = storage.getExpirationSettingsConfiguration(threadID) val localConfig = storage.getExpirationConfiguration(threadID)
if (localConfig == null || localConfig.lastChangeTimestampMs < proto.lastDisappearingMessageChangeTimestamp) return if (localConfig == null || localConfig.lastChangeTimestampMs < proto.lastDisappearingMessageChangeTimestamp) return
val durationSeconds = if (proto.hasExpirationTimer()) proto.expirationTimer else 0 val durationSeconds = if (proto.hasExpirationTimer()) proto.expirationTimer else 0
val isEnabled = durationSeconds != 0
val type = if (proto.hasExpirationType()) proto.expirationType else null val type = if (proto.hasExpirationType()) proto.expirationType else null
val remoteConfig = ExpirationSettingsConfiguration( val remoteConfig = ExpirationConfiguration(
threadID, threadID,
isEnabled,
durationSeconds, durationSeconds,
type, type,
proto.lastDisappearingMessageChangeTimestamp proto.lastDisappearingMessageChangeTimestamp
) )
storage.addExpirationSettingsConfiguration(remoteConfig) storage.updateExpirationConfiguration(remoteConfig)
} }
// region Control Messages // region Control Messages
@ -116,7 +114,7 @@ private fun MessageReceiver.handleSyncedExpiriesMessage(message: SyncedExpiriesM
val userPublicKey = storage.getUserPublicKey() ?: return val userPublicKey = storage.getUserPublicKey() ?: return
if (userPublicKey != message.sender) return if (userPublicKey != message.sender) return
message.conversationExpiries.forEach { (syncTarget, syncedExpiries) -> message.conversationExpiries.forEach { (syncTarget, syncedExpiries) ->
val config = storage.getExpirationSettingsConfiguration(storage.getOrCreateThreadIdFor(syncTarget)) ?: return@forEach val config = storage.getExpirationConfiguration(storage.getOrCreateThreadIdFor(syncTarget)) ?: return@forEach
syncedExpiries.forEach { syncedExpiry -> syncedExpiries.forEach { syncedExpiry ->
val startedAtMs = syncedExpiry.expirationTimestamp!! - config.durationSeconds * 1000 val startedAtMs = syncedExpiry.expirationTimestamp!! - config.durationSeconds * 1000
SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(startedAtMs, syncTarget) SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(startedAtMs, syncTarget)

Loading…
Cancel
Save