Compare commits

...

38 Commits

Author SHA1 Message Date
Andrew 0e7a981386
Merge pull request #1445 from alansley/SES1251_AppCrashOnNonAlphanumeric_REFIX
SES1251 - App crash on non alphanumeric RE-FIX
4 weeks ago
Andrew e1652f8c6b
Merge pull request #1466 from AL-Session/SES1688_LastDeletedMessageFix
SES1688 - Last deleted message REFIX
4 weeks ago
Andrew 797d6eb658
Merge pull request #1329 from bemusementpark/fix-dialog-button
Fix dialog button style
4 weeks ago
Andrew afa9af1803
Merge pull request #1465 from bemusementpark/ses-1733
[SES-1733] Fix group expiration update messages
4 weeks ago
Andrew 569d0c23a2
Merge pull request #1463 from bemusementpark/fix-multi-preview
[SES-1732] Fix multiple link previews
4 weeks ago
Andrew 577e86e31f Correctly don't start disappear timer on group timer updates 4 weeks ago
Andrew dbe3834dc1 Fix expiry update message for groups 4 weeks ago
Andrew c84f543069 Merge branch 'dev' into ses-1733 4 weeks ago
Andrew 4876523bf2 Don't start expiration for group expiration update messages 4 weeks ago
Al Lansley a7e255091b Implemented PR feedback 1 month ago
Al Lansley 3b3935f9be Cleanup for PR review 1 month ago
Al Lansley 4ee5f36ad8 Merged dev & fixed merge conflicts 1 month ago
Al Lansley 2b46441977 Fixed conversation view closing + hopefully wrong status text displayed + deletion of contact on removal of last message in 1-on-1 convo 1 month ago
Andrew 0ba18dcd46 Fix group expiration update config messages 1 month ago
Andrew 207da18115 Remove old expiration config strings from UpdateMessageBuilder 1 month ago
Al Lansley 13902607b2 Commit before converting SmsDatabase from Java to Kotlin 1 month ago
alansley 26fb268c76 WIP 1 month ago
Andrew c942bbac25 Fix margins 1 month ago
Andrew c28eba313a Merge branch 'dev' into fix-dialog-button 1 month ago
Andrew 3a36fe7fb1 Merge branch 'dev' into fix-dialog-button 1 month ago
Andrew 706e5766bb Fix multiple link previews 1 month ago
Al Lansley 0ab0ecbb55 Addressed PR feedback 1 month ago
Al Lansley dce0fd8413 Fixes #1448 1 month ago
Al Lansley ad51746e84 Initial fix implemented 1 month ago
Al Lansley 0146a2975f Cleanup 1 month ago
Al Lansley f6d21534e0 Addressed PR feedback 1 month ago
Al Lansley 48a95a1357 Merged dev & fixed conflicts 1 month ago
Al Lansley 04fb296787 Fixes #1346 - properly this time! 1 month ago
Al Lansley d65705c845 Push before cleanup 1 month ago
Al Lansley 73f11c5a4f Merge branch 'dev' into SA1346_AppCrashOnNonAlphanumericFirstCharSearch 2 months ago
Andrew a26b0e52d4 Merge branch 'dev' into fix-dialog-button 2 months ago
alansley b21cdab0e4 Minor cleanup 3 months ago
alansley cab1ffec1e Put back some whitespace 4 months ago
alansley 42e58c477b Removed unused logging imports 4 months ago
alansley 09e48fdc5c Fixes #1346 4 months ago
alansley cfe2dbca7b Working fix push before cleanup 4 months ago
alansley 88ceffeee2 Investigation in progress 4 months ago
andrew 1f6542eff3 Fix dialog button style 8 months ago

@ -7,7 +7,6 @@ import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.Button
import android.widget.LinearLayout
import android.widget.LinearLayout.LayoutParams
import android.widget.LinearLayout.VERTICAL
import android.widget.Space
import android.widget.TextView
@ -31,6 +30,7 @@ class SessionDialogBuilder(val context: Context) {
private val dp20 = toPx(20, context.resources)
private val dp40 = toPx(40, context.resources)
private val dp60 = toPx(60, context.resources)
private val dialogBuilder: AlertDialog.Builder = AlertDialog.Builder(context)
@ -76,7 +76,7 @@ class SessionDialogBuilder(val context: Context) {
}.let(topView::addView)
Space(context).apply {
layoutParams = LayoutParams(0, dp20)
layoutParams = LinearLayout.LayoutParams(0, dp20)
}.let(topView::addView)
}
@ -130,8 +130,7 @@ class SessionDialogBuilder(val context: Context) {
) = Button(context, null, 0, style).apply {
setText(text)
contentDescription = resources.getString(contentDescriptionRes)
layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, 1f)
.apply { setMargins(dp20) }
layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, dp60, 1f)
setOnClickListener {
listener.invoke()
if (dismiss) dismiss()

@ -35,11 +35,28 @@ class ContactListAdapter(
binding.profilePictureView.update(contact.recipient)
binding.nameTextView.text = contact.displayName
binding.root.setOnClickListener { listener(contact.recipient) }
}
fun unbind() {
binding.profilePictureView.recycle()
// TODO: When we implement deleting contacts (hide might be safest) then probably set a long-click listener here w/ something like:
/*
binding.root.setOnLongClickListener {
Log.w("[ACL]", "Long clicked on contact ${contact.recipient.name}")
binding.contentView.context.showSessionDialog {
title("Delete Contact")
text("Are you sure you want to delete this contact?")
button(R.string.delete) {
val contacts = configFactory.contacts ?: return
contacts.upsertContact(contact.recipient.address.serialize()) { priority = PRIORITY_HIDDEN }
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
endActionMode()
}
cancelButton(::endActionMode)
}
true
}
*/
}
fun unbind() { binding.profilePictureView.recycle() }
}
class HeaderViewHolder(
@ -52,15 +69,11 @@ class ContactListAdapter(
}
}
override fun getItemCount(): Int {
return items.size
}
override fun getItemCount(): Int { return items.size }
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
super.onViewRecycled(holder)
if (holder is ContactViewHolder) {
holder.unbind()
}
if (holder is ContactViewHolder) { holder.unbind() }
}
override fun getItemViewType(position: Int): Int {
@ -72,13 +85,9 @@ class ContactListAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == ViewType.Contact) {
ContactViewHolder(
ViewContactBinding.inflate(LayoutInflater.from(context), parent, false)
)
ContactViewHolder(ViewContactBinding.inflate(LayoutInflater.from(context), parent, false))
} else {
HeaderViewHolder(
ContactSectionHeaderBinding.inflate(LayoutInflater.from(context), parent, false)
)
HeaderViewHolder(ContactSectionHeaderBinding.inflate(LayoutInflater.from(context), parent, false))
}
}

@ -180,7 +180,7 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li
// message we'll bail early if a link preview View already exists and just let
// `updateLinkPreview` get called to update the existing View.
if (linkPreview != null && linkPreviewDraftView != null) return
linkPreviewDraftView?.let(binding.inputBarAdditionalContentContainer::removeView)
linkPreviewDraftView = LinkPreviewDraftView(context).also { it.delegate = this }
// Add the link preview View. Note: If there's already a quote View in the 'additional

@ -22,15 +22,11 @@ import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.annimon.stream.Stream;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteStatement;
import org.apache.commons.lang3.StringUtils;
import org.session.libsession.messaging.calls.CallMessageType;
import org.session.libsession.messaging.messages.signal.IncomingGroupMessage;
@ -51,7 +47,6 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.ReactionRecord;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
import java.io.Closeable;
import java.io.IOException;
import java.security.SecureRandom;
@ -634,7 +629,7 @@ public class SmsDatabase extends MessagingDatabase {
long threadId = getThreadIdForMessage(messageId);
db.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""});
notifyConversationListeners(threadId);
boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false, true);
boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false, false);
return threadDeleted;
}
@ -701,12 +696,7 @@ public class SmsDatabase extends MessagingDatabase {
}
}
/*package */void deleteThread(long threadId) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.delete(TABLE_NAME, THREAD_ID + " = ?", new String[] {threadId+""});
}
/*package*/void deleteMessagesInThreadBeforeDate(long threadId, long date) {
void deleteMessagesInThreadBeforeDate(long threadId, long date) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
String where = THREAD_ID + " = ? AND (CASE " + TYPE;
@ -719,7 +709,12 @@ public class SmsDatabase extends MessagingDatabase {
db.delete(TABLE_NAME, where, new String[] {threadId + ""});
}
/*package*/ void deleteThreads(Set<Long> threadIds) {
void deleteThread(long threadId) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.delete(TABLE_NAME, THREAD_ID + " = ?", new String[] {threadId+""});
}
void deleteThreads(Set<Long> threadIds) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
String where = "";
@ -727,23 +722,23 @@ public class SmsDatabase extends MessagingDatabase {
where += THREAD_ID + " = '" + threadId + "' OR ";
}
where = where.substring(0, where.length() - 4);
where = where.substring(0, where.length() - 4); // Remove the final: "' OR "
db.delete(TABLE_NAME, where, null);
}
/*package */ void deleteAllThreads() {
void deleteAllThreads() {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.delete(TABLE_NAME, null, null);
}
/*package*/ SQLiteDatabase beginTransaction() {
SQLiteDatabase beginTransaction() {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.beginTransaction();
return database;
}
/*package*/ void endTransaction(SQLiteDatabase database) {
void endTransaction(SQLiteDatabase database) {
database.setTransactionSuccessful();
database.endTransaction();
}

@ -99,7 +99,7 @@ private const val TAG = "Storage"
open class Storage(
context: Context,
helper: SQLCipherOpenHelper,
private val configFactory: ConfigFactory
val configFactory: ConfigFactory
) : Database(context, helper), StorageProtocol, ThreadDatabase.ConversationThreadUpdateListener {
override fun threadCreated(address: Address, threadId: Long) {
@ -1371,29 +1371,29 @@ open class Storage(
val threadDB = DatabaseComponent.get(context).threadDatabase()
val groupDB = DatabaseComponent.get(context).groupDatabase()
threadDB.deleteConversation(threadID)
val recipient = getRecipientForThread(threadID) ?: return
when {
recipient.isContactRecipient -> {
if (recipient.isLocalNumber) return
val contacts = configFactory.contacts ?: return
contacts.upsertContact(recipient.address.serialize()) { priority = PRIORITY_HIDDEN }
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
}
recipient.isClosedGroupRecipient -> {
// TODO: handle closed group
val volatile = configFactory.convoVolatile ?: return
val groups = configFactory.userGroups ?: return
val groupID = recipient.address.toGroupString()
val closedGroup = getGroup(groupID)
val groupPublicKey = GroupUtil.doubleDecodeGroupId(recipient.address.serialize())
if (closedGroup != null) {
groupDB.delete(groupID) // TODO: Should we delete the group? (seems odd to leave it)
volatile.eraseLegacyClosedGroup(groupPublicKey)
groups.eraseLegacyGroup(groupPublicKey)
} else {
Log.w("Loki-DBG", "Failed to find a closed group for ${groupPublicKey.take(4)}")
}
}
val recipient = getRecipientForThread(threadID)
if (recipient == null) {
Log.w(TAG, "Got null recipient when deleting conversation - aborting.");
return
}
// There is nothing further we need to do if this is a 1-on-1 conversation, and it's not
// possible to delete communities in this manner so bail.
if (recipient.isContactRecipient || recipient.isCommunityRecipient) return
// If we get here then this is a closed group conversation (i.e., recipient.isClosedGroupRecipient)
val volatile = configFactory.convoVolatile ?: return
val groups = configFactory.userGroups ?: return
val groupID = recipient.address.toGroupString()
val closedGroup = getGroup(groupID)
val groupPublicKey = GroupUtil.doubleDecodeGroupId(recipient.address.serialize())
if (closedGroup != null) {
groupDB.delete(groupID)
volatile.eraseLegacyClosedGroup(groupPublicKey)
groups.eraseLegacyGroup(groupPublicKey)
} else {
Log.w("Loki-DBG", "Failed to find a closed group for ${groupPublicKey.take(4)}")
}
}

@ -26,14 +26,10 @@ import android.content.Context;
import android.database.Cursor;
import android.database.MergeCursor;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.annimon.stream.Stream;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.jetbrains.annotations.NotNull;
import org.session.libsession.snode.SnodeAPI;
import org.session.libsession.utilities.Address;
@ -61,7 +57,6 @@ import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
import org.thoughtcrime.securesms.util.SessionMetaProtocol;
import java.io.Closeable;
import java.util.Collections;
import java.util.HashMap;
@ -83,7 +78,7 @@ public class ThreadDatabase extends Database {
public static final String TABLE_NAME = "thread";
public static final String ID = "_id";
public static final String DATE = "date";
public static final String THREAD_CREATION_DATE = "date";
public static final String MESSAGE_COUNT = "message_count";
public static final String ADDRESS = "recipient_ids";
public static final String SNIPPET = "snippet";
@ -91,7 +86,7 @@ public class ThreadDatabase extends Database {
public static final String READ = "read";
public static final String UNREAD_COUNT = "unread_count";
public static final String UNREAD_MENTION_COUNT = "unread_mention_count";
public static final String TYPE = "type";
public static final String DISTRIBUTION_TYPE = "type"; // See: DistributionTypes.kt
private static final String ERROR = "error";
public static final String SNIPPET_TYPE = "snippet_type";
public static final String SNIPPET_URI = "snippet_uri";
@ -101,27 +96,27 @@ public class ThreadDatabase extends Database {
public static final String READ_RECEIPT_COUNT = "read_receipt_count";
public static final String EXPIRES_IN = "expires_in";
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 CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" +
ID + " INTEGER PRIMARY KEY, " + DATE + " INTEGER DEFAULT 0, " +
ID + " INTEGER PRIMARY KEY, " + THREAD_CREATION_DATE + " INTEGER DEFAULT 0, " +
MESSAGE_COUNT + " INTEGER DEFAULT 0, " + ADDRESS + " TEXT, " + SNIPPET + " TEXT, " +
SNIPPET_CHARSET + " INTEGER DEFAULT 0, " + READ + " INTEGER DEFAULT 1, " +
TYPE + " INTEGER DEFAULT 0, " + ERROR + " INTEGER DEFAULT 0, " +
DISTRIBUTION_TYPE + " INTEGER DEFAULT 0, " + ERROR + " INTEGER DEFAULT 0, " +
SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL, " +
ARCHIVED + " INTEGER DEFAULT 0, " + STATUS + " INTEGER DEFAULT 0, " +
DELIVERY_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + EXPIRES_IN + " INTEGER DEFAULT 0, " +
LAST_SEEN + " INTEGER DEFAULT 0, " + HAS_SENT + " INTEGER DEFAULT 0, " +
READ_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + UNREAD_COUNT + " INTEGER DEFAULT 0);";
public static final String[] CREATE_INDEXS = {
public static final String[] CREATE_INDEXES = {
"CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + ADDRESS + ");",
"CREATE INDEX IF NOT EXISTS archived_count_index ON " + TABLE_NAME + " (" + ARCHIVED + ", " + MESSAGE_COUNT + ");",
};
private static final String[] THREAD_PROJECTION = {
ID, DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, UNREAD_MENTION_COUNT, TYPE, ERROR, SNIPPET_TYPE,
ID, THREAD_CREATION_DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, UNREAD_MENTION_COUNT, DISTRIBUTION_TYPE, ERROR, SNIPPET_TYPE,
SNIPPET_URI, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT, IS_PINNED
};
@ -131,8 +126,8 @@ public class ThreadDatabase extends Database {
private static final List<String> COMBINED_THREAD_RECIPIENT_GROUP_PROJECTION = Stream.concat(Stream.concat(Stream.of(TYPED_THREAD_PROJECTION),
Stream.of(RecipientDatabase.TYPED_RECIPIENT_PROJECTION)),
Stream.of(GroupDatabase.TYPED_GROUP_PROJECTION))
.toList();
Stream.of(GroupDatabase.TYPED_GROUP_PROJECTION))
.toList();
public static String getCreatePinnedCommand() {
return "ALTER TABLE "+ TABLE_NAME + " " +
@ -158,11 +153,10 @@ public class ThreadDatabase extends Database {
ContentValues contentValues = new ContentValues(4);
long date = SnodeAPI.getNowWithOffset();
contentValues.put(DATE, date - date % 1000);
contentValues.put(THREAD_CREATION_DATE, date - date % 1000);
contentValues.put(ADDRESS, address.serialize());
if (group)
contentValues.put(TYPE, distributionType);
if (group) contentValues.put(DISTRIBUTION_TYPE, distributionType);
contentValues.put(MESSAGE_COUNT, 0);
@ -175,7 +169,7 @@ public class ThreadDatabase extends Database {
long expiresIn, int readReceiptCount)
{
ContentValues contentValues = new ContentValues(7);
contentValues.put(DATE, date - date % 1000);
contentValues.put(THREAD_CREATION_DATE, date - date % 1000);
contentValues.put(MESSAGE_COUNT, count);
if (!body.isEmpty()) {
contentValues.put(SNIPPET, body);
@ -187,9 +181,7 @@ public class ThreadDatabase extends Database {
contentValues.put(READ_RECEIPT_COUNT, readReceiptCount);
contentValues.put(EXPIRES_IN, expiresIn);
if (unarchive) {
contentValues.put(ARCHIVED, 0);
}
if (unarchive) { contentValues.put(ARCHIVED, 0); }
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.update(TABLE_NAME, contentValues, ID + " = ?", new String[] {threadId + ""});
@ -199,7 +191,7 @@ public class ThreadDatabase extends Database {
public void updateSnippet(long threadId, String snippet, @Nullable Uri attachment, long date, long type, boolean unarchive) {
ContentValues contentValues = new ContentValues(4);
contentValues.put(DATE, date - date % 1000);
contentValues.put(THREAD_CREATION_DATE, date - date % 1000);
if (!snippet.isEmpty()) {
contentValues.put(SNIPPET, snippet);
}
@ -230,9 +222,7 @@ public class ThreadDatabase extends Database {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
String where = "";
for (long threadId : threadIds) {
where += ID + " = '" + threadId + "' OR ";
}
for (long threadId : threadIds) { where += ID + " = '" + threadId + "' OR "; }
where = where.substring(0, where.length() - 4);
@ -358,7 +348,7 @@ public class ThreadDatabase extends Database {
public void setDistributionType(long threadId, int distributionType) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(TYPE, distributionType);
contentValues.put(DISTRIBUTION_TYPE, distributionType);
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId + ""});
@ -367,7 +357,7 @@ public class ThreadDatabase extends Database {
public void setDate(long threadId, long date) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(DATE, date);
contentValues.put(THREAD_CREATION_DATE, date);
SQLiteDatabase db = databaseHelper.getWritableDatabase();
int updated = db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId+""});
if (updated > 0) notifyConversationListListeners();
@ -375,11 +365,11 @@ public class ThreadDatabase extends Database {
public int getDistributionType(long threadId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = db.query(TABLE_NAME, new String[]{TYPE}, ID_WHERE, new String[]{String.valueOf(threadId)}, null, null, null);
Cursor cursor = db.query(TABLE_NAME, new String[]{DISTRIBUTION_TYPE}, ID_WHERE, new String[]{String.valueOf(threadId)}, null, null, null);
try {
if (cursor != null && cursor.moveToNext()) {
return cursor.getInt(cursor.getColumnIndexOrThrow(TYPE));
return cursor.getInt(cursor.getColumnIndexOrThrow(DISTRIBUTION_TYPE));
}
return DistributionTypes.DEFAULT;
@ -469,7 +459,7 @@ public class ThreadDatabase extends Database {
Cursor cursor = null;
try {
String where = "SELECT " + DATE + " FROM " + TABLE_NAME +
String where = "SELECT " + THREAD_CREATION_DATE + " FROM " + TABLE_NAME +
" LEFT OUTER JOIN " + RecipientDatabase.TABLE_NAME +
" ON " + TABLE_NAME + "." + ADDRESS + " = " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.ADDRESS +
" LEFT OUTER JOIN " + GroupDatabase.TABLE_NAME +
@ -477,7 +467,7 @@ public class ThreadDatabase extends Database {
" WHERE " + MESSAGE_COUNT + " != 0 AND " + ARCHIVED + " = 0 AND " + HAS_SENT + " = 0 AND " +
RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.BLOCK + " = 0 AND " +
RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.APPROVED + " = 0 AND " +
GroupDatabase.TABLE_NAME + "." + GROUP_ID + " IS NULL ORDER BY " + DATE + " DESC LIMIT 1";
GroupDatabase.TABLE_NAME + "." + GROUP_ID + " IS NULL ORDER BY " + THREAD_CREATION_DATE + " DESC LIMIT 1";
cursor = db.rawQuery(where, null);
if (cursor != null && cursor.moveToFirst())
@ -595,7 +585,7 @@ public class ThreadDatabase extends Database {
public Long getLastUpdated(long threadId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = db.query(TABLE_NAME, new String[]{DATE}, ID_WHERE, new String[]{String.valueOf(threadId)}, null, null, null);
Cursor cursor = db.query(TABLE_NAME, new String[]{THREAD_CREATION_DATE}, ID_WHERE, new String[]{String.valueOf(threadId)}, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
@ -736,7 +726,7 @@ public class ThreadDatabase extends Database {
MmsSmsDatabase mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase();
long count = mmsSmsDatabase.getConversationCount(threadId);
boolean shouldDeleteEmptyThread = shouldDeleteOnEmpty && deleteThreadOnEmpty(threadId);
boolean shouldDeleteEmptyThread = shouldDeleteOnEmpty && possibleToDeleteThreadOnEmpty(threadId);
if (count == 0 && shouldDeleteEmptyThread) {
deleteThread(threadId);
@ -810,7 +800,7 @@ public class ThreadDatabase extends Database {
return setLastSeen(threadId, lastSeenTime);
}
private boolean deleteThreadOnEmpty(long threadId) {
private boolean possibleToDeleteThreadOnEmpty(long threadId) {
Recipient threadRecipient = getRecipientForThreadId(threadId);
return threadRecipient != null && !threadRecipient.isCommunityRecipient();
}
@ -855,7 +845,7 @@ public class ThreadDatabase extends Database {
" LEFT OUTER JOIN " + GroupDatabase.TABLE_NAME +
" ON " + TABLE_NAME + "." + ADDRESS + " = " + GroupDatabase.TABLE_NAME + "." + GROUP_ID +
" WHERE " + where +
" ORDER BY " + TABLE_NAME + "." + IS_PINNED + " DESC, " + TABLE_NAME + "." + DATE + " DESC";
" ORDER BY " + TABLE_NAME + "." + IS_PINNED + " DESC, " + TABLE_NAME + "." + THREAD_CREATION_DATE + " DESC";
if (limit > 0) {
query += " LIMIT " + limit;
@ -900,7 +890,7 @@ public class ThreadDatabase extends Database {
public ThreadRecord getCurrent() {
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.ID));
int distributionType = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.TYPE));
int distributionType = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.DISTRIBUTION_TYPE));
Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.ADDRESS)));
Optional<RecipientSettings> settings;
@ -916,7 +906,7 @@ public class ThreadDatabase extends Database {
Recipient recipient = Recipient.from(context, address, settings, groupRecord, true);
String body = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET));
long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE));
long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.THREAD_CREATION_DATE));
long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT));
int unreadCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.UNREAD_COUNT));
int unreadMentionCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.UNREAD_MENTION_COUNT));

@ -357,7 +357,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
executeStatements(db, SmsDatabase.CREATE_INDEXS);
executeStatements(db, MmsDatabase.CREATE_INDEXS);
executeStatements(db, AttachmentDatabase.CREATE_INDEXS);
executeStatements(db, ThreadDatabase.CREATE_INDEXS);
executeStatements(db, ThreadDatabase.CREATE_INDEXES);
executeStatements(db, DraftDatabase.CREATE_INDEXS);
executeStatements(db, GroupDatabase.CREATE_INDEXS);
executeStatements(db, GroupReceiptDatabase.CREATE_INDEXES);

@ -31,6 +31,7 @@ import org.session.libsession.messaging.utilities.UpdateMessageData;
import org.session.libsession.utilities.IdentityKeyMismatch;
import org.session.libsession.utilities.NetworkFailure;
import org.session.libsession.utilities.recipients.Recipient;
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
import java.util.List;
import java.util.Objects;
@ -120,7 +121,8 @@ public abstract class MessageRecord extends DisplayRecord {
return new SpannableString(UpdateMessageBuilder.INSTANCE.buildGroupUpdateMessage(context, updateMessageData, getIndividualRecipient().getAddress().serialize(), isOutgoing()));
} else if (isExpirationTimerUpdate()) {
int seconds = (int) (getExpiresIn() / 1000);
return new SpannableString(UpdateMessageBuilder.INSTANCE.buildExpirationTimerMessage(context, seconds, getRecipient(), getIndividualRecipient().getAddress().serialize(), isOutgoing(), getTimestamp(), expireStarted));
boolean isGroup = DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(getThreadId()).isGroupRecipient();
return new SpannableString(UpdateMessageBuilder.INSTANCE.buildExpirationTimerMessage(context, seconds, isGroup, getIndividualRecipient().getAddress().serialize(), isOutgoing(), getTimestamp(), expireStarted));
} else if (isDataExtractionNotification()) {
if (isScreenshotNotification()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT, getIndividualRecipient().getAddress().serialize())));
else if (isMediaSavedNotification()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED, getIndividualRecipient().getAddress().serialize())));

@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.home.search
import android.content.Context
import android.text.Editable
import android.text.InputFilter
import android.text.InputFilter.LengthFilter
import android.text.TextWatcher
import android.util.AttributeSet
import android.view.KeyEvent
@ -34,6 +36,7 @@ class GlobalSearchInputLayout @JvmOverloads constructor(
binding.searchInput.onFocusChangeListener = this
binding.searchInput.addTextChangedListener(this)
binding.searchInput.setOnEditorActionListener(this)
binding.searchInput.setFilters( arrayOf<InputFilter>(LengthFilter(100)) ) // 100 char search limit
binding.searchCancel.setOnClickListener(this)
binding.searchClear.setOnClickListener(this)
}

@ -24,8 +24,7 @@ class GlobalSearchViewModel @Inject constructor(private val searchRepository: Se
private val executor = viewModelScope + SupervisorJob()
private val _result: MutableStateFlow<GlobalSearchResult> =
MutableStateFlow(GlobalSearchResult.EMPTY)
private val _result: MutableStateFlow<GlobalSearchResult> = MutableStateFlow(GlobalSearchResult.EMPTY)
val result: StateFlow<GlobalSearchResult> = _result
@ -41,13 +40,14 @@ class GlobalSearchViewModel @Inject constructor(private val searchRepository: Se
_queryText
.buffer(onBufferOverflow = BufferOverflow.DROP_OLDEST)
.mapLatest { query ->
if (query.trim().length < 2) {
// Early exit on empty search query
if (query.trim().isEmpty()) {
SearchResult.EMPTY
} else {
// user input delay here in case we get a new query within a few hundred ms
// this coroutine will be cancelled and expensive query will not be run if typing quickly
// first query of 2 characters will be instant however
// User input delay in case we get a new query within a few hundred ms this
// coroutine will be cancelled and the expensive query will not be run.
delay(300)
val settableFuture = SettableFuture<SearchResult>()
searchRepository.query(query.toString(), settableFuture::set)
try {
@ -64,6 +64,4 @@ class GlobalSearchViewModel @Inject constructor(private val searchRepository: Se
}
.launchIn(executor)
}
}

@ -61,11 +61,15 @@ class MarkReadReceiver : BroadcastReceiver() {
val mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase()
val threadDb = DatabaseComponent.get(context).threadDatabase()
// start disappear after read messages except TimerUpdates in groups.
markedReadMessages
.filter { it.expiryType == ExpiryType.AFTER_READ }
.map { it.syncMessageId }
.filter { mmsSmsDatabase.getMessageForTimestamp(it.timetamp)?.run { isExpirationTimerUpdate && recipient.isClosedGroupRecipient } == false }
.filter { mmsSmsDatabase.getMessageForTimestamp(it.timetamp)?.run {
isExpirationTimerUpdate && threadDb.getRecipientForThreadId(threadId)?.isGroupRecipient == true } == false
}
.forEach { messageExpirationManager.startDisappearAfterRead(it.timetamp, it.address.serialize()) }
hashToDisappearAfterReadMessage(context, markedReadMessages)?.let {

@ -4,19 +4,14 @@ import network.loki.messenger.libsession_util.util.ExpiryMode
import android.content.ContentResolver
import android.content.Context
import app.cash.copper.Query
import app.cash.copper.flow.observeQuery
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import org.session.libsession.database.MessageDataProvider
import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.control.MessageRequestResponse
@ -32,9 +27,7 @@ import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.database.DatabaseContentProviders
import org.thoughtcrime.securesms.database.DraftDatabase
import org.thoughtcrime.securesms.database.ExpirationConfigurationDatabase
@ -51,7 +44,6 @@ import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.dependencies.ConfigFactory
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import javax.inject.Inject
interface ConversationRepository {
@ -239,7 +231,7 @@ class DefaultConversationRepository @Inject constructor(
.success {
continuation.resume(ResultOf.Success(Unit))
}.fail { error ->
Log.w("[onversationRepository", "Call to SnodeAPI.deleteMessage failed - attempting to resume..")
Log.w("ConversationRepository", "Call to SnodeAPI.deleteMessage failed - attempting to resume..")
continuation.resumeWithException(error)
}
}
@ -330,9 +322,7 @@ class DefaultConversationRepository @Inject constructor(
while (reader.next != null) {
deleteMessageRequest(reader.current)
val recipient = reader.current.recipient
if (block) {
setBlocked(recipient, true)
}
if (block) { setBlocked(recipient, true) }
}
}
return ResultOf.Success(Unit)
@ -359,9 +349,7 @@ class DefaultConversationRepository @Inject constructor(
val cursor = mmsSmsDb.getConversation(threadId, true)
mmsSmsDb.readerFor(cursor).use { reader ->
while (reader.next != null) {
if (!reader.current.isOutgoing) {
return true
}
if (!reader.current.isOutgoing) { return true }
}
}
return false

@ -4,12 +4,8 @@ import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.MergeCursor;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import com.annimon.stream.Stream;
import org.session.libsession.messaging.contacts.Contact;
import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.GroupRecord;
@ -27,37 +23,25 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.search.model.MessageResult;
import org.thoughtcrime.securesms.search.model.SearchResult;
import org.thoughtcrime.securesms.util.Stopwatch;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import kotlin.Pair;
/**
* Manages data retrieval for search.
*/
// Class to manage data retrieval for search
public class SearchRepository {
private static final String TAG = SearchRepository.class.getSimpleName();
private static final Set<Character> BANNED_CHARACTERS = new HashSet<>();
static {
// Several ranges of invalid ASCII characters
for (int i = 33; i <= 47; i++) {
BANNED_CHARACTERS.add((char) i);
}
for (int i = 58; i <= 64; i++) {
BANNED_CHARACTERS.add((char) i);
}
for (int i = 91; i <= 96; i++) {
BANNED_CHARACTERS.add((char) i);
}
for (int i = 123; i <= 126; i++) {
BANNED_CHARACTERS.add((char) i);
}
// Construct a list containing several ranges of invalid ASCII characters
// See: https://www.ascii-code.com/
for (int i = 33; i <= 47; i++) { BANNED_CHARACTERS.add((char) i); } // !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /
for (int i = 58; i <= 64; i++) { BANNED_CHARACTERS.add((char) i); } // :, ;, <, =, >, ?, @
for (int i = 91; i <= 96; i++) { BANNED_CHARACTERS.add((char) i); } // [, \, ], ^, _, `
for (int i = 123; i <= 126; i++) { BANNED_CHARACTERS.add((char) i); } // {, |, }, ~
}
private final Context context;
@ -86,35 +70,25 @@ public class SearchRepository {
}
public void query(@NonNull String query, @NonNull Callback<SearchResult> callback) {
if (TextUtils.isEmpty(query)) {
// If the sanitized search is empty then abort without search
String cleanQuery = sanitizeQuery(query).trim();
if (cleanQuery.isEmpty()) {
callback.onResult(SearchResult.EMPTY);
return;
}
executor.execute(() -> {
Stopwatch timer = new Stopwatch("FtsQuery");
String cleanQuery = sanitizeQuery(query);
// If the search is for a single character and it was stripped by `sanitizeQuery` then abort
// the search for an empty string to avoid SQLite error.
if (cleanQuery.length() == 0)
{
Log.d(TAG, "Aborting empty search query.");
timer.stop(TAG);
return;
}
timer.split("clean");
Pair<CursorList<Contact>, List<String>> contacts = queryContacts(cleanQuery);
timer.split("contacts");
timer.split("Contacts");
CursorList<GroupRecord> conversations = queryConversations(cleanQuery, contacts.getSecond());
timer.split("conversations");
timer.split("Conversations");
CursorList<MessageResult> messages = queryMessages(cleanQuery);
timer.split("messages");
timer.split("Messages");
timer.stop(TAG);
@ -123,23 +97,20 @@ public class SearchRepository {
}
public void query(@NonNull String query, long threadId, @NonNull Callback<CursorList<MessageResult>> callback) {
if (TextUtils.isEmpty(query)) {
// If the sanitized search query is empty then abort the search
String cleanQuery = sanitizeQuery(query).trim();
if (cleanQuery.isEmpty()) {
callback.onResult(CursorList.emptyList());
return;
}
executor.execute(() -> {
// If the sanitized search query is empty then abort the search to prevent SQLite errors.
String cleanQuery = sanitizeQuery(query).trim();
if (cleanQuery.isEmpty()) { return; }
CursorList<MessageResult> messages = queryMessages(cleanQuery, threadId);
callback.onResult(messages);
});
}
private Pair<CursorList<Contact>, List<String>> queryContacts(String query) {
Cursor contacts = contactDatabase.queryContactsByName(query);
List<Address> contactList = new ArrayList<>();
List<String> contactStrings = new ArrayList<>();
@ -166,11 +137,10 @@ public class SearchRepository {
MergeCursor merged = new MergeCursor(new Cursor[]{addressThreads, individualRecipients});
return new Pair<>(new CursorList<>(merged, new ContactModelBuilder(contactDatabase, threadDatabase)), contactStrings);
}
private CursorList<GroupRecord> queryConversations(@NonNull String query, List<String> matchingAddresses) {
List<String> numbers = contactAccessor.getNumbersForThreadSearchFilter(context, query);
List<String> numbers = contactAccessor.getNumbersForThreadSearchFilter(context, query);
String localUserNumber = TextSecurePreferences.getLocalNumber(context);
if (localUserNumber != null) {
matchingAddresses.remove(localUserNumber);
@ -189,9 +159,7 @@ public class SearchRepository {
membersGroupList.close();
}
Cursor conversations = threadDatabase.getFilteredConversationList(new ArrayList<>(addresses));
return conversations != null ? new CursorList<>(conversations, new GroupModelBuilder(threadDatabase, groupDatabase))
: CursorList.emptyList();
}
@ -256,9 +224,7 @@ public class SearchRepository {
private final Context context;
RecipientModelBuilder(@NonNull Context context) {
this.context = context;
}
RecipientModelBuilder(@NonNull Context context) { this.context = context; }
@Override
public Recipient build(@NonNull Cursor cursor) {
@ -301,9 +267,7 @@ public class SearchRepository {
private final Context context;
MessageModelBuilder(@NonNull Context context) {
this.context = context;
}
MessageModelBuilder(@NonNull Context context) { this.context = context; }
@Override
public MessageResult build(@NonNull Cursor cursor) {

@ -151,8 +151,8 @@ class ExpiringMessageManager(context: Context) : MessageExpirationManagerProtoco
val userPublicKey = getLocalNumber(context)
val senderPublicKey = message.sender
val sentTimestamp = if (message.sentTimestamp == null) 0 else message.sentTimestamp!!
val expireStartedAt = if (expiryMode is AfterSend || message.isSenderSelf) sentTimestamp else 0
val sentTimestamp = message.sentTimestamp ?: 0
val expireStartedAt = if ((expiryMode is AfterSend || message.isSenderSelf) && !message.isGroup) sentTimestamp else 0
// Notify the user
if (senderPublicKey == null || userPublicKey == senderPublicKey) {

@ -37,12 +37,10 @@ public class Stopwatch {
for (int i = 1; i < splits.size(); i++) {
out.append(splits.get(i).label).append(": ");
out.append(splits.get(i).time - splits.get(i - 1).time);
out.append(" ");
out.append("ms ");
}
out.append("total: ").append(splits.get(splits.size() - 1).time - startTime);
out.append("total: ").append(splits.get(splits.size() - 1).time - startTime).append("ms.");
}
Log.d(tag, out.toString());
}

@ -4,7 +4,6 @@
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<solid android:color="?android:textColorPrimary"/>
<corners android:radius="@dimen/medium_button_corner_radius" />
</shape>
</item>
</ripple>

@ -4,7 +4,6 @@
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<solid android:color="?android:textColorPrimary"/>
<corners android:radius="@dimen/medium_button_corner_radius" />
</shape>
</item>
</ripple>

@ -6,8 +6,7 @@
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:elevation="4dp"
android:padding="@dimen/medium_spacing">
android:elevation="4dp">
<TextView
android:layout_width="wrap_content"
@ -21,6 +20,8 @@
android:id="@+id/dialogDescriptionText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginTop="@dimen/large_spacing"
android:text="@string/dialog_clear_all_data_message"
android:textAlignment="center"
@ -46,16 +47,15 @@
style="@style/Widget.Session.Button.Dialog.DestructiveText"
android:id="@+id/clearAllDataButton"
android:layout_width="0dp"
android:layout_height="@dimen/small_button_height"
android:layout_height="@dimen/dialog_button_height"
android:layout_weight="1"
android:layout_marginStart="@dimen/medium_spacing"
android:text="@string/dialog_clear_all_data_clear" />
<Button
style="@style/Widget.Session.Button.Dialog.UnimportantText"
android:id="@+id/cancelButton"
android:layout_width="0dp"
android:layout_height="@dimen/small_button_height"
android:layout_height="@dimen/dialog_button_height"
android:layout_weight="1"
android:text="@string/cancel" />

@ -38,7 +38,7 @@
style="@style/Widget.Session.Button.Dialog.UnimportantText"
android:id="@+id/cancelButton"
android:layout_width="0dp"
android:layout_height="@dimen/small_button_height"
android:layout_height="@dimen/dialog_button_height"
android:layout_weight="1"
android:text="@string/cancel" />
@ -46,7 +46,7 @@
style="@style/Widget.Session.Button.Dialog.DestructiveText"
android:id="@+id/sendSeedButton"
android:layout_width="0dp"
android:layout_height="@dimen/small_button_height"
android:layout_height="@dimen/dialog_button_height"
android:layout_weight="1"
android:layout_marginStart="@dimen/medium_spacing"
android:text="@string/dialog_send_seed_send_button_title" />

@ -48,7 +48,7 @@
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/very_small_font_size"
android:textStyle="bold"
tools:text="@string/MessageRecord_you_disabled_disappearing_messages" />
tools:text="You disabled disappearing messages" />
<FrameLayout
android:id="@+id/call_view"

@ -11,6 +11,7 @@
<dimen name="massive_font_size">50sp</dimen>
<!-- Element Sizes -->
<dimen name="dialog_button_height">60dp</dimen>
<dimen name="small_button_height">34dp</dimen>
<dimen name="medium_button_height">38dp</dimen>
<dimen name="large_button_height">54dp</dimen>

@ -119,6 +119,7 @@
<item name="android:textAllCaps">false</item>
<item name="android:textSize">@dimen/small_font_size</item>
<item name="android:textColor">?android:textColorPrimary</item>
<item name="android:textStyle">bold</item>
</style>
<style name="Widget.Session.Button.Dialog.UnimportantText">

@ -10,11 +10,11 @@ import org.session.libsession.messaging.calls.CallMessageType.CALL_INCOMING
import org.session.libsession.messaging.calls.CallMessageType.CALL_MISSED
import org.session.libsession.messaging.calls.CallMessageType.CALL_OUTGOING
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.messages.ExpirationConfiguration.Companion.isNewConfigEnabled
import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage
import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED
import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage.Kind.SCREENSHOT
import org.session.libsession.utilities.ExpirationUtil
import org.session.libsession.utilities.getExpirationTypeDisplayValue
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsession.utilities.truncateIdForDisplay
object UpdateMessageBuilder {
@ -31,47 +31,35 @@ object UpdateMessageBuilder {
else getSenderName(senderId!!)
return when (updateData) {
is UpdateMessageData.Kind.GroupCreation -> if (isOutgoing) {
context.getString(R.string.MessageRecord_you_created_a_new_group)
} else {
context.getString(R.string.MessageRecord_s_added_you_to_the_group, senderName)
is UpdateMessageData.Kind.GroupCreation -> {
if (isOutgoing) context.getString(R.string.MessageRecord_you_created_a_new_group)
else context.getString(R.string.MessageRecord_s_added_you_to_the_group, senderName)
}
is UpdateMessageData.Kind.GroupNameChange -> if (isOutgoing) {
context.getString(R.string.MessageRecord_you_renamed_the_group_to_s, updateData.name)
} else {
context.getString(R.string.MessageRecord_s_renamed_the_group_to_s, senderName, updateData.name)
is UpdateMessageData.Kind.GroupNameChange -> {
if (isOutgoing) context.getString(R.string.MessageRecord_you_renamed_the_group_to_s, updateData.name)
else context.getString(R.string.MessageRecord_s_renamed_the_group_to_s, senderName, updateData.name)
}
is UpdateMessageData.Kind.GroupMemberAdded -> {
val members = updateData.updatedMembers.joinToString(", ", transform = ::getSenderName)
if (isOutgoing) {
context.getString(R.string.MessageRecord_you_added_s_to_the_group, members)
} else {
context.getString(R.string.MessageRecord_s_added_s_to_the_group, senderName, members)
}
if (isOutgoing) context.getString(R.string.MessageRecord_you_added_s_to_the_group, members)
else context.getString(R.string.MessageRecord_s_added_s_to_the_group, senderName, members)
}
is UpdateMessageData.Kind.GroupMemberRemoved -> {
val userPublicKey = storage.getUserPublicKey()!!
// 1st case: you are part of the removed members
return if (userPublicKey in updateData.updatedMembers) {
if (isOutgoing) {
context.getString(R.string.MessageRecord_left_group)
} else {
context.getString(R.string.MessageRecord_you_were_removed_from_the_group)
}
if (isOutgoing) context.getString(R.string.MessageRecord_left_group)
else context.getString(R.string.MessageRecord_you_were_removed_from_the_group)
} else {
// 2nd case: you are not part of the removed members
val members = updateData.updatedMembers.joinToString(", ", transform = ::getSenderName)
if (isOutgoing) {
context.getString(R.string.MessageRecord_you_removed_s_from_the_group, members)
} else {
context.getString(R.string.MessageRecord_s_removed_s_from_the_group, senderName, members)
}
if (isOutgoing) context.getString(R.string.MessageRecord_you_removed_s_from_the_group, members)
else context.getString(R.string.MessageRecord_s_removed_s_from_the_group, senderName, members)
}
}
is UpdateMessageData.Kind.GroupMemberLeft -> if (isOutgoing) {
context.getString(R.string.MessageRecord_left_group)
} else {
context.getString(R.string.ConversationItem_group_action_left, senderName)
is UpdateMessageData.Kind.GroupMemberLeft -> {
if (isOutgoing) context.getString(R.string.MessageRecord_left_group)
else context.getString(R.string.ConversationItem_group_action_left, senderName)
}
else -> return ""
}
@ -80,7 +68,7 @@ object UpdateMessageBuilder {
fun buildExpirationTimerMessage(
context: Context,
duration: Long,
recipient: Recipient,
isGroup: Boolean,
senderId: String? = null,
isOutgoing: Boolean = false,
timestamp: Long,
@ -89,44 +77,28 @@ object UpdateMessageBuilder {
if (!isOutgoing && senderId == null) return ""
val senderName = if (isOutgoing) context.getString(R.string.MessageRecord_you) else getSenderName(senderId!!)
return if (duration <= 0) {
if (isOutgoing) {
if (!isNewConfigEnabled) context.getString(R.string.MessageRecord_you_disabled_disappearing_messages)
else context.getString(if (recipient.is1on1) R.string.MessageRecord_you_turned_off_disappearing_messages_1_on_1 else R.string.MessageRecord_you_turned_off_disappearing_messages)
} else {
if (!isNewConfigEnabled) context.getString(R.string.MessageRecord_s_disabled_disappearing_messages, senderName)
else context.getString(if (recipient.is1on1) R.string.MessageRecord_s_turned_off_disappearing_messages_1_on_1 else R.string.MessageRecord_s_turned_off_disappearing_messages, senderName)
}
if (isOutgoing) context.getString(if (isGroup) R.string.MessageRecord_you_turned_off_disappearing_messages else R.string.MessageRecord_you_turned_off_disappearing_messages_1_on_1)
else context.getString(if (isGroup) R.string.MessageRecord_s_turned_off_disappearing_messages else R.string.MessageRecord_s_turned_off_disappearing_messages_1_on_1, senderName)
} else {
val time = ExpirationUtil.getExpirationDisplayValue(context, duration.toInt())
val action = context.getExpirationTypeDisplayValue(timestamp == expireStarted)
if (isOutgoing) {
if (!isNewConfigEnabled) context.getString(R.string.MessageRecord_you_set_disappearing_message_time_to_s, time)
else context.getString(
if (recipient.is1on1) R.string.MessageRecord_you_set_messages_to_disappear_s_after_s_1_on_1 else R.string.MessageRecord_you_set_messages_to_disappear_s_after_s,
time,
action
)
} else {
if (!isNewConfigEnabled) context.getString(R.string.MessageRecord_s_set_disappearing_message_time_to_s, senderName, time)
else context.getString(
if (recipient.is1on1) R.string.MessageRecord_s_set_messages_to_disappear_s_after_s_1_on_1 else R.string.MessageRecord_s_set_messages_to_disappear_s_after_s,
senderName,
time,
action
)
}
val action = context.getExpirationTypeDisplayValue(timestamp >= expireStarted)
if (isOutgoing) context.getString(
if (isGroup) R.string.MessageRecord_you_set_messages_to_disappear_s_after_s else R.string.MessageRecord_you_set_messages_to_disappear_s_after_s_1_on_1,
time,
action
) else context.getString(
if (isGroup) R.string.MessageRecord_s_set_messages_to_disappear_s_after_s else R.string.MessageRecord_s_set_messages_to_disappear_s_after_s_1_on_1,
senderName,
time,
action
)
}
}
fun buildDataExtractionMessage(context: Context, kind: DataExtractionNotificationInfoMessage.Kind, senderId: String? = null): String {
val senderName = getSenderName(senderId!!)
return when (kind) {
DataExtractionNotificationInfoMessage.Kind.SCREENSHOT ->
context.getString(R.string.MessageRecord_s_took_a_screenshot, senderName)
DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED ->
context.getString(R.string.MessageRecord_media_saved_by_s, senderName)
}
}
fun buildDataExtractionMessage(context: Context, kind: DataExtractionNotificationInfoMessage.Kind, senderId: String? = null) = when (kind) {
SCREENSHOT -> R.string.MessageRecord_s_took_a_screenshot
MEDIA_SAVED -> R.string.MessageRecord_media_saved_by_s
}.let { context.getString(it, getSenderName(senderId!!)) }
fun buildCallMessage(context: Context, type: CallMessageType, sender: String): String =
when (type) {

@ -15,10 +15,6 @@
<string name="MessageRecord_s_called_you">%s vous a appelé·e</string>
<string name="MessageRecord_called_s">Vous avez appelé %s</string>
<string name="MessageRecord_missed_call_from">Appel manqué de %s</string>
<string name="MessageRecord_you_disabled_disappearing_messages">Vous avez désactivé les messages éphémères.</string>
<string name="MessageRecord_s_disabled_disappearing_messages">%1$s a désactivé les messages éphémères.</string>
<string name="MessageRecord_you_set_disappearing_message_time_to_s">Vous avez défini lexpiration des messages éphémères à %1$s</string>
<string name="MessageRecord_s_set_disappearing_message_time_to_s">%1$s a défini lexpiration des messages éphémères à %2$s</string>
<string name="MessageRecord_s_took_a_screenshot">%1$s a pris une capture d\'écran.</string>
<string name="MessageRecord_media_saved_by_s">%1$s a enregistré le média.</string>
<!-- expiration -->

@ -15,10 +15,6 @@
<string name="MessageRecord_s_called_you">%s vous a appelé·e</string>
<string name="MessageRecord_called_s">Vous avez appelé %s</string>
<string name="MessageRecord_missed_call_from">Appel manqué de %s</string>
<string name="MessageRecord_you_disabled_disappearing_messages">Vous avez désactivé les messages éphémères.</string>
<string name="MessageRecord_s_disabled_disappearing_messages">%1$s a désactivé les messages éphémères.</string>
<string name="MessageRecord_you_set_disappearing_message_time_to_s">Vous avez défini lexpiration des messages éphémères à %1$s</string>
<string name="MessageRecord_s_set_disappearing_message_time_to_s">%1$s a défini lexpiration des messages éphémères à %2$s</string>
<string name="MessageRecord_s_took_a_screenshot">%1$s a pris une capture d\'écran.</string>
<string name="MessageRecord_media_saved_by_s">%1$s a enregistré le média.</string>
<!-- expiration -->

@ -17,17 +17,13 @@
<string name="MessageRecord_missed_call_from">Missed call from %s</string>
<string name="MessageRecord_follow_setting">Follow Setting</string>
<string name="AccessibilityId_follow_setting">Follow setting</string>
<string name="MessageRecord_you_disabled_disappearing_messages">You disabled disappearing messages.</string>
<string name="MessageRecord_you_turned_off_disappearing_messages">You have turned off disappearing messages.</string>
<string name="MessageRecord_you_turned_off_disappearing_messages_1_on_1">You turned off disappearing messages. Messages you send will no longer disappear.</string>
<string name="MessageRecord_s_disabled_disappearing_messages">%1$s disabled disappearing messages.</string>
<string name="MessageRecord_s_turned_off_disappearing_messages">%1$s turned off disappearing messages.</string>
<string name="MessageRecord_s_turned_off_disappearing_messages_1_on_1">%1$s has turned off disappearing messages. Messages they send will no longer disappear.</string>
<string name="MessageRecord_you_set_disappearing_message_time_to_s">You set the disappearing message timer to %1$s</string>
<string name="MessageRecord_you_set_messages_to_disappear_s_after_s">You have set messages to disappear %1$s after they have been %2$s</string>
<string name="MessageRecord_you_set_messages_to_disappear_s_after_s_1_on_1">You set your messages to disappear %1$s after they have been %2$s.</string>
<string name="MessageRecord_you_changed_messages_to_disappear_s_after_s">You have changed messages to disappear %1$s after they have been %2$s</string>
<string name="MessageRecord_s_set_disappearing_message_time_to_s">%1$s set the disappearing message timer to %2$s</string>
<string name="MessageRecord_s_set_messages_to_disappear_s_after_s">%1$s has set messages to disappear %2$s after they have been %3$s</string>
<string name="MessageRecord_s_set_messages_to_disappear_s_after_s_1_on_1">%1$s has set their messages to disappear %2$s after they have been %3$s.</string>
<string name="MessageRecord_s_changed_messages_to_disappear_s_after_s">%1$s has changed messages to disappear %2$s after they have been %3$s</string>

Loading…
Cancel
Save