mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-25 04:06:14 +00:00
Implement support for 'allows replies' toggle.
This commit is contained in:
@@ -5,8 +5,10 @@ import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListRecord
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.api.push.ACI
|
||||
import java.lang.IllegalStateException
|
||||
import java.util.UUID
|
||||
|
||||
class DistributionListDatabaseTest {
|
||||
@@ -60,9 +62,53 @@ class DistributionListDatabaseTest {
|
||||
Assert.assertEquals(members, foundMembers)
|
||||
}
|
||||
|
||||
fun createRecipients(count: Int) {
|
||||
@Test
|
||||
fun givenStoryExists_getStoryType_returnsStoryWithReplies() {
|
||||
val id: DistributionListId? = distributionDatabase.createList("test", recipientList(1, 2, 3))
|
||||
Assert.assertNotNull(id)
|
||||
|
||||
val storyType = distributionDatabase.getStoryType(id!!)
|
||||
Assert.assertEquals(StoryType.STORY_WITH_REPLIES, storyType)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenStoryExistsAndMarkedNoReplies_getStoryType_returnsStoryWithoutReplies() {
|
||||
val id: DistributionListId? = distributionDatabase.createList("test", recipientList(1, 2, 3))
|
||||
Assert.assertNotNull(id)
|
||||
distributionDatabase.setAllowsReplies(id!!, false)
|
||||
|
||||
val storyType = distributionDatabase.getStoryType(id)
|
||||
Assert.assertEquals(StoryType.STORY_WITHOUT_REPLIES, storyType)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenStoryExistsAndMarkedNoReplies_getAllListsForContactSelectionUi_returnsStoryWithoutReplies() {
|
||||
val id: DistributionListId? = distributionDatabase.createList("test", recipientList(1, 2, 3))
|
||||
Assert.assertNotNull(id)
|
||||
distributionDatabase.setAllowsReplies(id!!, false)
|
||||
|
||||
val records = distributionDatabase.getAllListsForContactSelectionUi(null, false)
|
||||
Assert.assertFalse(records.first().allowsReplies)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenStoryExists_getAllListsForContactSelectionUi_returnsStoryWithReplies() {
|
||||
val id: DistributionListId? = distributionDatabase.createList("test", recipientList(1, 2, 3))
|
||||
Assert.assertNotNull(id)
|
||||
|
||||
val records = distributionDatabase.getAllListsForContactSelectionUi(null, false)
|
||||
Assert.assertTrue(records.first().allowsReplies)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun givenStoryDoesNotExist_getStoryType_throwsIllegalStateException() {
|
||||
distributionDatabase.getStoryType(DistributionListId.from(12))
|
||||
Assert.fail("Expected an assertion error.")
|
||||
}
|
||||
|
||||
private fun createRecipients(count: Int) {
|
||||
for (i in 0 until count) {
|
||||
SignalDatabase.recipients.getOrInsertFromAci(ACI.from(UUID.randomUUID()))
|
||||
SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,7 @@ import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.ShortcutLauncherActivity;
|
||||
import org.thoughtcrime.securesms.TransportOption;
|
||||
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel;
|
||||
import org.thoughtcrime.securesms.database.model.StoryType;
|
||||
import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.TombstoneAttachment;
|
||||
@@ -291,7 +292,6 @@ import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperDimLevelUtil;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
@@ -302,7 +302,6 @@ import org.whispersystems.signalservice.api.SignalSessionLock;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -2961,7 +2960,7 @@ public class ConversationParentFragment extends Fragment
|
||||
long expiresIn = TimeUnit.SECONDS.toMillis(recipient.get().getExpiresInSeconds());
|
||||
QuoteModel quote = result.isViewOnce() ? null : inputPanel.getQuote().orNull();
|
||||
List<Mention> mentions = new ArrayList<>(result.getMentions());
|
||||
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient.get(), new SlideDeck(), result.getBody(), System.currentTimeMillis(), -1, expiresIn, result.isViewOnce(), distributionType, result.isStory(), null, quote, Collections.emptyList(), Collections.emptyList(), mentions);
|
||||
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient.get(), new SlideDeck(), result.getBody(), System.currentTimeMillis(), -1, expiresIn, result.isViewOnce(), distributionType, result.getStoryType(), null, quote, Collections.emptyList(), Collections.emptyList(), mentions);
|
||||
OutgoingMediaMessage secureMessage = new OutgoingSecureMediaMessage(message);
|
||||
|
||||
final Context context = requireContext().getApplicationContext();
|
||||
@@ -3037,7 +3036,7 @@ public class ConversationParentFragment extends Fragment
|
||||
}
|
||||
}
|
||||
|
||||
OutgoingMediaMessage outgoingMessageCandidate = new OutgoingMediaMessage(Recipient.resolved(recipientId), slideDeck, body, System.currentTimeMillis(), subscriptionId, expiresIn, viewOnce, distributionType, false, null, quote, contacts, previews, mentions);
|
||||
OutgoingMediaMessage outgoingMessageCandidate = new OutgoingMediaMessage(Recipient.resolved(recipientId), slideDeck, body, System.currentTimeMillis(), subscriptionId, expiresIn, viewOnce, distributionType, StoryType.NONE, null, quote, contacts, previews, mentions);
|
||||
|
||||
final SettableFuture<Void> future = new SettableFuture<>();
|
||||
final Context context = requireContext().getApplicationContext();
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.core.content.contentValuesOf
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListPartialRecord
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListRecord
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.thoughtcrime.securesms.util.Base64
|
||||
@@ -55,13 +56,15 @@ class DistributionListDatabase constructor(context: Context?, databaseHelper: Si
|
||||
const val NAME = "name"
|
||||
const val DISTRIBUTION_ID = "distribution_id"
|
||||
const val RECIPIENT_ID = "recipient_id"
|
||||
const val ALLOWS_REPLIES = "allows_replies"
|
||||
|
||||
const val CREATE_TABLE = """
|
||||
CREATE TABLE $TABLE_NAME (
|
||||
$ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
$NAME TEXT UNIQUE NOT NULL,
|
||||
$DISTRIBUTION_ID TEXT UNIQUE NOT NULL,
|
||||
$RECIPIENT_ID INTEGER UNIQUE REFERENCES ${RecipientDatabase.TABLE_NAME} (${RecipientDatabase.ID})
|
||||
$RECIPIENT_ID INTEGER UNIQUE REFERENCES ${RecipientDatabase.TABLE_NAME} (${RecipientDatabase.ID}),
|
||||
$ALLOWS_REPLIES INTEGER DEFAULT 1
|
||||
)
|
||||
"""
|
||||
}
|
||||
@@ -106,6 +109,7 @@ class DistributionListDatabase constructor(context: Context?, databaseHelper: Si
|
||||
DistributionListPartialRecord(
|
||||
id = DistributionListId.from(CursorUtil.requireLong(it, ListTable.ID)),
|
||||
name = CursorUtil.requireString(it, ListTable.NAME),
|
||||
allowsReplies = CursorUtil.requireBoolean(it, ListTable.ALLOWS_REPLIES),
|
||||
recipientId = RecipientId.from(CursorUtil.requireLong(it, ListTable.RECIPIENT_ID))
|
||||
)
|
||||
)
|
||||
@@ -117,7 +121,7 @@ class DistributionListDatabase constructor(context: Context?, databaseHelper: Si
|
||||
|
||||
fun getAllListsForContactSelectionUiCursor(query: String?, includeMyStory: Boolean): Cursor? {
|
||||
val db = readableDatabase
|
||||
val projection = arrayOf(ListTable.ID, ListTable.NAME, ListTable.RECIPIENT_ID)
|
||||
val projection = arrayOf(ListTable.ID, ListTable.NAME, ListTable.RECIPIENT_ID, ListTable.ALLOWS_REPLIES)
|
||||
|
||||
val where = when {
|
||||
query.isNullOrEmpty() && includeMyStory -> null
|
||||
@@ -137,7 +141,7 @@ class DistributionListDatabase constructor(context: Context?, databaseHelper: Si
|
||||
|
||||
fun getCustomListsForUi(): List<DistributionListPartialRecord> {
|
||||
val db = readableDatabase
|
||||
val projection = SqlUtil.buildArgs(ListTable.ID, ListTable.NAME, ListTable.RECIPIENT_ID)
|
||||
val projection = SqlUtil.buildArgs(ListTable.ID, ListTable.NAME, ListTable.RECIPIENT_ID, ListTable.ALLOWS_REPLIES)
|
||||
val selection = "${ListTable.ID} != ${DistributionListId.MY_STORY_ID}"
|
||||
|
||||
return db.query(ListTable.TABLE_NAME, projection, selection, null, null, null, null)?.use {
|
||||
@@ -147,6 +151,7 @@ class DistributionListDatabase constructor(context: Context?, databaseHelper: Si
|
||||
DistributionListPartialRecord(
|
||||
id = DistributionListId.from(CursorUtil.requireLong(it, ListTable.ID)),
|
||||
name = CursorUtil.requireString(it, ListTable.NAME),
|
||||
allowsReplies = CursorUtil.requireBoolean(it, ListTable.ALLOWS_REPLIES),
|
||||
recipientId = RecipientId.from(CursorUtil.requireLong(it, ListTable.RECIPIENT_ID))
|
||||
)
|
||||
)
|
||||
@@ -194,6 +199,24 @@ class DistributionListDatabase constructor(context: Context?, databaseHelper: Si
|
||||
}
|
||||
}
|
||||
|
||||
fun getStoryType(listId: DistributionListId): StoryType {
|
||||
readableDatabase.query(ListTable.TABLE_NAME, arrayOf(ListTable.ALLOWS_REPLIES), "${ListTable.ID} = ?", SqlUtil.buildArgs(listId), null, null, null).use { cursor ->
|
||||
return if (cursor.moveToFirst()) {
|
||||
if (CursorUtil.requireBoolean(cursor, ListTable.ALLOWS_REPLIES)) {
|
||||
StoryType.STORY_WITH_REPLIES
|
||||
} else {
|
||||
StoryType.STORY_WITHOUT_REPLIES
|
||||
}
|
||||
} else {
|
||||
error("Distribution list not in database.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setAllowsReplies(listId: DistributionListId, allowsReplies: Boolean) {
|
||||
writableDatabase.update(ListTable.TABLE_NAME, contentValuesOf(ListTable.ALLOWS_REPLIES to allowsReplies), "${ListTable.ID} = ?", SqlUtil.buildArgs(listId))
|
||||
}
|
||||
|
||||
fun getList(listId: DistributionListId): DistributionListRecord? {
|
||||
readableDatabase.query(ListTable.TABLE_NAME, null, "${ListTable.ID} = ?", SqlUtil.buildArgs(listId), null, null, null).use { cursor ->
|
||||
return if (cursor.moveToFirst()) {
|
||||
@@ -203,6 +226,7 @@ class DistributionListDatabase constructor(context: Context?, databaseHelper: Si
|
||||
id = id,
|
||||
name = cursor.requireNonNullString(ListTable.NAME),
|
||||
distributionId = DistributionId.from(cursor.requireNonNullString(ListTable.DISTRIBUTION_ID)),
|
||||
allowsReplies = CursorUtil.requireBoolean(cursor, ListTable.ALLOWS_REPLIES),
|
||||
members = getMembers(id)
|
||||
)
|
||||
} else {
|
||||
|
||||
@@ -51,10 +51,11 @@ import org.thoughtcrime.securesms.database.model.Mention;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.ParentStoryId;
|
||||
import org.thoughtcrime.securesms.database.model.Quote;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.StoryType;
|
||||
import org.thoughtcrime.securesms.database.model.StoryViewState;
|
||||
import org.thoughtcrime.securesms.database.model.ParentStoryId;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
|
||||
@@ -132,7 +133,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
static final String MESSAGE_RANGES = "ranges";
|
||||
|
||||
public static final String VIEW_ONCE = "reveal_duration";
|
||||
static final String IS_STORY = "is_story";
|
||||
static final String STORY_TYPE = "is_story";
|
||||
static final String PARENT_STORY_ID = "parent_story_id";
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
@@ -179,7 +180,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
SERVER_GUID + " TEXT DEFAULT NULL, " +
|
||||
RECEIPT_TIMESTAMP + " INTEGER DEFAULT -1, " +
|
||||
MESSAGE_RANGES + " BLOB DEFAULT NULL, " +
|
||||
IS_STORY + " INTEGER DEFAULT 0, " +
|
||||
STORY_TYPE + " INTEGER DEFAULT 0, " +
|
||||
PARENT_STORY_ID + " INTEGER DEFAULT 0);";
|
||||
|
||||
public static final String[] CREATE_INDEXS = {
|
||||
@@ -189,7 +190,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
"CREATE INDEX IF NOT EXISTS mms_date_server_index ON " + TABLE_NAME + " (" + DATE_SERVER + ");",
|
||||
"CREATE INDEX IF NOT EXISTS mms_thread_date_index ON " + TABLE_NAME + " (" + THREAD_ID + ", " + DATE_RECEIVED + ");",
|
||||
"CREATE INDEX IF NOT EXISTS mms_reactions_unread_index ON " + TABLE_NAME + " (" + REACTIONS_UNREAD + ");",
|
||||
"CREATE INDEX IF NOT EXISTS mms_is_story_index ON " + TABLE_NAME + " (" + IS_STORY + ");",
|
||||
"CREATE INDEX IF NOT EXISTS mms_is_story_index ON " + TABLE_NAME + " (" + STORY_TYPE + ");",
|
||||
"CREATE INDEX IF NOT EXISTS mms_parent_story_id_index ON " + TABLE_NAME + " (" + PARENT_STORY_ID + ");"
|
||||
};
|
||||
|
||||
@@ -206,7 +207,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
EXPIRES_IN, EXPIRE_STARTED, NOTIFIED, QUOTE_ID, QUOTE_AUTHOR, QUOTE_BODY, QUOTE_ATTACHMENT, QUOTE_MISSING, QUOTE_MENTIONS,
|
||||
SHARED_CONTACTS, LINK_PREVIEWS, UNIDENTIFIED, VIEW_ONCE, REACTIONS_UNREAD, REACTIONS_LAST_SEEN,
|
||||
REMOTE_DELETED, MENTIONS_SELF, NOTIFIED_TIMESTAMP, VIEWED_RECEIPT_COUNT, RECEIPT_TIMESTAMP, MESSAGE_RANGES,
|
||||
IS_STORY, PARENT_STORY_ID,
|
||||
STORY_TYPE, PARENT_STORY_ID,
|
||||
"json_group_array(json_object(" +
|
||||
"'" + AttachmentDatabase.ROW_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + ", " +
|
||||
"'" + AttachmentDatabase.UNIQUE_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UNIQUE_ID + ", " +
|
||||
@@ -237,9 +238,9 @@ public class MmsDatabase extends MessageDatabase {
|
||||
"'" + AttachmentDatabase.DISPLAY_ORDER + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DISPLAY_ORDER + ", " +
|
||||
"'" + AttachmentDatabase.UPLOAD_TIMESTAMP + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UPLOAD_TIMESTAMP +
|
||||
")) AS " + AttachmentDatabase.ATTACHMENT_JSON_ALIAS,
|
||||
};
|
||||
};
|
||||
|
||||
private static final String IS_STORY_CLAUSE = IS_STORY + " = ? AND " + REMOTE_DELETED + " = ?";
|
||||
private static final String IS_STORY_CLAUSE = STORY_TYPE + " > 0 AND " + REMOTE_DELETED + " = 0";
|
||||
|
||||
private static final String RAW_ID_WHERE = TABLE_NAME + "._id = ?";
|
||||
|
||||
@@ -538,7 +539,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
|
||||
String[] projection = new String[]{"1"};
|
||||
String where = IS_STORY_CLAUSE + " AND " + ID + " = ?";
|
||||
String[] whereArgs = SqlUtil.buildArgs(1, 0, messageId);
|
||||
String[] whereArgs = SqlUtil.buildArgs(messageId);
|
||||
|
||||
try (Cursor cursor = database.query(TABLE_NAME, projection, where, whereArgs, null, null, null, "1")) {
|
||||
return cursor != null && cursor.moveToFirst();
|
||||
@@ -559,7 +560,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
final String[] whereArgs;
|
||||
if (threadId == null) {
|
||||
where += " AND " + RECIPIENT_ID + " = ?";
|
||||
whereArgs = SqlUtil.buildArgs(1, 0, recipientId);
|
||||
whereArgs = SqlUtil.buildArgs(recipientId);
|
||||
} else {
|
||||
where += " AND " + THREAD_ID_WHERE;
|
||||
whereArgs = SqlUtil.buildArgs(1, 0, threadId);
|
||||
@@ -571,25 +572,20 @@ public class MmsDatabase extends MessageDatabase {
|
||||
@Override
|
||||
public @NonNull MessageDatabase.Reader getAllOutgoingStories() {
|
||||
String where = IS_STORY_CLAUSE + " AND (" + getOutgoingTypeClause() + ")";
|
||||
String[] whereArgs = SqlUtil.buildArgs(1, 0);
|
||||
|
||||
return new Reader(rawQuery(where, whereArgs, true, -1L));
|
||||
return new Reader(rawQuery(where, null, true, -1L));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull MessageDatabase.Reader getAllStories() {
|
||||
String where = IS_STORY_CLAUSE;
|
||||
String[] whereArgs = SqlUtil.buildArgs(1, 0);
|
||||
Cursor cursor = rawQuery(where, whereArgs, true, -1L);
|
||||
|
||||
return new Reader(cursor);
|
||||
return new Reader(rawQuery(IS_STORY_CLAUSE, null, true, -1L));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull MessageDatabase.Reader getAllStoriesFor(@NonNull RecipientId recipientId) {
|
||||
long threadId = SignalDatabase.threads().getThreadIdIfExistsFor(recipientId);
|
||||
String where = IS_STORY_CLAUSE + " AND " + THREAD_ID_WHERE;
|
||||
String[] whereArgs = SqlUtil.buildArgs(1, 0, threadId);
|
||||
String[] whereArgs = SqlUtil.buildArgs(threadId);
|
||||
Cursor cursor = rawQuery(where, whereArgs, true, -1L);
|
||||
|
||||
return new Reader(cursor);
|
||||
@@ -609,7 +605,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
@VisibleForTesting
|
||||
@NonNull StoryViewState getStoryViewState(long threadId) {
|
||||
final String hasStoryQuery = "SELECT EXISTS(SELECT 1 FROM " + TABLE_NAME + " WHERE " + IS_STORY_CLAUSE + " AND " + THREAD_ID_WHERE + " LIMIT 1)";
|
||||
final String[] hasStoryArgs = SqlUtil.buildArgs(1, 0, threadId);
|
||||
final String[] hasStoryArgs = SqlUtil.buildArgs(threadId);
|
||||
final boolean hasStories;
|
||||
|
||||
try (Cursor cursor = getReadableDatabase().rawQuery(hasStoryQuery, hasStoryArgs)) {
|
||||
@@ -621,7 +617,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
}
|
||||
|
||||
final String hasUnviewedStoriesQuery = "SELECT EXISTS(SELECT 1 FROM " + TABLE_NAME + " WHERE " + IS_STORY_CLAUSE + " AND " + THREAD_ID_WHERE + " AND " + VIEWED_RECEIPT_COUNT + " = ? " + "AND NOT (" + getOutgoingTypeClause() + ") LIMIT 1)";
|
||||
final String[] hasUnviewedStoriesArgs = SqlUtil.buildArgs(1, 0, threadId, 0);
|
||||
final String[] hasUnviewedStoriesArgs = SqlUtil.buildArgs(threadId, 0);
|
||||
final boolean hasUnviewedStories;
|
||||
|
||||
try (Cursor cursor = getReadableDatabase().rawQuery(hasUnviewedStoriesQuery, hasUnviewedStoriesArgs)) {
|
||||
@@ -640,7 +636,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
|
||||
String[] projection = new String[]{ID, RECIPIENT_ID};
|
||||
String where = IS_STORY_CLAUSE + " AND " + DATE_SENT + " = ?";
|
||||
String[] whereArgs = SqlUtil.buildArgs(1, 0, sentTimestamp);
|
||||
String[] whereArgs = SqlUtil.buildArgs(sentTimestamp);
|
||||
|
||||
try (Cursor cursor = database.query(TABLE_NAME, projection, where, whereArgs, null, null, null, "1")) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
@@ -664,10 +660,9 @@ public class MmsDatabase extends MessageDatabase {
|
||||
"ON " + TABLE_NAME + "." + THREAD_ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ID + " " +
|
||||
"WHERE " + IS_STORY_CLAUSE + " " +
|
||||
"ORDER BY " + ThreadDatabase.RECIPIENT_ID + " DESC";
|
||||
String[] args = SqlUtil.buildArgs(1, 0);
|
||||
List<RecipientId> recipientIds;
|
||||
|
||||
try (Cursor cursor = db.rawQuery(query, args)) {
|
||||
try (Cursor cursor = db.rawQuery(query, null)) {
|
||||
if (cursor != null) {
|
||||
recipientIds = new ArrayList<>(cursor.getCount());
|
||||
|
||||
@@ -694,9 +689,8 @@ public class MmsDatabase extends MessageDatabase {
|
||||
public long getUnreadStoryCount() {
|
||||
String[] columns = new String[]{"COUNT(*)"};
|
||||
String where = IS_STORY_CLAUSE + " AND NOT (" + getOutgoingTypeClause() + ") AND " + VIEWED_RECEIPT_COUNT + " = 0";
|
||||
String[] whereArgs = SqlUtil.buildArgs(1, 0);
|
||||
|
||||
try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, columns, where, whereArgs, null, null, null, null)) {
|
||||
try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, columns, where, null, null, null, null, null)) {
|
||||
return cursor != null && cursor.moveToFirst() ? cursor.getInt(0) : 0;
|
||||
}
|
||||
}
|
||||
@@ -730,11 +724,10 @@ public class MmsDatabase extends MessageDatabase {
|
||||
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
|
||||
String[] columns = new String[]{DATE_SENT};
|
||||
String where = IS_STORY_CLAUSE;
|
||||
String[] whereArgs = SqlUtil.buildArgs(1, 0);
|
||||
String orderBy = DATE_SENT + " ASC";
|
||||
String limit = "1";
|
||||
|
||||
try (Cursor cursor = db.query(TABLE_NAME, columns, where, whereArgs, null, null, orderBy, limit)) {
|
||||
try (Cursor cursor = db.query(TABLE_NAME, columns, where, null, null, null, orderBy, limit)) {
|
||||
return cursor != null && cursor.moveToNext() ? cursor.getLong(0) : null;
|
||||
}
|
||||
}
|
||||
@@ -746,7 +739,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
db.beginTransaction();
|
||||
try {
|
||||
String storiesBeforeTimestampWhere = IS_STORY_CLAUSE + " AND " + DATE_SENT + " < ?";
|
||||
String[] sharedArgs = SqlUtil.buildArgs(1, 0, timestamp);
|
||||
String[] sharedArgs = SqlUtil.buildArgs(timestamp);
|
||||
String deleteStoryRepliesQuery = "DELETE FROM " + TABLE_NAME + " " +
|
||||
"WHERE " + PARENT_STORY_ID + " IN (" +
|
||||
"SELECT " + ID + " " +
|
||||
@@ -814,7 +807,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
public int getMessageCountForThread(long threadId) {
|
||||
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
|
||||
|
||||
String query = THREAD_ID + " = ? AND " + IS_STORY + " = ? AND " + PARENT_STORY_ID + " <= ?";
|
||||
String query = THREAD_ID + " = ? AND " + STORY_TYPE + " = ? AND " + PARENT_STORY_ID + " <= ?";
|
||||
String[] args = SqlUtil.buildArgs(threadId, 0, 0);
|
||||
|
||||
try (Cursor cursor = db.query(TABLE_NAME, COUNT, query, args, null, null, null)) {
|
||||
@@ -830,7 +823,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
public int getMessageCountForThread(long threadId, long beforeTime) {
|
||||
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
|
||||
|
||||
String query = THREAD_ID + " = ? AND " + DATE_RECEIVED + " < ? AND " + IS_STORY + " = ? AND " + PARENT_STORY_ID + " <= ?";
|
||||
String query = THREAD_ID + " = ? AND " + DATE_RECEIVED + " < ? AND " + STORY_TYPE + " = ? AND " + PARENT_STORY_ID + " <= ?";
|
||||
String[] args = SqlUtil.buildArgs(threadId, beforeTime, 0, 0);
|
||||
|
||||
try (Cursor cursor = db.query(TABLE_NAME, COUNT, query, args, null, null, null)) {
|
||||
@@ -1208,20 +1201,20 @@ public class MmsDatabase extends MessageDatabase {
|
||||
@Override
|
||||
public List<MarkedMessageInfo> setMessagesReadSince(long threadId, long sinceTimestamp) {
|
||||
if (sinceTimestamp == -1) {
|
||||
return setMessagesRead(THREAD_ID + " = ? AND " + IS_STORY + " = 0 AND " + PARENT_STORY_ID + " <= 0 AND (" + READ + " = 0 OR (" + REACTIONS_UNREAD + " = 1 AND (" + getOutgoingTypeClause() + ")))", new String[] {String.valueOf(threadId)});
|
||||
return setMessagesRead(THREAD_ID + " = ? AND " + STORY_TYPE + " = 0 AND " + PARENT_STORY_ID + " <= 0 AND (" + READ + " = 0 OR (" + REACTIONS_UNREAD + " = 1 AND (" + getOutgoingTypeClause() + ")))", new String[] { String.valueOf(threadId)});
|
||||
} else {
|
||||
return setMessagesRead(THREAD_ID + " = ? AND " + IS_STORY + " = 0 AND " + PARENT_STORY_ID + " <= 0 AND (" + READ + " = 0 OR (" + REACTIONS_UNREAD + " = 1 AND ( " + getOutgoingTypeClause() + " ))) AND " + DATE_RECEIVED + " <= ?", new String[]{String.valueOf(threadId), String.valueOf(sinceTimestamp)});
|
||||
return setMessagesRead(THREAD_ID + " = ? AND " + STORY_TYPE + " = 0 AND " + PARENT_STORY_ID + " <= 0 AND (" + READ + " = 0 OR (" + REACTIONS_UNREAD + " = 1 AND ( " + getOutgoingTypeClause() + " ))) AND " + DATE_RECEIVED + " <= ?", new String[]{ String.valueOf(threadId), String.valueOf(sinceTimestamp)});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MarkedMessageInfo> setEntireThreadRead(long threadId) {
|
||||
return setMessagesRead(THREAD_ID + " = ? AND " + IS_STORY + " = 0 AND " + PARENT_STORY_ID + " <= 0", new String[] {String.valueOf(threadId)});
|
||||
return setMessagesRead(THREAD_ID + " = ? AND " + STORY_TYPE + " = 0 AND " + PARENT_STORY_ID + " <= 0", new String[] { String.valueOf(threadId)});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MarkedMessageInfo> setAllMessagesRead() {
|
||||
return setMessagesRead(IS_STORY+ " = 0 AND " + PARENT_STORY_ID + " <= 0 AND (" + READ + " = 0 OR (" + REACTIONS_UNREAD + " = 1 AND (" + getOutgoingTypeClause() + ")))", null);
|
||||
return setMessagesRead(STORY_TYPE + " = 0 AND " + PARENT_STORY_ID + " <= 0 AND (" + READ + " = 0 OR (" + REACTIONS_UNREAD + " = 1 AND (" + getOutgoingTypeClause() + ")))", null);
|
||||
}
|
||||
|
||||
private List<MarkedMessageInfo> setMessagesRead(String where, String[] arguments) {
|
||||
@@ -1419,7 +1412,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
int distributionType = SignalDatabase.threads().getDistributionType(threadId);
|
||||
String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.MISMATCHED_IDENTITIES));
|
||||
String networkDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.NETWORK_FAILURE));
|
||||
boolean isStory = CursorUtil.requireBoolean(cursor, IS_STORY);
|
||||
StoryType storyType = StoryType.fromCode(CursorUtil.requireInt(cursor, STORY_TYPE));
|
||||
ParentStoryId parentStoryId = ParentStoryId.deserialize(CursorUtil.requireLong(cursor, PARENT_STORY_ID));
|
||||
|
||||
long quoteId = cursor.getLong(cursor.getColumnIndexOrThrow(QUOTE_ID));
|
||||
@@ -1469,7 +1462,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
return new OutgoingExpirationUpdateMessage(recipient, timestamp, expiresIn);
|
||||
}
|
||||
|
||||
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, viewOnce, distributionType, isStory, parentStoryId, quote, contacts, previews, mentions, networkFailures, mismatches);
|
||||
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, viewOnce, distributionType, storyType, parentStoryId, quote, contacts, previews, mentions, networkFailures, mismatches);
|
||||
|
||||
if (Types.isSecureType(outboxType)) {
|
||||
return new OutgoingSecureMediaMessage(message);
|
||||
@@ -1590,7 +1583,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
contentValues.put(SUBSCRIPTION_ID, retrieved.getSubscriptionId());
|
||||
contentValues.put(EXPIRES_IN, retrieved.getExpiresIn());
|
||||
contentValues.put(VIEW_ONCE, retrieved.isViewOnce() ? 1 : 0);
|
||||
contentValues.put(IS_STORY, retrieved.isStory() ? 1 : 0);
|
||||
contentValues.put(STORY_TYPE, retrieved.getStoryType().getCode());
|
||||
contentValues.put(PARENT_STORY_ID, retrieved.getParentStoryId() != null ? retrieved.getParentStoryId().serialize() : 0);
|
||||
contentValues.put(READ, retrieved.isExpirationUpdate() ? 1 : 0);
|
||||
contentValues.put(UNIDENTIFIED, retrieved.isUnidentified());
|
||||
@@ -1623,7 +1616,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
|
||||
long messageId = insertMediaMessage(threadId, retrieved.getBody(), retrieved.getAttachments(), quoteAttachments, retrieved.getSharedContacts(), retrieved.getLinkPreviews(), retrieved.getMentions(), retrieved.getMessageRanges(), contentValues, null, true);
|
||||
|
||||
if (!Types.isExpirationTimerUpdate(mailbox) && !retrieved.isStory() && retrieved.getParentStoryId() == null) {
|
||||
if (!Types.isExpirationTimerUpdate(mailbox) && !retrieved.getStoryType().isStory() && retrieved.getParentStoryId() == null) {
|
||||
SignalDatabase.threads().incrementUnread(threadId, 1);
|
||||
SignalDatabase.threads().update(threadId, true);
|
||||
}
|
||||
@@ -1785,7 +1778,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
contentValues.put(RECIPIENT_ID, message.getRecipient().getId().serialize());
|
||||
contentValues.put(DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values()).mapToLong(EarlyReceiptCache.Receipt::getCount).sum());
|
||||
contentValues.put(RECEIPT_TIMESTAMP, Stream.of(earlyDeliveryReceipts.values()).mapToLong(EarlyReceiptCache.Receipt::getTimestamp).max().orElse(-1));
|
||||
contentValues.put(IS_STORY, message.isStory() ? 1 : 0);
|
||||
contentValues.put(STORY_TYPE, message.getStoryType().getCode());
|
||||
contentValues.put(PARENT_STORY_ID, message.getParentStoryId() != null ? message.getParentStoryId().serialize() : 0);
|
||||
|
||||
if (message.getRecipient().isSelf() && hasAudioAttachment(message.getAttachments())) {
|
||||
@@ -1840,7 +1833,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
|
||||
SignalDatabase.threads().updateLastSeenAndMarkSentAndLastScrolledSilenty(threadId);
|
||||
|
||||
if (!message.isStory()) {
|
||||
if (!message.getStoryType().isStory()) {
|
||||
ApplicationDependencies.getDatabaseObserver().notifyMessageInsertObservers(threadId, new MessageId(messageId, true));
|
||||
} else {
|
||||
ApplicationDependencies.getDatabaseObserver().notifyStoryObservers(message.getRecipient().getId());
|
||||
@@ -2276,7 +2269,8 @@ public class MmsDatabase extends MessageDatabase {
|
||||
0,
|
||||
0,
|
||||
-1,
|
||||
null);
|
||||
null,
|
||||
message.getStoryType());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2329,6 +2323,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.SUBSCRIPTION_ID));
|
||||
int viewedReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsColumns.VIEWED_RECEIPT_COUNT));
|
||||
long receiptTimestamp = CursorUtil.requireLong(cursor, MmsSmsColumns.RECEIPT_TIMESTAMP);
|
||||
StoryType storyType = StoryType.fromCode(CursorUtil.requireInt(cursor, STORY_TYPE));
|
||||
|
||||
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
|
||||
readReceiptCount = 0;
|
||||
@@ -2350,7 +2345,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
addressDeviceId, dateSent, dateReceived, deliveryReceiptCount, threadId,
|
||||
contentLocationBytes, messageSize, expiry, status,
|
||||
transactionIdBytes, mailbox, subscriptionId, slideDeck,
|
||||
readReceiptCount, viewedReceiptCount, receiptTimestamp);
|
||||
readReceiptCount, viewedReceiptCount, receiptTimestamp, storyType);
|
||||
}
|
||||
|
||||
private MediaMmsMessageRecord getMediaMmsMessageRecord(Cursor cursor) {
|
||||
@@ -2379,6 +2374,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
int viewedReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsColumns.VIEWED_RECEIPT_COUNT));
|
||||
long receiptTimestamp = CursorUtil.requireLong(cursor, MmsSmsColumns.RECEIPT_TIMESTAMP);
|
||||
byte[] messageRangesData = CursorUtil.requireBlob(cursor, MESSAGE_RANGES);
|
||||
StoryType storyType = StoryType.fromCode(CursorUtil.requireInt(cursor, STORY_TYPE));
|
||||
|
||||
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
|
||||
readReceiptCount = 0;
|
||||
@@ -2413,7 +2409,8 @@ public class MmsDatabase extends MessageDatabase {
|
||||
threadId, body, slideDeck, partCount, box, mismatches,
|
||||
networkFailures, subscriptionId, expiresIn, expireStarted,
|
||||
isViewOnce, readReceiptCount, quote, contacts, previews, unidentified, Collections.emptyList(),
|
||||
remoteDelete, mentionsSelf, notifiedTimestamp, viewedReceiptCount, receiptTimestamp, messageRanges);
|
||||
remoteDelete, mentionsSelf, notifiedTimestamp, viewedReceiptCount, receiptTimestamp, messageRanges,
|
||||
storyType);
|
||||
}
|
||||
|
||||
private Set<IdentityKeyMismatch> getMismatchedIdentities(String document) {
|
||||
|
||||
@@ -111,14 +111,14 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsSmsColumns.VIEWED_RECEIPT_COUNT,
|
||||
MmsSmsColumns.RECEIPT_TIMESTAMP,
|
||||
MmsDatabase.MESSAGE_RANGES,
|
||||
MmsDatabase.IS_STORY,
|
||||
MmsDatabase.STORY_TYPE,
|
||||
MmsDatabase.PARENT_STORY_ID};
|
||||
|
||||
private static final String SNIPPET_QUERY = "SELECT " + MmsSmsColumns.ID + ", 0 AS " + TRANSPORT + ", " + SmsDatabase.TYPE + " AS " + MmsSmsColumns.NORMALIZED_TYPE + ", " + SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " FROM " + SmsDatabase.TABLE_NAME + " " +
|
||||
"WHERE " + MmsSmsColumns.THREAD_ID + " = ? AND " + SmsDatabase.TYPE + " NOT IN (" + SmsDatabase.Types.PROFILE_CHANGE_TYPE + ", " + SmsDatabase.Types.GV1_MIGRATION_TYPE + ", " + SmsDatabase.Types.CHANGE_NUMBER_TYPE + ", " + SmsDatabase.Types.BOOST_REQUEST_TYPE + ") AND " + SmsDatabase.TYPE + " & " + GROUP_V2_LEAVE_BITS + " != " + GROUP_V2_LEAVE_BITS + " " +
|
||||
"UNION ALL " +
|
||||
"SELECT " + MmsSmsColumns.ID + ", 1 AS " + TRANSPORT + ", " + MmsDatabase.MESSAGE_BOX + " AS " + MmsSmsColumns.NORMALIZED_TYPE + ", " + MmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " FROM " + MmsDatabase.TABLE_NAME + " " +
|
||||
"WHERE " + MmsSmsColumns.THREAD_ID + " = ? AND " + MmsDatabase.MESSAGE_BOX + " & " + GROUP_V2_LEAVE_BITS + " != " + GROUP_V2_LEAVE_BITS + " AND " + MmsDatabase.IS_STORY + " = 0 AND " + MmsDatabase.PARENT_STORY_ID + " <= 0 " +
|
||||
"WHERE " + MmsSmsColumns.THREAD_ID + " = ? AND " + MmsDatabase.MESSAGE_BOX + " & " + GROUP_V2_LEAVE_BITS + " != " + GROUP_V2_LEAVE_BITS + " AND " + MmsDatabase.STORY_TYPE + " = 0 AND " + MmsDatabase.PARENT_STORY_ID + " <= 0 " +
|
||||
"ORDER BY " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC " +
|
||||
"LIMIT 1";
|
||||
|
||||
@@ -202,7 +202,7 @@ public class MmsSmsDatabase extends Database {
|
||||
public Cursor getConversation(long threadId, long offset, long limit) {
|
||||
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
|
||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
|
||||
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsDatabase.IS_STORY + " = 0 AND " + MmsDatabase.PARENT_STORY_ID + " <= 0";
|
||||
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsDatabase.STORY_TYPE + " = 0 AND " + MmsDatabase.PARENT_STORY_ID + " <= 0";
|
||||
String limitStr = limit > 0 || offset > 0 ? offset + ", " + limit : null;
|
||||
String query = buildQuery(PROJECTION, selection, order, limitStr, false);
|
||||
|
||||
@@ -264,13 +264,13 @@ public class MmsSmsDatabase extends Database {
|
||||
}
|
||||
|
||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
|
||||
String selection = MmsSmsColumns.NOTIFIED + " = 0 AND " + MmsDatabase.IS_STORY + " = 0 AND " + MmsDatabase.PARENT_STORY_ID + " <= 0 AND (" + MmsSmsColumns.READ + " = 0 OR " + MmsSmsColumns.REACTIONS_UNREAD + " = 1" + (stickyQuery.length() > 0 ? " OR (" + stickyQuery.toString() + ")" : "") + ")";
|
||||
String selection = MmsSmsColumns.NOTIFIED + " = 0 AND " + MmsDatabase.STORY_TYPE + " = 0 AND " + MmsDatabase.PARENT_STORY_ID + " <= 0 AND (" + MmsSmsColumns.READ + " = 0 OR " + MmsSmsColumns.REACTIONS_UNREAD + " = 1" + (stickyQuery.length() > 0 ? " OR (" + stickyQuery.toString() + ")" : "") + ")";
|
||||
|
||||
return queryTables(PROJECTION, selection, order, null);
|
||||
}
|
||||
|
||||
public int getUnreadCount(long threadId) {
|
||||
String selection = MmsSmsColumns.READ + " = 0 AND " + MmsDatabase.IS_STORY + " = 0 AND " + MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsDatabase.PARENT_STORY_ID + " <= 0";
|
||||
String selection = MmsSmsColumns.READ + " = 0 AND " + MmsDatabase.STORY_TYPE + " = 0 AND " + MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsDatabase.PARENT_STORY_ID + " <= 0";
|
||||
|
||||
try (Cursor cursor = queryTables(PROJECTION, selection, null, null)) {
|
||||
return cursor != null ? cursor.getCount() : 0;
|
||||
@@ -729,7 +729,7 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsSmsColumns.VIEWED_RECEIPT_COUNT,
|
||||
MmsSmsColumns.RECEIPT_TIMESTAMP,
|
||||
MmsDatabase.MESSAGE_RANGES,
|
||||
MmsDatabase.IS_STORY,
|
||||
MmsDatabase.STORY_TYPE,
|
||||
MmsDatabase.PARENT_STORY_ID};
|
||||
|
||||
String[] smsProjection = {SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||
@@ -765,8 +765,8 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsSmsColumns.VIEWED_RECEIPT_COUNT,
|
||||
MmsSmsColumns.RECEIPT_TIMESTAMP,
|
||||
MmsDatabase.MESSAGE_RANGES,
|
||||
"0 AS " + MmsDatabase.IS_STORY,
|
||||
"0 AS " + MmsDatabase.PARENT_STORY_ID };
|
||||
"0 AS " + MmsDatabase.STORY_TYPE,
|
||||
"0 AS " + MmsDatabase.PARENT_STORY_ID};
|
||||
|
||||
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
|
||||
SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
|
||||
@@ -829,7 +829,7 @@ public class MmsSmsDatabase extends Database {
|
||||
mmsColumnsPresent.add(MmsSmsColumns.VIEWED_RECEIPT_COUNT);
|
||||
mmsColumnsPresent.add(MmsSmsColumns.RECEIPT_TIMESTAMP);
|
||||
mmsColumnsPresent.add(MmsDatabase.MESSAGE_RANGES);
|
||||
mmsColumnsPresent.add(MmsDatabase.IS_STORY);
|
||||
mmsColumnsPresent.add(MmsDatabase.STORY_TYPE);
|
||||
mmsColumnsPresent.add(MmsDatabase.PARENT_STORY_ID);
|
||||
|
||||
Set<String> smsColumnsPresent = new HashSet<>();
|
||||
@@ -858,7 +858,7 @@ public class MmsSmsDatabase extends Database {
|
||||
smsColumnsPresent.add(MmsSmsColumns.REMOTE_DELETED);
|
||||
smsColumnsPresent.add(MmsSmsColumns.NOTIFIED_TIMESTAMP);
|
||||
smsColumnsPresent.add(MmsSmsColumns.RECEIPT_TIMESTAMP);
|
||||
smsColumnsPresent.add("0 AS " + MmsDatabase.IS_STORY);
|
||||
smsColumnsPresent.add("0 AS " + MmsDatabase.STORY_TYPE);
|
||||
smsColumnsPresent.add("0 AS " + MmsDatabase.PARENT_STORY_ID);
|
||||
|
||||
String mmsGroupBy = includeAttachments ? MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID : null;
|
||||
|
||||
@@ -192,8 +192,9 @@ object SignalDatabaseMigrations {
|
||||
private const val PNI_STORES = 130
|
||||
private const val DONATION_RECEIPTS = 131
|
||||
private const val STORIES = 132
|
||||
private const val ALLOW_STORY_REPLIES = 133
|
||||
|
||||
const val DATABASE_VERSION = 132
|
||||
const val DATABASE_VERSION = 133
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
@@ -2471,6 +2472,10 @@ object SignalDatabaseMigrations {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (oldVersion < ALLOW_STORY_REPLIES) {
|
||||
db.execSQL("ALTER TABLE distribution_list ADD COLUMN allows_replies INTEGER DEFAULT 1")
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
||||
@@ -5,5 +5,6 @@ import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
data class DistributionListPartialRecord(
|
||||
val id: DistributionListId,
|
||||
val name: CharSequence,
|
||||
val recipientId: RecipientId
|
||||
val recipientId: RecipientId,
|
||||
val allowsReplies: Boolean
|
||||
)
|
||||
|
||||
@@ -10,5 +10,6 @@ data class DistributionListRecord(
|
||||
val id: DistributionListId,
|
||||
val name: String,
|
||||
val distributionId: DistributionId,
|
||||
val allowsReplies: Boolean,
|
||||
val members: List<RecipientId>
|
||||
)
|
||||
|
||||
@@ -91,12 +91,14 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
|
||||
long notifiedTimestamp,
|
||||
int viewedReceiptCount,
|
||||
long receiptTimestamp,
|
||||
@Nullable BodyRangeList messageRanges)
|
||||
@Nullable BodyRangeList messageRanges,
|
||||
@NonNull StoryType storyType)
|
||||
{
|
||||
super(id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent,
|
||||
dateReceived, dateServer, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures,
|
||||
subscriptionId, expiresIn, expireStarted, viewOnce, slideDeck,
|
||||
readReceiptCount, quote, contacts, linkPreviews, unidentified, reactions, remoteDelete, notifiedTimestamp, viewedReceiptCount, receiptTimestamp);
|
||||
readReceiptCount, quote, contacts, linkPreviews, unidentified, reactions, remoteDelete, notifiedTimestamp, viewedReceiptCount, receiptTimestamp,
|
||||
storyType);
|
||||
this.partCount = partCount;
|
||||
this.mentionsSelf = mentionsSelf;
|
||||
this.messageRanges = messageRanges;
|
||||
@@ -150,7 +152,7 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
|
||||
return new MediaMmsMessageRecord(getId(), getRecipient(), getIndividualRecipient(), getRecipientDeviceId(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
|
||||
getPartCount(), getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
|
||||
getReadReceiptCount(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), reactions, isRemoteDelete(), mentionsSelf,
|
||||
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges());
|
||||
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType());
|
||||
}
|
||||
|
||||
public @NonNull MediaMmsMessageRecord withAttachments(@NonNull Context context, @NonNull List<DatabaseAttachment> attachments) {
|
||||
@@ -171,7 +173,7 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
|
||||
return new MediaMmsMessageRecord(getId(), getRecipient(), getIndividualRecipient(), getRecipientDeviceId(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), slideDeck,
|
||||
getPartCount(), getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
|
||||
getReadReceiptCount(), quote, contacts, linkPreviews, isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
|
||||
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges());
|
||||
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType());
|
||||
}
|
||||
|
||||
private static @NonNull List<Contact> updateContacts(@NonNull List<Contact> contacts, @NonNull Map<AttachmentId, DatabaseAttachment> attachmentIdMap) {
|
||||
|
||||
@@ -22,6 +22,7 @@ public abstract class MmsMessageRecord extends MessageRecord {
|
||||
private final @Nullable Quote quote;
|
||||
private final @NonNull List<Contact> contacts = new LinkedList<>();
|
||||
private final @NonNull List<LinkPreview> linkPreviews = new LinkedList<>();
|
||||
private final @NonNull StoryType storyType;
|
||||
|
||||
private final boolean viewOnce;
|
||||
|
||||
@@ -35,7 +36,7 @@ public abstract class MmsMessageRecord extends MessageRecord {
|
||||
@Nullable Quote quote, @NonNull List<Contact> contacts,
|
||||
@NonNull List<LinkPreview> linkPreviews, boolean unidentified,
|
||||
@NonNull List<ReactionRecord> reactions, boolean remoteDelete, long notifiedTimestamp,
|
||||
int viewedReceiptCount, long receiptTimestamp)
|
||||
int viewedReceiptCount, long receiptTimestamp, @NonNull StoryType storyType)
|
||||
{
|
||||
super(id, body, conversationRecipient, individualRecipient, recipientDeviceId,
|
||||
dateSent, dateReceived, dateServer, threadId, deliveryStatus, deliveryReceiptCount,
|
||||
@@ -45,6 +46,7 @@ public abstract class MmsMessageRecord extends MessageRecord {
|
||||
this.slideDeck = slideDeck;
|
||||
this.quote = quote;
|
||||
this.viewOnce = viewOnce;
|
||||
this.storyType = storyType;
|
||||
|
||||
this.contacts.addAll(contacts);
|
||||
this.linkPreviews.addAll(linkPreviews);
|
||||
@@ -76,6 +78,10 @@ public abstract class MmsMessageRecord extends MessageRecord {
|
||||
return viewOnce;
|
||||
}
|
||||
|
||||
public @NonNull StoryType getStoryType() {
|
||||
return storyType;
|
||||
}
|
||||
|
||||
public boolean containsMediaSlide() {
|
||||
return slideDeck.containsMediaSlide();
|
||||
}
|
||||
|
||||
@@ -53,13 +53,13 @@ public class NotificationMmsMessageRecord extends MmsMessageRecord {
|
||||
long threadId, byte[] contentLocation, long messageSize,
|
||||
long expiry, int status, byte[] transactionId, long mailbox,
|
||||
int subscriptionId, SlideDeck slideDeck, int readReceiptCount,
|
||||
int viewedReceiptCount, long receiptTimestamp)
|
||||
int viewedReceiptCount, long receiptTimestamp, @NonNull StoryType storyType)
|
||||
{
|
||||
super(id, "", conversationRecipient, individualRecipient, recipientDeviceId,
|
||||
dateSent, dateReceived, -1, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox,
|
||||
new HashSet<>(), new HashSet<>(), subscriptionId,
|
||||
0, 0, false, slideDeck, readReceiptCount, null, Collections.emptyList(), Collections.emptyList(), false,
|
||||
Collections.emptyList(), false, 0, viewedReceiptCount, receiptTimestamp);
|
||||
Collections.emptyList(), false, 0, viewedReceiptCount, receiptTimestamp, storyType);
|
||||
|
||||
this.contentLocation = contentLocation;
|
||||
this.messageSize = messageSize;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.thoughtcrime.securesms.database.model
|
||||
|
||||
/**
|
||||
* Represents whether a given story can be replied to.
|
||||
*/
|
||||
enum class StoryType(val code: Int) {
|
||||
/**
|
||||
* Not a story.
|
||||
*/
|
||||
NONE(0),
|
||||
|
||||
/**
|
||||
* User can send replies to this story.
|
||||
*/
|
||||
STORY_WITH_REPLIES(1),
|
||||
|
||||
/**
|
||||
* User cannot send replies to this story.
|
||||
*/
|
||||
STORY_WITHOUT_REPLIES(2);
|
||||
|
||||
val isStory get() = this == STORY_WITH_REPLIES || this == STORY_WITHOUT_REPLIES
|
||||
|
||||
val isStoryWithReplies get() = this == STORY_WITH_REPLIES
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromCode(code: Int): StoryType {
|
||||
return when (code) {
|
||||
1 -> STORY_WITH_REPLIES
|
||||
2 -> STORY_WITHOUT_REPLIES
|
||||
else -> NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ public final class PushDistributionListSendJob extends PushSendJob {
|
||||
|
||||
OutgoingMediaMessage message = SignalDatabase.mms().getOutgoingMessage(messageId);
|
||||
|
||||
if (!message.isStory()) {
|
||||
if (!message.getStoryType().isStory()) {
|
||||
throw new AssertionError("Only story messages are currently supported! MessageId: " + messageId);
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ public final class PushDistributionListSendJob extends PushSendJob {
|
||||
Set<NetworkFailure> existingNetworkFailures = message.getNetworkFailures();
|
||||
Set<IdentityKeyMismatch> existingIdentityMismatches = message.getIdentityKeyMismatches();
|
||||
|
||||
if (!message.isStory()) {
|
||||
if (!message.getStoryType().isStory()) {
|
||||
throw new MmsException("Only story sends are currently supported!");
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ public final class PushDistributionListSendJob extends PushSendJob {
|
||||
boolean isRecipientUpdate = Stream.of(SignalDatabase.groupReceipts().getGroupReceiptInfo(messageId))
|
||||
.anyMatch(info -> info.getStatus() > GroupReceiptDatabase.STATUS_UNDELIVERED);
|
||||
|
||||
SignalServiceStoryMessage storyMessage = SignalServiceStoryMessage.forFileAttachment(Recipient.self().getProfileKey(), null, attachmentPointers.get(0));
|
||||
SignalServiceStoryMessage storyMessage = SignalServiceStoryMessage.forFileAttachment(Recipient.self().getProfileKey(), null, attachmentPointers.get(0), message.getStoryType().isStoryWithReplies());
|
||||
return GroupSendUtil.sendStoryMessage(context, message.getRecipient().requireDistributionListId(), destinations, isRecipientUpdate, new MessageId(messageId, true), message.getSentTimeMillis(), storyMessage);
|
||||
} catch (ServerRejectedException e) {
|
||||
throw new UndeliverableMessageException(e);
|
||||
|
||||
@@ -51,7 +51,6 @@ import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Quote;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServicePreview;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessage;
|
||||
@@ -231,7 +230,7 @@ public final class PushGroupSendJob extends PushSendJob {
|
||||
boolean isRecipientUpdate = Stream.of(SignalDatabase.groupReceipts().getGroupReceiptInfo(messageId))
|
||||
.anyMatch(info -> info.getStatus() > GroupReceiptDatabase.STATUS_UNDELIVERED);
|
||||
|
||||
if (message.isStory()) {
|
||||
if (message.getStoryType().isStory()) {
|
||||
// TODO [stories] Filter based off of stories capability
|
||||
Optional<GroupDatabase.GroupRecord> groupRecord = SignalDatabase.groups().getGroup(groupId);
|
||||
|
||||
@@ -241,7 +240,7 @@ public final class PushGroupSendJob extends PushSendJob {
|
||||
.withRevision(v2GroupProperties.getGroupRevision())
|
||||
.build();
|
||||
|
||||
SignalServiceStoryMessage storyMessage = SignalServiceStoryMessage.forFileAttachment(Recipient.self().getProfileKey(), groupContext, attachmentPointers.get(0));
|
||||
SignalServiceStoryMessage storyMessage = SignalServiceStoryMessage.forFileAttachment(Recipient.self().getProfileKey(), groupContext, attachmentPointers.get(0), message.getStoryType().isStoryWithReplies());
|
||||
|
||||
return GroupSendUtil.sendGroupStoryMessage(context, groupId.requireV2(), destinations, isRecipientUpdate, new MessageId(messageId, true), message.getSentTimeMillis(), storyMessage);
|
||||
} else {
|
||||
@@ -391,7 +390,7 @@ public final class PushGroupSendJob extends PushSendJob {
|
||||
SignalDatabase.attachments().deleteAttachmentFilesForViewOnceMessage(messageId);
|
||||
}
|
||||
|
||||
if (message.isStory()) {
|
||||
if (message.getStoryType().isStory()) {
|
||||
ApplicationDependencies.getExpireStoriesManager().scheduleIfNecessary();
|
||||
}
|
||||
} else if (!identityMismatches.isEmpty()) {
|
||||
|
||||
@@ -111,7 +111,7 @@ public class RefreshAttributesJob extends BaseJob {
|
||||
"\n Sender Key? " + capabilities.isSenderKey() +
|
||||
"\n Announcement Groups? " + capabilities.isAnnouncementGroup() +
|
||||
"\n Change Number? " + capabilities.isChangeNumber() +
|
||||
"\n Stories? " + capabilities.isChangeNumber() +
|
||||
"\n Stories? " + capabilities.isStories() +
|
||||
"\n UUID? " + capabilities.isUuid());
|
||||
|
||||
SignalServiceAccountManager signalAccountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
|
||||
import org.thoughtcrime.securesms.TransportOption;
|
||||
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
||||
import org.thoughtcrime.securesms.database.model.Mention;
|
||||
import org.thoughtcrime.securesms.database.model.StoryType;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender.PreUploadResult;
|
||||
import org.thoughtcrime.securesms.util.ParcelUtil;
|
||||
@@ -32,7 +33,7 @@ public class MediaSendActivityResult implements Parcelable {
|
||||
private final TransportOption transport;
|
||||
private final boolean viewOnce;
|
||||
private final Collection<Mention> mentions;
|
||||
private final boolean isStory;
|
||||
private final StoryType storyType;
|
||||
|
||||
public static @NonNull MediaSendActivityResult fromData(@NonNull Intent data) {
|
||||
MediaSendActivityResult result = data.getParcelableExtra(MediaSendActivityResult.EXTRA_RESULT);
|
||||
@@ -49,10 +50,10 @@ public class MediaSendActivityResult implements Parcelable {
|
||||
@NonNull TransportOption transport,
|
||||
boolean viewOnce,
|
||||
@NonNull List<Mention> mentions,
|
||||
boolean isStory)
|
||||
@NonNull StoryType storyType)
|
||||
{
|
||||
Preconditions.checkArgument(uploadResults.size() > 0, "Must supply uploadResults!");
|
||||
return new MediaSendActivityResult(recipientId, uploadResults, Collections.emptyList(), body, transport, viewOnce, mentions, isStory);
|
||||
return new MediaSendActivityResult(recipientId, uploadResults, Collections.emptyList(), body, transport, viewOnce, mentions, storyType);
|
||||
}
|
||||
|
||||
public static @NonNull MediaSendActivityResult forTraditionalSend(@NonNull RecipientId recipientId,
|
||||
@@ -61,10 +62,10 @@ public class MediaSendActivityResult implements Parcelable {
|
||||
@NonNull TransportOption transport,
|
||||
boolean viewOnce,
|
||||
@NonNull List<Mention> mentions,
|
||||
boolean isStory)
|
||||
@NonNull StoryType storyType)
|
||||
{
|
||||
Preconditions.checkArgument(nonUploadedMedia.size() > 0, "Must supply media!");
|
||||
return new MediaSendActivityResult(recipientId, Collections.emptyList(), nonUploadedMedia, body, transport, viewOnce, mentions, isStory);
|
||||
return new MediaSendActivityResult(recipientId, Collections.emptyList(), nonUploadedMedia, body, transport, viewOnce, mentions, storyType);
|
||||
}
|
||||
|
||||
private MediaSendActivityResult(@NonNull RecipientId recipientId,
|
||||
@@ -74,7 +75,7 @@ public class MediaSendActivityResult implements Parcelable {
|
||||
@NonNull TransportOption transport,
|
||||
boolean viewOnce,
|
||||
@NonNull List<Mention> mentions,
|
||||
boolean isStory)
|
||||
@NonNull StoryType storyType)
|
||||
{
|
||||
this.recipientId = recipientId;
|
||||
this.uploadResults = uploadResults;
|
||||
@@ -83,7 +84,7 @@ public class MediaSendActivityResult implements Parcelable {
|
||||
this.transport = transport;
|
||||
this.viewOnce = viewOnce;
|
||||
this.mentions = mentions;
|
||||
this.isStory = isStory;
|
||||
this.storyType = storyType;
|
||||
}
|
||||
|
||||
private MediaSendActivityResult(Parcel in) {
|
||||
@@ -94,7 +95,7 @@ public class MediaSendActivityResult implements Parcelable {
|
||||
this.transport = in.readParcelable(TransportOption.class.getClassLoader());
|
||||
this.viewOnce = ParcelUtil.readBoolean(in);
|
||||
this.mentions = ParcelUtil.readParcelableCollection(in, Mention.class);
|
||||
this.isStory = ParcelUtil.readBoolean(in);
|
||||
this.storyType = StoryType.fromCode(in.readInt());
|
||||
}
|
||||
|
||||
public @NonNull RecipientId getRecipientId() {
|
||||
@@ -129,8 +130,8 @@ public class MediaSendActivityResult implements Parcelable {
|
||||
return mentions;
|
||||
}
|
||||
|
||||
public boolean isStory() {
|
||||
return isStory;
|
||||
public @NonNull StoryType getStoryType() {
|
||||
return storyType;
|
||||
}
|
||||
|
||||
public static final Creator<MediaSendActivityResult> CREATOR = new Creator<MediaSendActivityResult>() {
|
||||
@@ -159,6 +160,6 @@ public class MediaSendActivityResult implements Parcelable {
|
||||
dest.writeParcelable(transport, 0);
|
||||
ParcelUtil.writeBoolean(dest, viewOnce);
|
||||
ParcelUtil.writeParcelableCollection(dest, mentions);
|
||||
ParcelUtil.writeBoolean(dest, isStory);
|
||||
dest.writeInt(storyType.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,10 @@ import org.thoughtcrime.securesms.TransportOption
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.contacts.paged.RecipientSearchKey
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
import org.thoughtcrime.securesms.database.model.Mention
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.mediasend.CompositeMediaTransform
|
||||
import org.thoughtcrime.securesms.mediasend.ImageEditorModelRenderMediaTransform
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
@@ -95,9 +97,15 @@ class MediaSelectionRepository(context: Context) {
|
||||
}
|
||||
|
||||
val singleRecipient: Recipient? = singleContact?.let { Recipient.resolved(it.recipientId) }
|
||||
val storyType: StoryType = if (singleRecipient?.isDistributionList == true) {
|
||||
SignalDatabase.distributionLists.getStoryType(singleRecipient.requireDistributionListId())
|
||||
} else {
|
||||
StoryType.NONE
|
||||
}
|
||||
|
||||
if (isSms || MessageSender.isLocalSelfSend(context, singleRecipient, isSms)) {
|
||||
Log.i(TAG, "SMS or local self-send. Skipping pre-upload.")
|
||||
emitter.onSuccess(MediaSendActivityResult.forTraditionalSend(singleRecipient!!.id, updatedMedia, trimmedBody, transport, isViewOnce, trimmedMentions, false))
|
||||
emitter.onSuccess(MediaSendActivityResult.forTraditionalSend(singleRecipient!!.id, updatedMedia, trimmedBody, transport, isViewOnce, trimmedMentions, StoryType.NONE))
|
||||
} else {
|
||||
val splitMessage = MessageUtil.getSplitMessage(context, trimmedBody, transport.calculateCharacters(trimmedBody).maxPrimaryMessageSize)
|
||||
val splitBody = splitMessage.body
|
||||
@@ -126,10 +134,10 @@ class MediaSelectionRepository(context: Context) {
|
||||
uploadRepository.deleteAbandonedAttachments()
|
||||
emitter.onComplete()
|
||||
} else if (uploadResults.isNotEmpty()) {
|
||||
emitter.onSuccess(MediaSendActivityResult.forPreUpload(singleRecipient!!.id, uploadResults, splitBody, transport, isViewOnce, trimmedMentions, singleContact.isStory))
|
||||
emitter.onSuccess(MediaSendActivityResult.forPreUpload(singleRecipient!!.id, uploadResults, splitBody, transport, isViewOnce, trimmedMentions, storyType))
|
||||
} else {
|
||||
Log.w(TAG, "Got empty upload results! isSms: $isSms, updatedMedia.size(): ${updatedMedia.size}, isViewOnce: $isViewOnce, target: $singleContact")
|
||||
emitter.onSuccess(MediaSendActivityResult.forTraditionalSend(singleRecipient!!.id, updatedMedia, trimmedBody, transport, isViewOnce, trimmedMentions, singleContact.isStory))
|
||||
emitter.onSuccess(MediaSendActivityResult.forTraditionalSend(singleRecipient!!.id, updatedMedia, trimmedBody, transport, isViewOnce, trimmedMentions, storyType))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,6 +204,13 @@ class MediaSelectionRepository(context: Context) {
|
||||
for (contact in contacts) {
|
||||
val recipient = Recipient.resolved(contact.recipientId)
|
||||
val isStory = contact is ContactSearchKey.Story || recipient.isDistributionList
|
||||
|
||||
val storyType: StoryType = when {
|
||||
recipient.isDistributionList -> SignalDatabase.distributionLists.getStoryType(recipient.requireDistributionListId())
|
||||
isStory -> StoryType.STORY_WITH_REPLIES
|
||||
else -> StoryType.NONE
|
||||
}
|
||||
|
||||
val message = OutgoingMediaMessage(
|
||||
recipient,
|
||||
body,
|
||||
@@ -205,7 +220,7 @@ class MediaSelectionRepository(context: Context) {
|
||||
TimeUnit.SECONDS.toMillis(recipient.expiresInSeconds.toLong()),
|
||||
isViewOnce,
|
||||
ThreadDatabase.DistributionTypes.DEFAULT,
|
||||
isStory,
|
||||
storyType,
|
||||
null,
|
||||
null,
|
||||
emptyList(),
|
||||
|
||||
@@ -50,6 +50,7 @@ import org.thoughtcrime.securesms.database.model.ParentStoryId;
|
||||
import org.thoughtcrime.securesms.database.model.PendingRetryReceiptModel;
|
||||
import org.thoughtcrime.securesms.database.model.ReactionRecord;
|
||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
||||
import org.thoughtcrime.securesms.database.model.StoryType;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.BadGroupIdException;
|
||||
@@ -830,7 +831,7 @@ public final class MessageContentProcessor {
|
||||
content.getTimestamp(),
|
||||
content.getServerReceivedTimestamp(),
|
||||
receivedTime,
|
||||
false,
|
||||
StoryType.NONE,
|
||||
null,
|
||||
-1,
|
||||
expiresInSeconds * 1000L,
|
||||
@@ -1324,11 +1325,18 @@ public final class MessageContentProcessor {
|
||||
database.beginTransaction();
|
||||
|
||||
try {
|
||||
final StoryType storyType;
|
||||
if (message.getAllowsReplies().or(false)) {
|
||||
storyType = StoryType.STORY_WITH_REPLIES;
|
||||
} else {
|
||||
storyType = StoryType.STORY_WITHOUT_REPLIES;
|
||||
}
|
||||
|
||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(senderRecipient.getId(),
|
||||
content.getTimestamp(),
|
||||
content.getServerReceivedTimestamp(),
|
||||
System.currentTimeMillis(),
|
||||
true,
|
||||
storyType,
|
||||
null,
|
||||
-1,
|
||||
0,
|
||||
@@ -1388,6 +1396,11 @@ public final class MessageContentProcessor {
|
||||
} else {
|
||||
MmsMessageRecord story = (MmsMessageRecord) database.getMessageRecord(storyMessageId.getId());
|
||||
|
||||
if (!story.getStoryType().isStoryWithReplies()) {
|
||||
warn(content.getTimestamp(), "Story has replies disabled. Dropping reply.");
|
||||
return;
|
||||
}
|
||||
|
||||
parentStoryId = new ParentStoryId.DirectReply(storyMessageId.getId());
|
||||
quoteModel = new QuoteModel(storyContext.getSentTimestamp(), storyAuthorRecipient, "", false, story.getSlideDeck().asAttachments(), Collections.emptyList());
|
||||
}
|
||||
@@ -1400,7 +1413,7 @@ public final class MessageContentProcessor {
|
||||
content.getTimestamp(),
|
||||
content.getServerReceivedTimestamp(),
|
||||
System.currentTimeMillis(),
|
||||
false,
|
||||
StoryType.NONE,
|
||||
parentStoryId,
|
||||
-1,
|
||||
0,
|
||||
@@ -1457,7 +1470,7 @@ public final class MessageContentProcessor {
|
||||
message.getTimestamp(),
|
||||
content.getServerReceivedTimestamp(),
|
||||
receivedTime,
|
||||
false,
|
||||
StoryType.NONE,
|
||||
null,
|
||||
-1,
|
||||
TimeUnit.SECONDS.toMillis(message.getExpiresInSeconds()),
|
||||
@@ -1563,7 +1576,7 @@ public final class MessageContentProcessor {
|
||||
TimeUnit.SECONDS.toMillis(message.getMessage().getExpiresInSeconds()),
|
||||
viewOnce,
|
||||
ThreadDatabase.DistributionTypes.DEFAULT,
|
||||
false,
|
||||
StoryType.NONE,
|
||||
null,
|
||||
quote.orNull(),
|
||||
sharedContacts.or(Collections.emptyList()),
|
||||
@@ -1757,7 +1770,7 @@ public final class MessageContentProcessor {
|
||||
expiresInMillis,
|
||||
false,
|
||||
ThreadDatabase.DistributionTypes.DEFAULT,
|
||||
false,
|
||||
StoryType.NONE,
|
||||
null,
|
||||
null,
|
||||
Collections.emptyList(),
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.thoughtcrime.securesms.attachments.PointerAttachment
|
||||
import org.thoughtcrime.securesms.contactshare.Contact
|
||||
import org.thoughtcrime.securesms.database.model.Mention
|
||||
import org.thoughtcrime.securesms.database.model.ParentStoryId
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||
@@ -19,7 +20,7 @@ class IncomingMediaMessage(
|
||||
val groupId: GroupId? = null,
|
||||
val body: String? = null,
|
||||
val isPushMessage: Boolean = false,
|
||||
val isStory: Boolean = false,
|
||||
val storyType: StoryType = StoryType.NONE,
|
||||
val parentStoryId: ParentStoryId? = null,
|
||||
val sentTimeMillis: Long,
|
||||
val serverTimeMillis: Long,
|
||||
@@ -83,7 +84,7 @@ class IncomingMediaMessage(
|
||||
sentTimeMillis: Long,
|
||||
serverTimeMillis: Long,
|
||||
receivedTimeMillis: Long,
|
||||
isStory: Boolean,
|
||||
storyType: StoryType,
|
||||
parentStoryId: ParentStoryId?,
|
||||
subscriptionId: Int,
|
||||
expiresIn: Long,
|
||||
@@ -104,7 +105,7 @@ class IncomingMediaMessage(
|
||||
groupId = if (group.isPresent) GroupUtil.idFromGroupContextOrThrow(group.get()) else null,
|
||||
body = body.orNull(),
|
||||
isPushMessage = true,
|
||||
isStory = isStory,
|
||||
storyType = storyType,
|
||||
parentStoryId = parentStoryId,
|
||||
sentTimeMillis = sentTimeMillis,
|
||||
serverTimeMillis = serverTimeMillis,
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.mms;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.StoryType;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -12,12 +13,12 @@ public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage
|
||||
public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn) {
|
||||
super(recipient,
|
||||
"",
|
||||
new LinkedList<Attachment>(),
|
||||
new LinkedList<>(),
|
||||
sentTimeMillis,
|
||||
ThreadDatabase.DistributionTypes.CONVERSATION,
|
||||
expiresIn,
|
||||
false,
|
||||
false,
|
||||
StoryType.NONE,
|
||||
null,
|
||||
null,
|
||||
Collections.emptyList(),
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.contactshare.Contact;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.Mention;
|
||||
import org.thoughtcrime.securesms.database.model.StoryType;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -38,7 +39,7 @@ public final class OutgoingGroupUpdateMessage extends OutgoingSecureMediaMessage
|
||||
ThreadDatabase.DistributionTypes.CONVERSATION,
|
||||
expiresIn,
|
||||
viewOnce,
|
||||
false,
|
||||
StoryType.NONE,
|
||||
null,
|
||||
quote,
|
||||
contacts,
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
import org.thoughtcrime.securesms.database.model.Mention;
|
||||
import org.thoughtcrime.securesms.database.model.ParentStoryId;
|
||||
import org.thoughtcrime.securesms.database.model.StoryType;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
@@ -30,7 +31,7 @@ public class OutgoingMediaMessage {
|
||||
private final long expiresIn;
|
||||
private final boolean viewOnce;
|
||||
private final QuoteModel outgoingQuote;
|
||||
private final boolean isStory;
|
||||
private final StoryType storyType;
|
||||
private final ParentStoryId parentStoryId;
|
||||
|
||||
private final Set<NetworkFailure> networkFailures = new HashSet<>();
|
||||
@@ -47,7 +48,7 @@ public class OutgoingMediaMessage {
|
||||
long expiresIn,
|
||||
boolean viewOnce,
|
||||
int distributionType,
|
||||
boolean isStory,
|
||||
@NonNull StoryType storyType,
|
||||
@Nullable ParentStoryId parentStoryId,
|
||||
@Nullable QuoteModel outgoingQuote,
|
||||
@NonNull List<Contact> contacts,
|
||||
@@ -65,7 +66,7 @@ public class OutgoingMediaMessage {
|
||||
this.expiresIn = expiresIn;
|
||||
this.viewOnce = viewOnce;
|
||||
this.outgoingQuote = outgoingQuote;
|
||||
this.isStory = isStory;
|
||||
this.storyType = storyType;
|
||||
this.parentStoryId = parentStoryId;
|
||||
|
||||
this.contacts.addAll(contacts);
|
||||
@@ -83,7 +84,7 @@ public class OutgoingMediaMessage {
|
||||
long expiresIn,
|
||||
boolean viewOnce,
|
||||
int distributionType,
|
||||
boolean isStory,
|
||||
@NonNull StoryType storyType,
|
||||
@Nullable ParentStoryId parentStoryId,
|
||||
@Nullable QuoteModel outgoingQuote,
|
||||
@NonNull List<Contact> contacts,
|
||||
@@ -98,7 +99,7 @@ public class OutgoingMediaMessage {
|
||||
expiresIn,
|
||||
viewOnce,
|
||||
distributionType,
|
||||
isStory,
|
||||
storyType,
|
||||
parentStoryId,
|
||||
outgoingQuote,
|
||||
contacts,
|
||||
@@ -118,7 +119,7 @@ public class OutgoingMediaMessage {
|
||||
this.expiresIn = that.expiresIn;
|
||||
this.viewOnce = that.viewOnce;
|
||||
this.outgoingQuote = that.outgoingQuote;
|
||||
this.isStory = that.isStory;
|
||||
this.storyType = that.storyType;
|
||||
this.parentStoryId = that.parentStoryId;
|
||||
|
||||
this.identityKeyMismatches.addAll(that.identityKeyMismatches);
|
||||
@@ -138,7 +139,7 @@ public class OutgoingMediaMessage {
|
||||
expiresIn,
|
||||
viewOnce,
|
||||
distributionType,
|
||||
isStory,
|
||||
storyType,
|
||||
parentStoryId,
|
||||
outgoingQuote,
|
||||
contacts,
|
||||
@@ -193,8 +194,8 @@ public class OutgoingMediaMessage {
|
||||
return viewOnce;
|
||||
}
|
||||
|
||||
public boolean isStory() {
|
||||
return isStory;
|
||||
public @NonNull StoryType getStoryType() {
|
||||
return storyType;
|
||||
}
|
||||
|
||||
public @Nullable ParentStoryId getParentStoryId() {
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.contactshare.Contact;
|
||||
import org.thoughtcrime.securesms.database.model.Mention;
|
||||
import org.thoughtcrime.securesms.database.model.ParentStoryId;
|
||||
import org.thoughtcrime.securesms.database.model.StoryType;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
@@ -22,14 +23,14 @@ public class OutgoingSecureMediaMessage extends OutgoingMediaMessage {
|
||||
int distributionType,
|
||||
long expiresIn,
|
||||
boolean viewOnce,
|
||||
boolean isStory,
|
||||
@NonNull StoryType storyType,
|
||||
@Nullable ParentStoryId parentStoryId,
|
||||
@Nullable QuoteModel quote,
|
||||
@NonNull List<Contact> contacts,
|
||||
@NonNull List<LinkPreview> previews,
|
||||
@NonNull List<Mention> mentions)
|
||||
{
|
||||
super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, viewOnce, distributionType, isStory, parentStoryId, quote, contacts, previews, mentions, Collections.emptySet(), Collections.emptySet());
|
||||
super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, viewOnce, distributionType, storyType, parentStoryId, quote, contacts, previews, mentions, Collections.emptySet(), Collections.emptySet());
|
||||
}
|
||||
|
||||
public OutgoingSecureMediaMessage(OutgoingMediaMessage base) {
|
||||
@@ -50,7 +51,7 @@ public class OutgoingSecureMediaMessage extends OutgoingMediaMessage {
|
||||
getDistributionType(),
|
||||
expiresIn,
|
||||
isViewOnce(),
|
||||
isStory(),
|
||||
getStoryType(),
|
||||
getParentStoryId(),
|
||||
getOutgoingQuote(),
|
||||
getSharedContacts(),
|
||||
|
||||
@@ -28,6 +28,7 @@ import androidx.core.app.RemoteInput;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.thoughtcrime.securesms.database.MessageDatabase.MarkedMessageInfo;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.StoryType;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
import org.thoughtcrime.securesms.notifications.v2.MessageNotifierV2;
|
||||
@@ -86,7 +87,7 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
|
||||
expiresIn,
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
StoryType.NONE,
|
||||
null,
|
||||
null,
|
||||
Collections.emptyList(),
|
||||
|
||||
@@ -14,8 +14,10 @@ import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.TransportOption;
|
||||
import org.thoughtcrime.securesms.TransportOptions;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.Mention;
|
||||
import org.thoughtcrime.securesms.database.model.StoryType;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
@@ -30,6 +32,7 @@ import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||
import org.thoughtcrime.securesms.util.MessageUtil;
|
||||
import org.thoughtcrime.securesms.util.ParcelUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
|
||||
@@ -170,7 +173,16 @@ public final class MultiShareSender {
|
||||
|
||||
List<OutgoingMediaMessage> outgoingMessages = new ArrayList<>();
|
||||
|
||||
StoryType storyType = StoryType.NONE;
|
||||
if (isStory && recipient.isDistributionList()) {
|
||||
storyType = SignalDatabase.distributionLists().getStoryType(recipient.requireDistributionListId());
|
||||
}
|
||||
|
||||
if (isStory && slideDeck.getSlides().size() > 1) {
|
||||
if (storyType == StoryType.NONE) {
|
||||
storyType = StoryType.STORY_WITH_REPLIES;
|
||||
}
|
||||
|
||||
for (final Slide slide : slideDeck.getSlides()) {
|
||||
SlideDeck singletonDeck = new SlideDeck();
|
||||
singletonDeck.addSlide(slide);
|
||||
@@ -183,7 +195,7 @@ public final class MultiShareSender {
|
||||
expiresIn,
|
||||
isViewOnce,
|
||||
ThreadDatabase.DistributionTypes.DEFAULT,
|
||||
true,
|
||||
storyType,
|
||||
null,
|
||||
null,
|
||||
Collections.emptyList(),
|
||||
@@ -206,7 +218,7 @@ public final class MultiShareSender {
|
||||
expiresIn,
|
||||
isViewOnce,
|
||||
ThreadDatabase.DistributionTypes.DEFAULT,
|
||||
isStory,
|
||||
storyType,
|
||||
null,
|
||||
null,
|
||||
Collections.emptyList(),
|
||||
|
||||
@@ -26,4 +26,16 @@ class PrivateStorySettingsRepository {
|
||||
SignalDatabase.distributionLists.deleteList(distributionListId)
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun getRepliesAndReactionsEnabled(distributionListId: DistributionListId): Single<Boolean> {
|
||||
return Single.fromCallable {
|
||||
SignalDatabase.distributionLists.getStoryType(distributionListId).isStoryWithReplies
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun setRepliesAndReactionsEnabled(distributionListId: DistributionListId, repliesAndReactionsEnabled: Boolean): Completable {
|
||||
return Completable.fromAction {
|
||||
SignalDatabase.distributionLists.setAllowsReplies(distributionListId, repliesAndReactionsEnabled)
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,15 @@ class PrivateStorySettingsViewModel(private val distributionListId: Distribution
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
disposables.clear()
|
||||
disposables += repository.getRecord(distributionListId)
|
||||
.subscribe { record ->
|
||||
store.update { it.copy(privateStory = record) }
|
||||
}
|
||||
disposables += repository.getRepliesAndReactionsEnabled(distributionListId)
|
||||
.subscribe { repliesAndReactionsEnabled ->
|
||||
store.update { it.copy(areRepliesAndReactionsEnabled = repliesAndReactionsEnabled) }
|
||||
}
|
||||
}
|
||||
|
||||
fun getName(): String {
|
||||
@@ -41,7 +46,9 @@ class PrivateStorySettingsViewModel(private val distributionListId: Distribution
|
||||
}
|
||||
|
||||
fun setRepliesAndReactionsEnabled(repliesAndReactionsEnabled: Boolean) {
|
||||
// TODO [stories] impl
|
||||
disposables += repository.setRepliesAndReactionsEnabled(distributionListId, repliesAndReactionsEnabled)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { refresh() }
|
||||
}
|
||||
|
||||
fun delete(): Completable {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.thoughtcrime.securesms.stories.settings.my
|
||||
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
@@ -9,7 +10,19 @@ class MyStorySettingsRepository {
|
||||
|
||||
fun getHiddenRecipientCount(): Single<Int> {
|
||||
return Single.fromCallable {
|
||||
SignalDatabase.distributionLists.getRawMemberCount(DistributionListId.from(DistributionListId.MY_STORY_ID))
|
||||
SignalDatabase.distributionLists.getRawMemberCount(DistributionListId.MY_STORY)
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun getRepliesAndReactionsEnabled(): Single<Boolean> {
|
||||
return Single.fromCallable {
|
||||
SignalDatabase.distributionLists.getStoryType(DistributionListId.MY_STORY).isStoryWithReplies
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun setRepliesAndReactionsEnabled(repliesAndReactionsEnabled: Boolean): Completable {
|
||||
return Completable.fromAction {
|
||||
SignalDatabase.distributionLists.setAllowsReplies(DistributionListId.MY_STORY, repliesAndReactionsEnabled)
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.stories.settings.my
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
@@ -18,11 +19,17 @@ class MyStorySettingsViewModel(private val repository: MyStorySettingsRepository
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
disposables.clear()
|
||||
disposables += repository.getHiddenRecipientCount()
|
||||
.subscribe { count -> store.update { it.copy(hiddenStoryFromCount = count) } }
|
||||
disposables += repository.getRepliesAndReactionsEnabled()
|
||||
.subscribe { repliesAndReactionsEnabled -> store.update { it.copy(areRepliesAndReactionsEnabled = repliesAndReactionsEnabled) } }
|
||||
}
|
||||
|
||||
fun setRepliesAndReactionsEnabled(repliesAndReactionsEnabled: Boolean) {
|
||||
disposables += repository.setRepliesAndReactionsEnabled(repliesAndReactionsEnabled)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { refresh() }
|
||||
}
|
||||
|
||||
class Factory(private val repository: MyStorySettingsRepository) : ViewModelProvider.Factory {
|
||||
|
||||
@@ -16,5 +16,6 @@ class StoryPost(
|
||||
val replyCount: Int,
|
||||
val dateInMilliseconds: Long,
|
||||
val attachment: Attachment,
|
||||
val conversationMessage: ConversationMessage
|
||||
val conversationMessage: ConversationMessage,
|
||||
val allowsReplies: Boolean
|
||||
)
|
||||
|
||||
@@ -70,7 +70,8 @@ class StoryViewerPageRepository(context: Context) {
|
||||
replyCount = SignalDatabase.mms.getNumberOfStoryReplies(record.id),
|
||||
dateInMilliseconds = record.dateSent,
|
||||
attachment = (record as MmsMessageRecord).slideDeck.firstSlide!!.asAttachment(),
|
||||
conversationMessage = ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, record)
|
||||
conversationMessage = ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, record),
|
||||
allowsReplies = record.storyType.isStoryWithReplies
|
||||
)
|
||||
|
||||
emitter.onNext(story)
|
||||
|
||||
@@ -146,7 +146,11 @@ class StoryViewerPageViewModel(
|
||||
val isFromSelf = post.sender.isSelf
|
||||
val isToGroup = post.group != null
|
||||
|
||||
return StoryViewerPageState.ReplyState.resolve(isFromSelf, isToGroup)
|
||||
return when {
|
||||
post.allowsReplies -> StoryViewerPageState.ReplyState.resolve(isFromSelf, isToGroup)
|
||||
isFromSelf -> StoryViewerPageState.ReplyState.SELF
|
||||
else -> StoryViewerPageState.ReplyState.NONE
|
||||
}
|
||||
}
|
||||
|
||||
fun getPostAt(index: Int): StoryPost {
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.ParentStoryId
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
||||
import org.thoughtcrime.securesms.mms.QuoteModel
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
@@ -53,7 +54,7 @@ class StoryDirectReplyRepository {
|
||||
0L,
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
StoryType.NONE,
|
||||
ParentStoryId.DirectReply(storyId),
|
||||
QuoteModel(message.dateSent, quoteAuthor.id, "", false, message.slideDeck.asAttachments(), null),
|
||||
emptyList(),
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.Mention
|
||||
import org.thoughtcrime.securesms.database.model.ParentStoryId
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
||||
import org.thoughtcrime.securesms.sms.MessageSender
|
||||
|
||||
@@ -30,7 +31,7 @@ object StoryGroupReplySender {
|
||||
0L,
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
StoryType.NONE,
|
||||
ParentStoryId.GroupReply(message.id),
|
||||
null,
|
||||
emptyList(),
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.database.model.StoryViewState
|
||||
import org.thoughtcrime.securesms.testing.TestDatabaseUtil
|
||||
|
||||
@@ -80,35 +81,35 @@ class MmsDatabaseTest {
|
||||
|
||||
@Test
|
||||
fun `Given stories in database not in thread 1, when I getStoryViewState for thread 1, then I expect NONE`() {
|
||||
TestMms.insert(db, threadId = 2, isStory = true)
|
||||
TestMms.insert(db, threadId = 2, isStory = true)
|
||||
TestMms.insert(db, threadId = 2, storyType = StoryType.STORY_WITH_REPLIES)
|
||||
TestMms.insert(db, threadId = 2, storyType = StoryType.STORY_WITH_REPLIES)
|
||||
assertEquals(StoryViewState.NONE, mmsDatabase.getStoryViewState(1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given viewed incoming stories in database, when I getStoryViewState, then I expect VIEWED`() {
|
||||
TestMms.insert(db, threadId = 1, isStory = true, viewed = true)
|
||||
TestMms.insert(db, threadId = 1, isStory = true, viewed = true)
|
||||
TestMms.insert(db, threadId = 1, storyType = StoryType.STORY_WITH_REPLIES, viewed = true)
|
||||
TestMms.insert(db, threadId = 1, storyType = StoryType.STORY_WITH_REPLIES, viewed = true)
|
||||
assertEquals(StoryViewState.VIEWED, mmsDatabase.getStoryViewState(1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given unviewed incoming stories in database, when I getStoryViewState, then I expect UNVIEWED`() {
|
||||
TestMms.insert(db, threadId = 1, isStory = true, viewed = false)
|
||||
TestMms.insert(db, threadId = 1, isStory = true, viewed = false)
|
||||
TestMms.insert(db, threadId = 1, storyType = StoryType.STORY_WITH_REPLIES, viewed = false)
|
||||
TestMms.insert(db, threadId = 1, storyType = StoryType.STORY_WITH_REPLIES, viewed = false)
|
||||
assertEquals(StoryViewState.UNVIEWED, mmsDatabase.getStoryViewState(1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given mix of viewed and unviewed incoming stories in database, when I getStoryViewState, then I expect UNVIEWED`() {
|
||||
TestMms.insert(db, threadId = 1, isStory = true, viewed = true)
|
||||
TestMms.insert(db, threadId = 1, isStory = true, viewed = false)
|
||||
TestMms.insert(db, threadId = 1, storyType = StoryType.STORY_WITH_REPLIES, viewed = true)
|
||||
TestMms.insert(db, threadId = 1, storyType = StoryType.STORY_WITH_REPLIES, viewed = false)
|
||||
assertEquals(StoryViewState.UNVIEWED, mmsDatabase.getStoryViewState(1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given only outgoing story in database, when I getStoryViewState, then I expect VIEWED`() {
|
||||
TestMms.insert(db, threadId = 1, isStory = true, type = Types.BASE_OUTBOX_TYPE)
|
||||
TestMms.insert(db, threadId = 1, storyType = StoryType.STORY_WITH_REPLIES, type = Types.BASE_OUTBOX_TYPE)
|
||||
assertEquals(StoryViewState.VIEWED, mmsDatabase.getStoryViewState(1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.database
|
||||
import android.content.ContentValues
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import com.google.android.mms.pdu_alt.PduHeaders
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
||||
@@ -25,7 +26,7 @@ object TestMms {
|
||||
unread: Boolean = false,
|
||||
viewed: Boolean = false,
|
||||
threadId: Long = 1,
|
||||
isStory: Boolean = false
|
||||
storyType: StoryType = StoryType.NONE
|
||||
): Long {
|
||||
val message = OutgoingMediaMessage(
|
||||
recipient,
|
||||
@@ -36,7 +37,7 @@ object TestMms {
|
||||
expiresIn,
|
||||
viewOnce,
|
||||
distributionType,
|
||||
isStory,
|
||||
storyType,
|
||||
null,
|
||||
null,
|
||||
emptyList(),
|
||||
@@ -83,7 +84,7 @@ object TestMms {
|
||||
put(MmsSmsColumns.DELIVERY_RECEIPT_COUNT, 0)
|
||||
put(MmsSmsColumns.RECEIPT_TIMESTAMP, 0)
|
||||
put(MmsSmsColumns.VIEWED_RECEIPT_COUNT, if (viewed) 1 else 0)
|
||||
put(MmsDatabase.IS_STORY, if (message.isStory) 1 else 0)
|
||||
put(MmsDatabase.STORY_TYPE, message.storyType.code)
|
||||
|
||||
put(MmsSmsColumns.BODY, body)
|
||||
put(MmsDatabase.PART_COUNT, 0)
|
||||
|
||||
@@ -770,6 +770,8 @@ public class SignalServiceMessageSender {
|
||||
builder.setTextAttachment(createTextAttachment(message.getTextAttachment().get()));
|
||||
}
|
||||
|
||||
builder.setAllowsReplies(message.getAllowsReplies().or(true));
|
||||
|
||||
return container.setStoryMessage(builder).build();
|
||||
}
|
||||
|
||||
|
||||
@@ -958,11 +958,13 @@ public final class SignalServiceContent {
|
||||
if (content.hasFileAttachment()) {
|
||||
return SignalServiceStoryMessage.forFileAttachment(profileKey,
|
||||
createGroupV2Info(content),
|
||||
createAttachmentPointer(content.getFileAttachment()));
|
||||
createAttachmentPointer(content.getFileAttachment()),
|
||||
content.getAllowsReplies());
|
||||
} else {
|
||||
return SignalServiceStoryMessage.forTextAttachment(profileKey,
|
||||
createGroupV2Info(content),
|
||||
createTextAttachment(content.getTextAttachment()));
|
||||
createTextAttachment(content.getTextAttachment()),
|
||||
content.getAllowsReplies());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,27 +7,32 @@ public class SignalServiceStoryMessage {
|
||||
private final Optional<SignalServiceGroupV2> groupContext;
|
||||
private final Optional<SignalServiceAttachment> fileAttachment;
|
||||
private final Optional<SignalServiceTextAttachment> textAttachment;
|
||||
private final Optional<Boolean> allowsReplies;
|
||||
|
||||
private SignalServiceStoryMessage(byte[] profileKey,
|
||||
SignalServiceGroupV2 groupContext,
|
||||
SignalServiceAttachment fileAttachment,
|
||||
SignalServiceTextAttachment textAttachment) {
|
||||
SignalServiceTextAttachment textAttachment,
|
||||
boolean allowsReplies) {
|
||||
this.profileKey = Optional.fromNullable(profileKey);
|
||||
this.groupContext = Optional.fromNullable(groupContext);
|
||||
this.fileAttachment = Optional.fromNullable(fileAttachment);
|
||||
this.textAttachment = Optional.fromNullable(textAttachment);
|
||||
this.allowsReplies = Optional.of(allowsReplies);
|
||||
}
|
||||
|
||||
public static SignalServiceStoryMessage forFileAttachment(byte[] profileKey,
|
||||
SignalServiceGroupV2 groupContext,
|
||||
SignalServiceAttachment fileAttachment) {
|
||||
return new SignalServiceStoryMessage(profileKey, groupContext, fileAttachment, null);
|
||||
SignalServiceAttachment fileAttachment,
|
||||
boolean allowsReplies) {
|
||||
return new SignalServiceStoryMessage(profileKey, groupContext, fileAttachment, null, allowsReplies);
|
||||
}
|
||||
|
||||
public static SignalServiceStoryMessage forTextAttachment(byte[] profileKey,
|
||||
SignalServiceGroupV2 groupContext,
|
||||
SignalServiceTextAttachment textAttachment) {
|
||||
return new SignalServiceStoryMessage(profileKey, groupContext, null, textAttachment);
|
||||
SignalServiceTextAttachment textAttachment,
|
||||
boolean allowsReplies) {
|
||||
return new SignalServiceStoryMessage(profileKey, groupContext, null, textAttachment, allowsReplies);
|
||||
}
|
||||
|
||||
public Optional<byte[]> getProfileKey() {
|
||||
@@ -45,4 +50,8 @@ public class SignalServiceStoryMessage {
|
||||
public Optional<SignalServiceTextAttachment> getTextAttachment() {
|
||||
return textAttachment;
|
||||
}
|
||||
|
||||
public Optional<Boolean> getAllowsReplies() {
|
||||
return allowsReplies;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,12 +350,13 @@ message TypingMessage {
|
||||
}
|
||||
|
||||
message StoryMessage {
|
||||
optional bytes profileKey = 1;
|
||||
optional GroupContextV2 group = 2;
|
||||
optional bytes profileKey = 1;
|
||||
optional GroupContextV2 group = 2;
|
||||
oneof attachment {
|
||||
AttachmentPointer fileAttachment = 3;
|
||||
TextAttachment textAttachment = 4;
|
||||
}
|
||||
optional bool allowsReplies = 5;
|
||||
}
|
||||
|
||||
message Preview {
|
||||
|
||||
Reference in New Issue
Block a user