Implement story ring support.

This commit is contained in:
Alex Hart
2022-02-25 15:06:12 -04:00
parent fe088c39c7
commit 2d7655a6bb
20 changed files with 244 additions and 13 deletions

View File

@@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.database.model.StoryViewState;
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
import org.thoughtcrime.securesms.insights.InsightsConstants;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
@@ -193,6 +194,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
public abstract long getUnreadStoryCount();
public abstract @Nullable Long getOldestStorySendTimestamp();
public abstract int deleteStoriesOlderThan(long timestamp);
public abstract @NonNull StoryViewState getStoryViewState(@NonNull RecipientId recipientId);
final @NonNull String getOutgoingTypeClause() {
List<String> segments = new ArrayList<>(Types.OUTGOING_MESSAGE_TYPES.length);

View File

@@ -23,6 +23,7 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
@@ -52,6 +53,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.Quote;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.database.model.StoryViewState;
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
@@ -74,6 +76,7 @@ import org.thoughtcrime.securesms.revealable.ViewOnceUtil;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.CursorUtil;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.JsonUtils;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.SqlUtil;
@@ -591,6 +594,46 @@ public class MmsDatabase extends MessageDatabase {
return new Reader(cursor);
}
@Override
public @NonNull StoryViewState getStoryViewState(@NonNull RecipientId recipientId) {
if (!FeatureFlags.stories() || SignalStore.storyValues().isFeatureDisabled()) {
return StoryViewState.NONE;
}
long threadId = SignalDatabase.threads().getThreadIdIfExistsFor(recipientId);
return getStoryViewState(threadId);
}
@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 boolean hasStories;
try (Cursor cursor = getReadableDatabase().rawQuery(hasStoryQuery, hasStoryArgs)) {
hasStories = cursor != null && cursor.moveToFirst() && !cursor.isNull(0) && cursor.getInt(0) == 1;
}
if (!hasStories) {
return StoryViewState.NONE;
}
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 boolean hasUnviewedStories;
try (Cursor cursor = getReadableDatabase().rawQuery(hasUnviewedStoriesQuery, hasUnviewedStoriesArgs)) {
hasUnviewedStories = cursor != null && cursor.moveToFirst() && !cursor.isNull(0) && cursor.getInt(0) == 1;
}
if (hasUnviewedStories) {
return StoryViewState.UNVIEWED;
} else {
return StoryViewState.VIEWED;
}
}
@Override
public @NonNull MessageId getStoryId(@NonNull RecipientId authorId, long sentTimestamp) throws NoSuchMessageException {
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();

View File

@@ -39,6 +39,7 @@ import org.thoughtcrime.securesms.database.model.GroupCallUpdateDetailsUtil;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.database.model.StoryViewState;
import org.thoughtcrime.securesms.database.model.databaseprotos.GroupCallUpdateDetails;
import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
@@ -1431,6 +1432,11 @@ public class SmsDatabase extends MessageDatabase {
throw new UnsupportedOperationException();
}
@Override
public @NonNull StoryViewState getStoryViewState(@NonNull RecipientId recipientId) {
throw new UnsupportedOperationException();
}
@Override
public int deleteStoriesOlderThan(long timestamp) {
throw new UnsupportedOperationException();

View File

@@ -0,0 +1,39 @@
package org.thoughtcrime.securesms.database.model
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.database.DatabaseObserver
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.recipients.RecipientId
/**
* Denotes whether a given recipient has stories, and whether those stories are viewed or unviewed.
*/
enum class StoryViewState {
NONE,
UNVIEWED,
VIEWED;
companion object {
@JvmStatic
fun getForRecipientId(recipientId: RecipientId): Observable<StoryViewState> {
return Observable.create<StoryViewState> { emitter ->
fun refresh() {
emitter.onNext(SignalDatabase.mms.getStoryViewState(recipientId))
}
val storyObserver = DatabaseObserver.Observer {
refresh()
}
ApplicationDependencies.getDatabaseObserver().registerStoryObserver(recipientId, storyObserver)
emitter.setCancellable {
ApplicationDependencies.getDatabaseObserver().unregisterObserver(storyObserver)
}
refresh()
}.observeOn(Schedulers.io())
}
}
}