Add support for separate story view receipt control.
This reverts commit 1046265d23
.
This commit is contained in:
parent
2f2711c9a3
commit
ca36eaacce
23 changed files with 332 additions and 48 deletions
|
@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
|
|||
import org.thoughtcrime.securesms.database.model.ParentStoryId;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.StoryResult;
|
||||
import org.thoughtcrime.securesms.database.model.StoryType;
|
||||
import org.thoughtcrime.securesms.database.model.StoryViewState;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExportState;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent;
|
||||
|
@ -135,7 +136,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns,
|
|||
public abstract void markGiftRedemptionStarted(long messageId);
|
||||
public abstract void markGiftRedemptionFailed(long messageId);
|
||||
|
||||
public abstract Set<MessageUpdate> incrementReceiptCount(SyncMessageId messageId, long timestamp, @NonNull ReceiptType receiptType, boolean storiesOnly);
|
||||
public abstract Set<MessageUpdate> incrementReceiptCount(SyncMessageId messageId, long timestamp, @NonNull ReceiptType receiptType, @NonNull MessageQualifier messageType);
|
||||
|
||||
public abstract List<MarkedMessageInfo> setEntireThreadRead(long threadId);
|
||||
public abstract List<MarkedMessageInfo> setMessagesReadSince(long threadId, long timestamp);
|
||||
|
@ -218,6 +219,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns,
|
|||
public abstract void deleteGroupStoryReplies(long parentStoryId);
|
||||
public abstract boolean isOutgoingStoryAlreadyInDatabase(@NonNull RecipientId recipientId, long sentTimestamp);
|
||||
public abstract @NonNull List<MarkedMessageInfo> setGroupStoryMessagesReadSince(long threadId, long groupStoryId, long sinceTimestamp);
|
||||
public abstract @NonNull List<StoryType> getStoryTypes(@NonNull List<MessageId> messageIds);
|
||||
|
||||
public abstract @NonNull StoryViewState getStoryViewState(@NonNull RecipientId recipientId);
|
||||
public abstract void updateViewedStories(@NonNull Set<SyncMessageId> syncMessageIds);
|
||||
|
@ -920,4 +922,23 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns,
|
|||
return dateReceived;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes which messages to act on. This is used when incrementing receipts.
|
||||
* Specifically, this was added to support stories having separate viewed receipt settings.
|
||||
*/
|
||||
public enum MessageQualifier {
|
||||
/**
|
||||
* A normal database message (i.e. not a story)
|
||||
*/
|
||||
NORMAL,
|
||||
/**
|
||||
* A story message
|
||||
*/
|
||||
STORY,
|
||||
/**
|
||||
* Both normal and story message
|
||||
*/
|
||||
ALL
|
||||
}
|
||||
}
|
||||
|
|
|
@ -379,7 +379,7 @@ public class MmsDatabase extends MessageDatabase {
|
|||
@Override
|
||||
public @NonNull List<MarkedMessageInfo> getViewedIncomingMessages(long threadId) {
|
||||
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
|
||||
String[] columns = new String[]{ID, RECIPIENT_ID, DATE_SENT, MESSAGE_BOX, THREAD_ID};
|
||||
String[] columns = new String[]{ID, RECIPIENT_ID, DATE_SENT, MESSAGE_BOX, THREAD_ID, STORY_TYPE};
|
||||
String where = THREAD_ID + " = ? AND " + VIEWED_RECEIPT_COUNT + " > 0 AND " + MESSAGE_BOX + " & " + Types.BASE_INBOX_TYPE + " = " + Types.BASE_INBOX_TYPE;
|
||||
String[] args = SqlUtil.buildArgs(threadId);
|
||||
|
||||
|
@ -395,6 +395,7 @@ public class MmsDatabase extends MessageDatabase {
|
|||
RecipientId recipientId = RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID));
|
||||
long dateSent = CursorUtil.requireLong(cursor, DATE_SENT);
|
||||
SyncMessageId syncMessageId = new SyncMessageId(recipientId, dateSent);
|
||||
StoryType storyType = StoryType.fromCode(CursorUtil.requireInt(cursor, STORY_TYPE));
|
||||
|
||||
results.add(new MarkedMessageInfo(threadId, syncMessageId, new MessageId(messageId, true), null));
|
||||
}
|
||||
|
@ -421,7 +422,7 @@ public class MmsDatabase extends MessageDatabase {
|
|||
}
|
||||
|
||||
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
|
||||
String[] columns = new String[]{ID, RECIPIENT_ID, DATE_SENT, MESSAGE_BOX, THREAD_ID};
|
||||
String[] columns = new String[]{ID, RECIPIENT_ID, DATE_SENT, MESSAGE_BOX, THREAD_ID, STORY_TYPE};
|
||||
String where = ID + " IN (" + Util.join(messageIds, ",") + ") AND " + VIEWED_RECEIPT_COUNT + " = 0";
|
||||
List<MarkedMessageInfo> results = new LinkedList<>();
|
||||
|
||||
|
@ -435,6 +436,7 @@ public class MmsDatabase extends MessageDatabase {
|
|||
RecipientId recipientId = RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID));
|
||||
long dateSent = CursorUtil.requireLong(cursor, DATE_SENT);
|
||||
SyncMessageId syncMessageId = new SyncMessageId(recipientId, dateSent);
|
||||
StoryType storyType = StoryType.fromCode(CursorUtil.requireInt(cursor, STORY_TYPE));
|
||||
|
||||
results.add(new MarkedMessageInfo(threadId, syncMessageId, new MessageId(messageId, true), null));
|
||||
|
||||
|
@ -463,7 +465,7 @@ public class MmsDatabase extends MessageDatabase {
|
|||
@Override
|
||||
public @NonNull List<MarkedMessageInfo>
|
||||
setOutgoingGiftsRevealed(@NonNull List<Long> messageIds) {
|
||||
String[] projection = SqlUtil.buildArgs(ID, RECIPIENT_ID, DATE_SENT, THREAD_ID);
|
||||
String[] projection = SqlUtil.buildArgs(ID, RECIPIENT_ID, DATE_SENT, THREAD_ID, STORY_TYPE);
|
||||
String where = ID + " IN (" + Util.join(messageIds, ",") + ") AND (" + getOutgoingTypeClause() + ") AND (" + getTypeField() + " & " + Types.SPECIAL_TYPES_MASK + " = " + Types.SPECIAL_TYPE_GIFT_BADGE + ") AND " + VIEWED_RECEIPT_COUNT + " = 0";
|
||||
List<MarkedMessageInfo> results = new LinkedList<>();
|
||||
|
||||
|
@ -475,6 +477,7 @@ public class MmsDatabase extends MessageDatabase {
|
|||
RecipientId recipientId = RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID));
|
||||
long dateSent = CursorUtil.requireLong(cursor, DATE_SENT);
|
||||
SyncMessageId syncMessageId = new SyncMessageId(recipientId, dateSent);
|
||||
StoryType storyType = StoryType.fromCode(CursorUtil.requireInt(cursor, STORY_TYPE));
|
||||
|
||||
results.add(new MarkedMessageInfo(threadId, syncMessageId, new MessageId(messageId, true), null));
|
||||
|
||||
|
@ -1122,13 +1125,28 @@ public class MmsDatabase extends MessageDatabase {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Set<MessageUpdate> incrementReceiptCount(SyncMessageId messageId, long timestamp, @NonNull ReceiptType receiptType, boolean storiesOnly) {
|
||||
public Set<MessageUpdate> incrementReceiptCount(SyncMessageId messageId, long timestamp, @NonNull ReceiptType receiptType, MessageQualifier messageQualifier) {
|
||||
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
|
||||
Set<MessageUpdate> messageUpdates = new HashSet<>();
|
||||
|
||||
final String qualifierWhere;
|
||||
switch (messageQualifier) {
|
||||
case NORMAL:
|
||||
qualifierWhere = " AND NOT (" + IS_STORY_CLAUSE + ")";
|
||||
break;
|
||||
case STORY:
|
||||
qualifierWhere = " AND " + IS_STORY_CLAUSE;
|
||||
break;
|
||||
case ALL:
|
||||
qualifierWhere = "";
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported qualifier: " + messageQualifier);
|
||||
}
|
||||
|
||||
try (Cursor cursor = SQLiteDatabaseExtensionsKt.select(database, ID, THREAD_ID, MESSAGE_BOX, RECIPIENT_ID, receiptType.getColumnName(), RECEIPT_TIMESTAMP)
|
||||
.from(TABLE_NAME)
|
||||
.where(DATE_SENT + " = ?" + (storiesOnly ? " AND " + IS_STORY_CLAUSE : ""), messageId.getTimetamp())
|
||||
.where(DATE_SENT + " = ?" + qualifierWhere, messageId.getTimetamp())
|
||||
.run())
|
||||
{
|
||||
while (cursor.moveToNext()) {
|
||||
|
@ -1509,6 +1527,38 @@ public class MmsDatabase extends MessageDatabase {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<StoryType> getStoryTypes(@NonNull List<MessageId> messageIds) {
|
||||
List<Long> mmsMessages = messageIds.stream()
|
||||
.filter(MessageId::isMms)
|
||||
.map(MessageId::getId)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
|
||||
if (mmsMessages.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String[] projection = SqlUtil.buildArgs(ID, STORY_TYPE);
|
||||
List<SqlUtil.Query> queries = SqlUtil.buildCollectionQuery(ID, mmsMessages);
|
||||
HashMap<Long, StoryType> storyTypes = new HashMap<>();
|
||||
|
||||
for (final SqlUtil.Query query : queries) {
|
||||
try (Cursor cursor = getWritableDatabase().query(TABLE_NAME, projection, query.getWhere(), query.getWhereArgs(), null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
storyTypes.put(CursorUtil.requireLong(cursor, ID), StoryType.fromCode(CursorUtil.requireInt(cursor, STORY_TYPE)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return messageIds.stream().map(id -> {
|
||||
if (id.isMms() && storyTypes.containsKey(id.getId())) {
|
||||
return storyTypes.get(id.getId());
|
||||
} else {
|
||||
return StoryType.NONE;
|
||||
}
|
||||
}).collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MarkedMessageInfo> setEntireThreadRead(long threadId) {
|
||||
return setMessagesRead(THREAD_ID + " = ? AND " + STORY_TYPE + " = 0 AND " + PARENT_STORY_ID + " <= 0", new String[] { String.valueOf(threadId)});
|
||||
|
@ -1528,7 +1578,7 @@ public class MmsDatabase extends MessageDatabase {
|
|||
database.beginTransaction();
|
||||
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, new String[] {ID, RECIPIENT_ID, DATE_SENT, MESSAGE_BOX, EXPIRES_IN, EXPIRE_STARTED, THREAD_ID }, where, arguments, null, null, null);
|
||||
cursor = database.query(TABLE_NAME, new String[] {ID, RECIPIENT_ID, DATE_SENT, MESSAGE_BOX, EXPIRES_IN, EXPIRE_STARTED, THREAD_ID, STORY_TYPE }, where, arguments, null, null, null);
|
||||
|
||||
while(cursor != null && cursor.moveToNext()) {
|
||||
if (Types.isSecureType(CursorUtil.requireLong(cursor, MESSAGE_BOX))) {
|
||||
|
@ -1540,6 +1590,7 @@ public class MmsDatabase extends MessageDatabase {
|
|||
long expireStarted = CursorUtil.requireLong(cursor, EXPIRE_STARTED);
|
||||
SyncMessageId syncMessageId = new SyncMessageId(recipientId, dateSent);
|
||||
ExpirationInfo expirationInfo = new ExpirationInfo(messageId, expiresIn, expireStarted, true);
|
||||
StoryType storyType = StoryType.fromCode(CursorUtil.requireInt(cursor, STORY_TYPE));
|
||||
|
||||
if (!recipientId.equals(releaseChannelId)) {
|
||||
result.add(new MarkedMessageInfo(threadId, syncMessageId, new MessageId(messageId, true), expirationInfo));
|
||||
|
|
|
@ -493,6 +493,10 @@ public class MmsSmsDatabase extends Database {
|
|||
return incrementReceiptCounts(syncMessageIds, timestamp, MessageDatabase.ReceiptType.VIEWED);
|
||||
}
|
||||
|
||||
public @NonNull Collection<SyncMessageId> incrementViewedNonStoryReceiptCounts(@NonNull List<SyncMessageId> syncMessageIds, long timestamp) {
|
||||
return incrementReceiptCounts(syncMessageIds, timestamp, MessageDatabase.ReceiptType.VIEWED, MessageDatabase.MessageQualifier.NORMAL);
|
||||
}
|
||||
|
||||
public boolean incrementViewedReceiptCount(SyncMessageId syncMessageId, long timestamp) {
|
||||
return incrementReceiptCount(syncMessageId, timestamp, MessageDatabase.ReceiptType.VIEWED);
|
||||
}
|
||||
|
@ -505,7 +509,7 @@ public class MmsSmsDatabase extends Database {
|
|||
db.beginTransaction();
|
||||
try {
|
||||
for (SyncMessageId id : syncMessageIds) {
|
||||
Set<MessageUpdate> updates = incrementStoryReceiptCountInternal(id, timestamp, MessageDatabase.ReceiptType.VIEWED);
|
||||
Set<MessageUpdate> updates = incrementReceiptCountInternal(id, timestamp, MessageDatabase.ReceiptType.VIEWED, MessageDatabase.MessageQualifier.STORY);
|
||||
|
||||
if (updates.size() > 0) {
|
||||
messageUpdates.addAll(updates);
|
||||
|
@ -537,13 +541,17 @@ public class MmsSmsDatabase extends Database {
|
|||
* @return Whether or not some thread was updated.
|
||||
*/
|
||||
private boolean incrementReceiptCount(SyncMessageId syncMessageId, long timestamp, @NonNull MessageDatabase.ReceiptType receiptType) {
|
||||
return incrementReceiptCount(syncMessageId, timestamp, receiptType, MessageDatabase.MessageQualifier.ALL);
|
||||
}
|
||||
|
||||
private boolean incrementReceiptCount(SyncMessageId syncMessageId, long timestamp, @NonNull MessageDatabase.ReceiptType receiptType, @NonNull MessageDatabase.MessageQualifier messageQualifier) {
|
||||
SQLiteDatabase db = databaseHelper.getSignalWritableDatabase();
|
||||
ThreadDatabase threadDatabase = SignalDatabase.threads();
|
||||
Set<MessageUpdate> messageUpdates = new HashSet<>();
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
messageUpdates = incrementReceiptCountInternal(syncMessageId, timestamp, receiptType);
|
||||
messageUpdates = incrementReceiptCountInternal(syncMessageId, timestamp, receiptType, messageQualifier);
|
||||
|
||||
for (MessageUpdate messageUpdate : messageUpdates) {
|
||||
threadDatabase.update(messageUpdate.getThreadId(), false);
|
||||
|
@ -567,6 +575,10 @@ public class MmsSmsDatabase extends Database {
|
|||
* @return All of the messages that didn't result in updates.
|
||||
*/
|
||||
private @NonNull Collection<SyncMessageId> incrementReceiptCounts(@NonNull List<SyncMessageId> syncMessageIds, long timestamp, @NonNull MessageDatabase.ReceiptType receiptType) {
|
||||
return incrementReceiptCounts(syncMessageIds, timestamp, receiptType, MessageDatabase.MessageQualifier.ALL);
|
||||
}
|
||||
|
||||
private @NonNull Collection<SyncMessageId> incrementReceiptCounts(@NonNull List<SyncMessageId> syncMessageIds, long timestamp, @NonNull MessageDatabase.ReceiptType receiptType, @NonNull MessageDatabase.MessageQualifier messageQualifier) {
|
||||
SQLiteDatabase db = databaseHelper.getSignalWritableDatabase();
|
||||
ThreadDatabase threadDatabase = SignalDatabase.threads();
|
||||
Set<MessageUpdate> messageUpdates = new HashSet<>();
|
||||
|
@ -575,7 +587,7 @@ public class MmsSmsDatabase extends Database {
|
|||
db.beginTransaction();
|
||||
try {
|
||||
for (SyncMessageId id : syncMessageIds) {
|
||||
Set<MessageUpdate> updates = incrementReceiptCountInternal(id, timestamp, receiptType);
|
||||
Set<MessageUpdate> updates = incrementReceiptCountInternal(id, timestamp, receiptType, messageQualifier);
|
||||
|
||||
if (updates.size() > 0) {
|
||||
messageUpdates.addAll(updates);
|
||||
|
@ -609,22 +621,15 @@ public class MmsSmsDatabase extends Database {
|
|||
/**
|
||||
* Doesn't do any transactions or updates, so we can re-use the method safely.
|
||||
*/
|
||||
private @NonNull Set<MessageUpdate> incrementReceiptCountInternal(SyncMessageId syncMessageId, long timestamp, MessageDatabase.ReceiptType receiptType) {
|
||||
private @NonNull Set<MessageUpdate> incrementReceiptCountInternal(SyncMessageId syncMessageId, long timestamp, MessageDatabase.ReceiptType receiptType, @NonNull MessageDatabase.MessageQualifier messageQualifier) {
|
||||
Set<MessageUpdate> messageUpdates = new HashSet<>();
|
||||
|
||||
messageUpdates.addAll(SignalDatabase.sms().incrementReceiptCount(syncMessageId, timestamp, receiptType, false));
|
||||
messageUpdates.addAll(SignalDatabase.mms().incrementReceiptCount(syncMessageId, timestamp, receiptType, false));
|
||||
messageUpdates.addAll(SignalDatabase.sms().incrementReceiptCount(syncMessageId, timestamp, receiptType, messageQualifier));
|
||||
messageUpdates.addAll(SignalDatabase.mms().incrementReceiptCount(syncMessageId, timestamp, receiptType, messageQualifier));
|
||||
|
||||
return messageUpdates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Doesn't do any transactions or updates, so we can re-use the method safely.
|
||||
*/
|
||||
private @NonNull Set<MessageUpdate> incrementStoryReceiptCountInternal(@NonNull SyncMessageId syncMessageId, long timestamp, @NonNull MessageDatabase.ReceiptType receiptType) {
|
||||
return SignalDatabase.mms().incrementReceiptCount(syncMessageId, timestamp, receiptType, true);
|
||||
}
|
||||
|
||||
public void updateViewedStories(@NonNull Set<SyncMessageId> syncMessageIds) {
|
||||
SignalDatabase.mms().updateViewedStories(syncMessageIds);
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
|
|||
import org.thoughtcrime.securesms.database.model.ParentStoryId;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.StoryResult;
|
||||
import org.thoughtcrime.securesms.database.model.StoryType;
|
||||
import org.thoughtcrime.securesms.database.model.StoryViewState;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GroupCallUpdateDetails;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExportState;
|
||||
|
@ -515,7 +516,11 @@ public class SmsDatabase extends MessageDatabase {
|
|||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Set<MessageUpdate> incrementReceiptCount(SyncMessageId messageId, long timestamp, @NonNull ReceiptType receiptType, boolean storiesOnly) {
|
||||
public @NonNull Set<MessageUpdate> incrementReceiptCount(SyncMessageId messageId, long timestamp, @NonNull ReceiptType receiptType, @NonNull MessageQualifier messageQualifier) {
|
||||
if (messageQualifier == MessageQualifier.STORY) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
if (receiptType == ReceiptType.VIEWED) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
@ -1567,6 +1572,11 @@ public class SmsDatabase extends MessageDatabase {
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<StoryType> getStoryTypes(@NonNull List<MessageId> messageIds) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteGroupStoryReplies(long parentStoryId) {
|
||||
throw new UnsupportedOperationException();
|
||||
|
|
|
@ -120,6 +120,18 @@ public class Data {
|
|||
return integerArrays.get(key);
|
||||
}
|
||||
|
||||
public List<Integer> getIntegerArrayAsList(@NonNull String key) {
|
||||
throwIfAbsent(integerArrays, key);
|
||||
|
||||
int[] array = Objects.requireNonNull(integerArrays.get(key));
|
||||
List<Integer> ints = new ArrayList<>(array.length);
|
||||
|
||||
for (int l : array) {
|
||||
ints.add(l);
|
||||
}
|
||||
|
||||
return ints;
|
||||
}
|
||||
|
||||
public boolean hasLong(@NonNull String key) {
|
||||
return longs.containsKey(key);
|
||||
|
@ -295,6 +307,17 @@ public class Data {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder putIntegerListAsArray(@NonNull String key, @NonNull List<Integer> value) {
|
||||
int[] ints = new int[value.size()];
|
||||
|
||||
for (int i = 0; i < value.size(); i++) {
|
||||
ints[i] = value.get(i);
|
||||
}
|
||||
|
||||
integerArrays.put(key, ints);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder putInt(@NonNull String key, int value) {
|
||||
integers.put(key, value);
|
||||
return this;
|
||||
|
|
|
@ -62,6 +62,7 @@ import org.thoughtcrime.securesms.migrations.StickerMyDailyLifeMigrationJob;
|
|||
import org.thoughtcrime.securesms.migrations.StorageCapabilityMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StorageServiceMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StorageServiceSystemNameMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StoryViewedReceiptsStateMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.SyncDistributionListsMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.TrimByLengthSettingsMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.UserNotificationMigrationJob;
|
||||
|
@ -225,6 +226,7 @@ public final class JobManagerFactories {
|
|||
put(StorageCapabilityMigrationJob.KEY, new StorageCapabilityMigrationJob.Factory());
|
||||
put(StorageServiceMigrationJob.KEY, new StorageServiceMigrationJob.Factory());
|
||||
put(StorageServiceSystemNameMigrationJob.KEY, new StorageServiceSystemNameMigrationJob.Factory());
|
||||
put(StoryViewedReceiptsStateMigrationJob.KEY, new StoryViewedReceiptsStateMigrationJob.Factory());
|
||||
put(TrimByLengthSettingsMigrationJob.KEY, new TrimByLengthSettingsMigrationJob.Factory());
|
||||
put(UserNotificationMigrationJob.KEY, new UserNotificationMigrationJob.Factory());
|
||||
put(UuidMigrationJob.KEY, new UuidMigrationJob.Factory());
|
||||
|
|
|
@ -11,11 +11,13 @@ import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
|||
import org.thoughtcrime.securesms.database.MessageDatabase.MarkedMessageInfo;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.database.model.StoryType;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
@ -33,6 +35,7 @@ import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedExcept
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -64,10 +67,10 @@ public class SendViewedReceiptJob extends BaseJob {
|
|||
|
||||
private SendViewedReceiptJob(long threadId, @NonNull RecipientId recipientId, @NonNull List<Long> messageSentTimestamps, @NonNull List<MessageId> messageIds) {
|
||||
this(new Parameters.Builder()
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.build(),
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.build(),
|
||||
threadId,
|
||||
recipientId,
|
||||
SendReadReceiptJob.ensureSize(messageSentTimestamps, MAX_TIMESTAMPS),
|
||||
|
@ -130,15 +133,36 @@ public class SendViewedReceiptJob extends BaseJob {
|
|||
|
||||
@Override
|
||||
public void onRun() throws IOException, UntrustedIdentityException {
|
||||
|
||||
boolean canSendNonStoryReceipts = TextSecurePreferences.isReadReceiptsEnabled(context);
|
||||
boolean canSendStoryReceipts = SignalStore.storyValues().getViewedReceiptsEnabled();
|
||||
|
||||
List<MessageId> messageIds = new LinkedList<>();
|
||||
List<Long> messageSentTimestamps = new LinkedList<>();
|
||||
List<StoryType> storyTypes = SignalDatabase.mms().getStoryTypes(messageIds);
|
||||
|
||||
for (int i = 0; i < storyTypes.size(); i++) {
|
||||
StoryType storyType = storyTypes.get(i);
|
||||
if ((storyType == StoryType.NONE && canSendNonStoryReceipts) || (storyType.isStory() && canSendStoryReceipts)) {
|
||||
messageIds.add(this.messageIds.get(i));
|
||||
messageSentTimestamps.add(this.messageSentTimestamps.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
if (!Recipient.self().isRegistered()) {
|
||||
throw new NotPushRegisteredException();
|
||||
}
|
||||
|
||||
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
|
||||
if (storyTypes.isEmpty() && !TextSecurePreferences.isReadReceiptsEnabled(context)) {
|
||||
Log.w(TAG, "Read receipts not enabled!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (messageIds.isEmpty()) {
|
||||
Log.w(TAG, "No messages in this batch are allowed to be sent!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (messageSentTimestamps.isEmpty()) {
|
||||
Log.w(TAG, "No sync timestamps!");
|
||||
return;
|
||||
|
|
|
@ -44,16 +44,24 @@ internal class StoryValues(store: KeyValueStore) : SignalStoreValues(store) {
|
|||
* Marks whether the user has seen the beta dialog
|
||||
*/
|
||||
private const val USER_HAS_SEEN_BETA_DIALOG = "stories.user.has.seen.beta.dialog"
|
||||
|
||||
/**
|
||||
* Whether or not the user will send and receive viewed receipts for stories
|
||||
*/
|
||||
private const val STORY_VIEWED_RECEIPTS = "stories.viewed.receipts"
|
||||
}
|
||||
|
||||
override fun onFirstEverAppLaunch() = Unit
|
||||
override fun onFirstEverAppLaunch() {
|
||||
viewedReceiptsEnabled = true
|
||||
}
|
||||
|
||||
override fun getKeysToIncludeInBackup(): MutableList<String> = mutableListOf(
|
||||
MANUAL_FEATURE_DISABLE,
|
||||
USER_HAS_ADDED_TO_A_STORY,
|
||||
USER_HAS_SEEN_FIRST_NAV_VIEW,
|
||||
HAS_DOWNLOADED_ONBOARDING_STORY,
|
||||
USER_HAS_SEEN_BETA_DIALOG
|
||||
USER_HAS_SEEN_BETA_DIALOG,
|
||||
STORY_VIEWED_RECEIPTS
|
||||
)
|
||||
|
||||
var isFeatureDisabled: Boolean by booleanValue(MANUAL_FEATURE_DISABLE, false)
|
||||
|
@ -70,6 +78,12 @@ internal class StoryValues(store: KeyValueStore) : SignalStoreValues(store) {
|
|||
|
||||
var userHasSeenBetaDialog: Boolean by booleanValue(USER_HAS_SEEN_BETA_DIALOG, false)
|
||||
|
||||
var viewedReceiptsEnabled: Boolean by booleanValue(STORY_VIEWED_RECEIPTS, false)
|
||||
|
||||
fun isViewedReceiptsStateSet(): Boolean {
|
||||
return store.containsKey(STORY_VIEWED_RECEIPTS)
|
||||
}
|
||||
|
||||
fun setLatestStorySend(storySend: StorySend) {
|
||||
synchronized(this) {
|
||||
val storySends: List<StorySend> = getList(LATEST_STORY_SENDS, StorySendSerializer)
|
||||
|
|
|
@ -100,6 +100,7 @@ import org.thoughtcrime.securesms.jobs.SenderKeyDistributionSendJob;
|
|||
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
|
||||
import org.thoughtcrime.securesms.jobs.TrimThreadJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.keyvalue.StoryValues;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||
|
@ -2536,18 +2537,28 @@ public final class MessageContentProcessor {
|
|||
@NonNull SignalServiceReceiptMessage message,
|
||||
@NonNull Recipient senderRecipient)
|
||||
{
|
||||
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
|
||||
boolean readReceipts = TextSecurePreferences.isReadReceiptsEnabled(context);
|
||||
boolean storyViewedReceipts = SignalStore.storyValues().getViewedReceiptsEnabled();
|
||||
|
||||
if (!readReceipts && !storyViewedReceipts) {
|
||||
log("Ignoring viewed receipts for IDs: " + Util.join(message.getTimestamps(), ", "));
|
||||
return;
|
||||
}
|
||||
|
||||
log(TAG, "Processing viewed receipts. Sender: " + senderRecipient.getId() + ", Device: " + content.getSenderDevice() + ", Timestamps: " + Util.join(message.getTimestamps(), ", "));
|
||||
log(TAG, "Processing viewed receipts. Sender: " + senderRecipient.getId() + ", Device: " + content.getSenderDevice() + ", Only Stories: " + (!readReceipts && storyViewedReceipts) + ", Timestamps: " + Util.join(message.getTimestamps(), ", "));
|
||||
|
||||
List<SyncMessageId> ids = Stream.of(message.getTimestamps())
|
||||
.map(t -> new SyncMessageId(senderRecipient.getId(), t))
|
||||
.toList();
|
||||
|
||||
Collection<SyncMessageId> unhandled = SignalDatabase.mmsSms().incrementViewedReceiptCounts(ids, content.getTimestamp());
|
||||
final Collection<SyncMessageId> unhandled;
|
||||
if (readReceipts && storyViewedReceipts) {
|
||||
unhandled = SignalDatabase.mmsSms().incrementViewedReceiptCounts(ids, content.getTimestamp());
|
||||
} else if (readReceipts) {
|
||||
unhandled = SignalDatabase.mmsSms().incrementViewedNonStoryReceiptCounts(ids, content.getTimestamp());
|
||||
} else {
|
||||
unhandled = SignalDatabase.mmsSms().incrementViewedStoryReceiptCounts(ids, content.getTimestamp());
|
||||
}
|
||||
|
||||
Set<SyncMessageId> handled = new HashSet<>(ids);
|
||||
handled.removeAll(unhandled);
|
||||
|
|
|
@ -109,9 +109,10 @@ public class ApplicationMigrations {
|
|||
static final int KBS_MIGRATION_2 = 65;
|
||||
static final int PNI_2 = 66;
|
||||
static final int SYSTEM_NAME_SYNC = 67;
|
||||
static final int STORY_VIEWED_STATE = 68;
|
||||
}
|
||||
|
||||
public static final int CURRENT_VERSION = 67;
|
||||
public static final int CURRENT_VERSION = 68;
|
||||
|
||||
/**
|
||||
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
|
||||
|
@ -481,6 +482,10 @@ public class ApplicationMigrations {
|
|||
jobs.put(Version.SYSTEM_NAME_SYNC, new StorageServiceSystemNameMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.STORY_VIEWED_STATE) {
|
||||
jobs.put(Version.STORY_VIEWED_STATE, new StoryViewedReceiptsStateMigrationJob());
|
||||
}
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package org.thoughtcrime.securesms.migrations
|
||||
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.recipients
|
||||
import org.thoughtcrime.securesms.jobmanager.Data
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
|
||||
/**
|
||||
* Added as a way to initialize the story viewed receipts setting.
|
||||
*/
|
||||
internal class StoryViewedReceiptsStateMigrationJob(
|
||||
parameters: Parameters = Parameters.Builder().build()
|
||||
) : MigrationJob(parameters) {
|
||||
companion object {
|
||||
const val KEY = "StoryViewedReceiptsStateMigrationJob"
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String = KEY
|
||||
|
||||
override fun isUiBlocking(): Boolean = false
|
||||
|
||||
override fun performMigration() {
|
||||
if (!SignalStore.storyValues().isViewedReceiptsStateSet()) {
|
||||
SignalStore.storyValues().viewedReceiptsEnabled = TextSecurePreferences.isReadReceiptsEnabled(context)
|
||||
if (SignalStore.account().isRegistered) {
|
||||
recipients.markNeedsSync(Recipient.self().id)
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun shouldRetry(e: Exception): Boolean = false
|
||||
|
||||
class Factory : Job.Factory<StoryViewedReceiptsStateMigrationJob> {
|
||||
override fun create(parameters: Parameters, data: Data): StoryViewedReceiptsStateMigrationJob {
|
||||
return StoryViewedReceiptsStateMigrationJob(parameters)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ import org.whispersystems.signalservice.api.storage.SignalAccountRecord;
|
|||
import org.whispersystems.signalservice.api.storage.SignalAccountRecord.PinnedConversation;
|
||||
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.OptionalBool;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
@ -92,6 +93,13 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
|||
subscriber = local.getSubscriber();
|
||||
}
|
||||
|
||||
OptionalBool storyViewReceiptsState;
|
||||
if (remote.getStoryViewReceiptsState() == OptionalBool.UNSET) {
|
||||
storyViewReceiptsState = local.getStoryViewReceiptsState();
|
||||
} else {
|
||||
storyViewReceiptsState = remote.getStoryViewReceiptsState();
|
||||
}
|
||||
|
||||
byte[] unknownFields = remote.serializeUnknownFields();
|
||||
String avatarUrlPath = OptionalUtil.or(remote.getAvatarUrlPath(), local.getAvatarUrlPath()).orElse("");
|
||||
byte[] profileKey = OptionalUtil.or(remote.getProfileKey(), local.getProfileKey()).orElse(null);
|
||||
|
@ -115,8 +123,8 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
|||
boolean hasSetMyStoriesPrivacy = remote.hasSetMyStoriesPrivacy();
|
||||
boolean hasViewedOnboardingStory = remote.hasViewedOnboardingStory();
|
||||
boolean storiesDisabled = remote.isStoriesDisabled();
|
||||
boolean matchesRemote = doParamsMatch(remote, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, noteToSelfForcedUnread, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted, pinnedConversations, preferContactAvatars, payments, universalExpireTimer, primarySendsSms, e164, defaultReactions, subscriber, displayBadgesOnProfile, subscriptionManuallyCancelled, keepMutedChatsArchived, hasSetMyStoriesPrivacy, hasViewedOnboardingStory, storiesDisabled);
|
||||
boolean matchesLocal = doParamsMatch(local, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, noteToSelfForcedUnread, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted, pinnedConversations, preferContactAvatars, payments, universalExpireTimer, primarySendsSms, e164, defaultReactions, subscriber, displayBadgesOnProfile, subscriptionManuallyCancelled, keepMutedChatsArchived, hasSetMyStoriesPrivacy, hasViewedOnboardingStory, storiesDisabled);
|
||||
boolean matchesRemote = doParamsMatch(remote, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, noteToSelfForcedUnread, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted, pinnedConversations, preferContactAvatars, payments, universalExpireTimer, primarySendsSms, e164, defaultReactions, subscriber, displayBadgesOnProfile, subscriptionManuallyCancelled, keepMutedChatsArchived, hasSetMyStoriesPrivacy, hasViewedOnboardingStory, storiesDisabled, storyViewReceiptsState);
|
||||
boolean matchesLocal = doParamsMatch(local, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, noteToSelfForcedUnread, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted, pinnedConversations, preferContactAvatars, payments, universalExpireTimer, primarySendsSms, e164, defaultReactions, subscriber, displayBadgesOnProfile, subscriptionManuallyCancelled, keepMutedChatsArchived, hasSetMyStoriesPrivacy, hasViewedOnboardingStory, storiesDisabled, storyViewReceiptsState);
|
||||
|
||||
if (matchesRemote) {
|
||||
return remote;
|
||||
|
@ -197,7 +205,8 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
|||
boolean keepMutedChatsArchived,
|
||||
boolean hasSetMyStoriesPrivacy,
|
||||
boolean hasViewedOnboardingStory,
|
||||
boolean storiesDisabled)
|
||||
boolean storiesDisabled,
|
||||
@NonNull OptionalBool storyViewReceiptsState)
|
||||
{
|
||||
return Arrays.equals(contact.serializeUnknownFields(), unknownFields) &&
|
||||
Objects.equals(contact.getGivenName().orElse(""), givenName) &&
|
||||
|
@ -225,6 +234,7 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
|||
contact.isKeepMutedChatsArchived() == keepMutedChatsArchived &&
|
||||
contact.hasSetMyStoriesPrivacy() == hasSetMyStoriesPrivacy &&
|
||||
contact.hasViewedOnboardingStory() == hasViewedOnboardingStory &&
|
||||
contact.isStoriesDisabled() == storiesDisabled;
|
||||
contact.isStoriesDisabled() == storiesDisabled &&
|
||||
contact.getStoryViewReceiptsState().equals(storyViewReceiptsState);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
|
|||
import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
|
||||
import org.whispersystems.signalservice.api.storage.StorageId;
|
||||
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.OptionalBool;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
@ -56,10 +58,9 @@ public final class StorageSyncHelper {
|
|||
* you which keys are exclusively remote and which are exclusively local.
|
||||
*
|
||||
* @param remoteIds All remote keys available.
|
||||
* @param localIds All local keys available.
|
||||
*
|
||||
* @param localIds All local keys available.
|
||||
* @return An object describing which keys are exclusive to the remote data set and which keys are
|
||||
* exclusive to the local data set.
|
||||
* exclusive to the local data set.
|
||||
*/
|
||||
public static @NonNull IdDifferenceResult findIdDifference(@NonNull Collection<StorageId> remoteIds,
|
||||
@NonNull Collection<StorageId> localIds)
|
||||
|
@ -111,6 +112,9 @@ public final class StorageSyncHelper {
|
|||
.map(recipientDatabase::getRecordForSync)
|
||||
.toList();
|
||||
|
||||
final OptionalBool storyViewReceiptsState = SignalStore.storyValues().getViewedReceiptsEnabled() ? OptionalBool.ENABLED
|
||||
: OptionalBool.DISABLED;
|
||||
|
||||
if (self.getStorageServiceId() == null) {
|
||||
Log.w(TAG, "[buildAccountRecord] No storageId for self! Generating. (Record had ID: " + (record != null && record.getStorageId() != null) + ")");
|
||||
SignalDatabase.recipients().updateStorageId(self.getId(), generateKey());
|
||||
|
@ -145,6 +149,7 @@ public final class StorageSyncHelper {
|
|||
.setHasSetMyStoriesPrivacy(SignalStore.storyValues().getUserHasBeenNotifiedAboutStories())
|
||||
.setHasViewedOnboardingStory(SignalStore.storyValues().getUserHasSeenOnboardingStory())
|
||||
.setStoriesDisabled(SignalStore.storyValues().isFeatureDisabled())
|
||||
.setStoryViewReceiptsState(storyViewReceiptsState)
|
||||
.build();
|
||||
|
||||
return SignalStorageRecord.forAccount(account);
|
||||
|
@ -174,6 +179,12 @@ public final class StorageSyncHelper {
|
|||
SignalStore.storyValues().setUserHasSeenOnboardingStory(update.getNew().hasViewedOnboardingStory());
|
||||
SignalStore.storyValues().setFeatureDisabled(update.getNew().isStoriesDisabled());
|
||||
|
||||
if (update.getNew().getStoryViewReceiptsState() == OptionalBool.UNSET) {
|
||||
SignalStore.storyValues().setViewedReceiptsEnabled(update.getNew().isReadReceiptsEnabled());
|
||||
} else {
|
||||
SignalStore.storyValues().setViewedReceiptsEnabled(update.getNew().getStoryViewReceiptsState() == OptionalBool.ENABLED);
|
||||
}
|
||||
|
||||
if (update.getNew().isSubscriptionManuallyCancelled()) {
|
||||
SignalStore.donationsValues().updateLocalStateForManualCancellation();
|
||||
} else {
|
||||
|
@ -233,7 +244,7 @@ public final class StorageSyncHelper {
|
|||
|
||||
/**
|
||||
* @return True if there exist some keys that have matching raw ID's but different types,
|
||||
* otherwise false.
|
||||
* otherwise false.
|
||||
*/
|
||||
public boolean hasTypeMismatches() {
|
||||
return hasTypeMismatches;
|
||||
|
|
|
@ -16,11 +16,11 @@ import org.thoughtcrime.securesms.components.menu.SignalContextMenu
|
|||
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||
import org.thoughtcrime.securesms.conversation.ConversationMessage
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.stories.StoryTextPostModel
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
|
||||
|
@ -110,7 +110,7 @@ object MyStoriesItem {
|
|||
presentDateOrStatus(model)
|
||||
|
||||
if (model.distributionStory.messageRecord.isSent) {
|
||||
if (TextSecurePreferences.isReadReceiptsEnabled(context)) {
|
||||
if (SignalStore.storyValues().viewedReceiptsEnabled) {
|
||||
viewCount.text = context.resources.getQuantityString(
|
||||
R.plurals.MyStories__d_views,
|
||||
model.distributionStory.messageRecord.viewedReceiptCount,
|
||||
|
|
|
@ -150,6 +150,19 @@ class StoriesPrivacySettingsFragment :
|
|||
configure {
|
||||
dividerPref()
|
||||
|
||||
sectionHeaderPref(R.string.StoriesPrivacySettingsFragment__story_views)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.StoriesPrivacySettingsFragment__view_receipts),
|
||||
summary = DSLSettingsText.from(R.string.StoriesPrivacySettingsFragment__see_and_share),
|
||||
isChecked = state.areViewReceiptsEnabled,
|
||||
onClick = {
|
||||
viewModel.toggleViewReceipts()
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.StoriesPrivacySettingsFragment__turn_off_stories),
|
||||
summary = DSLSettingsText.from(
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.stories.settings.story
|
|||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
|
@ -36,6 +37,12 @@ class StoriesPrivacySettingsRepository {
|
|||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun onSettingsChanged() {
|
||||
SignalExecutors.BOUNDED_IO.execute {
|
||||
Stories.onStorySettingsChanged(Recipient.self().id)
|
||||
}
|
||||
}
|
||||
|
||||
fun userHasOutgoingStories(): Single<Boolean> {
|
||||
return Single.fromCallable {
|
||||
SignalDatabase.mms.getAllOutgoingStories(false, -1).use {
|
||||
|
|
|
@ -4,7 +4,8 @@ import org.thoughtcrime.securesms.contacts.paged.ContactSearchData
|
|||
|
||||
data class StoriesPrivacySettingsState(
|
||||
val areStoriesEnabled: Boolean,
|
||||
val areViewReceiptsEnabled: Boolean,
|
||||
val isUpdatingEnabledState: Boolean = false,
|
||||
val storyContactItems: List<ContactSearchData> = emptyList(),
|
||||
val userHasStories: Boolean = false
|
||||
val userHasStories: Boolean = false,
|
||||
)
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.signal.paging.ProxyPagingController
|
|||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchConfiguration
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchPagedDataSource
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
import org.thoughtcrime.securesms.util.rx.RxStore
|
||||
|
@ -22,7 +23,8 @@ class StoriesPrivacySettingsViewModel : ViewModel() {
|
|||
|
||||
private val store = RxStore(
|
||||
StoriesPrivacySettingsState(
|
||||
areStoriesEnabled = Stories.isFeatureEnabled()
|
||||
areStoriesEnabled = Stories.isFeatureEnabled(),
|
||||
areViewReceiptsEnabled = SignalStore.storyValues().viewedReceiptsEnabled
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -77,6 +79,12 @@ class StoriesPrivacySettingsViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
fun toggleViewReceipts() {
|
||||
SignalStore.storyValues().viewedReceiptsEnabled = !SignalStore.storyValues().viewedReceiptsEnabled
|
||||
store.update { it.copy(areViewReceiptsEnabled = SignalStore.storyValues().viewedReceiptsEnabled) }
|
||||
repository.onSettingsChanged()
|
||||
}
|
||||
|
||||
fun displayGroupsAsStories(recipientIds: List<RecipientId>) {
|
||||
disposables += repository.markGroupsAsStories(recipientIds).subscribe {
|
||||
pagingController.onDataInvalidated()
|
||||
|
|
|
@ -26,7 +26,6 @@ import org.thoughtcrime.securesms.recipients.RecipientId
|
|||
import org.thoughtcrime.securesms.sms.MessageSender
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
import org.thoughtcrime.securesms.util.Base64
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
|
||||
/**
|
||||
* Open for testing.
|
||||
|
@ -39,7 +38,7 @@ open class StoryViewerPageRepository(context: Context) {
|
|||
|
||||
private val context = context.applicationContext
|
||||
|
||||
fun isReadReceiptsEnabled(): Boolean = TextSecurePreferences.isReadReceiptsEnabled(context)
|
||||
fun isReadReceiptsEnabled(): Boolean = SignalStore.storyValues().viewedReceiptsEnabled
|
||||
|
||||
private fun getStoryRecords(recipientId: RecipientId, isOutgoingOnly: Boolean): Observable<List<MessageRecord>> {
|
||||
return Observable.create { emitter ->
|
||||
|
|
|
@ -9,8 +9,8 @@ import org.thoughtcrime.securesms.database.DatabaseObserver
|
|||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
|
||||
class StoryViewsRepository {
|
||||
|
||||
|
@ -18,7 +18,7 @@ class StoryViewsRepository {
|
|||
private val TAG = Log.tag(StoryViewsRepository::class.java)
|
||||
}
|
||||
|
||||
fun isReadReceiptsEnabled(): Boolean = TextSecurePreferences.isReadReceiptsEnabled(ApplicationDependencies.getApplication())
|
||||
fun isReadReceiptsEnabled(): Boolean = SignalStore.storyValues().viewedReceiptsEnabled
|
||||
|
||||
fun getStoryRecipient(storyId: Long): Single<Recipient> {
|
||||
return Single.fromCallable {
|
||||
|
|
|
@ -5311,6 +5311,12 @@
|
|||
<string name="StoriesPrivacySettingsFragment__story_privacy">Story privacy</string>
|
||||
<!-- Header for section that lists out stories -->
|
||||
<string name="StoriesPrivacySettingsFragment__stories">Stories</string>
|
||||
<!-- Story views header -->
|
||||
<string name="StoriesPrivacySettingsFragment__story_views">Story views</string>
|
||||
<!-- Story view receipts toggle title -->
|
||||
<string name="StoriesPrivacySettingsFragment__view_receipts">View receipts</string>
|
||||
<!-- Story view receipts toggle message -->
|
||||
<string name="StoriesPrivacySettingsFragment__see_and_share">See and share when stories are viewed. If disabled, you won\'t see when others view your story.</string>
|
||||
|
||||
<!-- NewStoryItem -->
|
||||
<string name="NewStoryItem__new_story">New story</string>
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
|||
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
||||
import org.whispersystems.signalservice.api.util.ProtoUtil;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.OptionalBool;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
@ -182,6 +183,10 @@ public final class SignalAccountRecord implements SignalRecord {
|
|||
diff.add("StoriesDisabled");
|
||||
}
|
||||
|
||||
if (getStoryViewReceiptsState() != that.getStoryViewReceiptsState()) {
|
||||
diff.add("StoryViewedReceipts");
|
||||
}
|
||||
|
||||
return diff.toString();
|
||||
} else {
|
||||
return "Different class. " + getClass().getSimpleName() + " | " + other.getClass().getSimpleName();
|
||||
|
@ -300,6 +305,10 @@ public final class SignalAccountRecord implements SignalRecord {
|
|||
return proto.getStoriesDisabled();
|
||||
}
|
||||
|
||||
public OptionalBool getStoryViewReceiptsState() {
|
||||
return proto.getStoryViewReceiptsEnabled();
|
||||
}
|
||||
|
||||
public AccountRecord toProto() {
|
||||
return proto;
|
||||
}
|
||||
|
@ -657,6 +666,11 @@ public final class SignalAccountRecord implements SignalRecord {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder setStoryViewReceiptsState(OptionalBool storyViewedReceiptsEnabled) {
|
||||
builder.setStoryViewReceiptsEnabled(storyViewedReceiptsEnabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
private static AccountRecord.Builder parseUnknowns(byte[] serializedUnknowns) {
|
||||
try {
|
||||
return AccountRecord.parseFrom(serializedUnknowns).toBuilder();
|
||||
|
|
|
@ -10,6 +10,12 @@ package signalservice;
|
|||
option java_package = "org.whispersystems.signalservice.internal.storage.protos";
|
||||
option java_multiple_files = true;
|
||||
|
||||
enum OptionalBool {
|
||||
UNSET = 0;
|
||||
ENABLED = 1;
|
||||
DISABLED = 2;
|
||||
}
|
||||
|
||||
message StorageManifest {
|
||||
uint64 version = 1;
|
||||
bytes value = 2;
|
||||
|
@ -176,6 +182,7 @@ message AccountRecord {
|
|||
bool hasViewedOnboardingStory = 27;
|
||||
reserved /* storiesDisabled */ 28;
|
||||
bool storiesDisabled = 29;
|
||||
OptionalBool storyViewReceiptsEnabled = 30;
|
||||
}
|
||||
|
||||
message StoryDistributionListRecord {
|
||||
|
|
Loading…
Add table
Reference in a new issue