mirror of
https://github.com/oxen-io/session-android.git
synced 2025-01-11 23:33:39 +00:00
Connect migrated expiry to settings ui
This commit is contained in:
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…
x
Reference in New Issue
Block a user