Add support for new group story display states.

fork-5.53.8
Alex Hart 2022-09-22 13:21:53 -03:00 zatwierdzone przez Cody Henthorne
rodzic 8ca94eb3d5
commit c47a724654
16 zmienionych plików z 211 dodań i 63 usunięć

Wyświetl plik

@ -13,7 +13,11 @@ sealed class ContactSearchData(val contactSearchKey: ContactSearchKey) {
* *
* Note that if the recipient is a group, it's participant list size is used instead of viewerCount. * Note that if the recipient is a group, it's participant list size is used instead of viewerCount.
*/ */
data class Story(val recipient: Recipient, val viewerCount: Int, val privacyMode: DistributionListPrivacyMode) : ContactSearchData(ContactSearchKey.RecipientSearchKey.Story(recipient.id)) data class Story(
val recipient: Recipient,
val viewerCount: Int,
val privacyMode: DistributionListPrivacyMode
) : ContactSearchData(ContactSearchKey.RecipientSearchKey.Story(recipient.id))
/** /**
* A row displaying a known recipient. * A row displaying a known recipient.

Wyświetl plik

@ -261,7 +261,6 @@ class ContactSearchPagedDataSource(
rhs.recipient.isMyStory -> 1 rhs.recipient.isMyStory -> 1
lhsActiveRank < rhsActiveRank -> -1 lhsActiveRank < rhsActiveRank -> -1
lhsActiveRank > rhsActiveRank -> 1 lhsActiveRank > rhsActiveRank -> 1
lhsActiveRank == rhsActiveRank -> -1
else -> 0 else -> 0
} }
} }

Wyświetl plik

@ -1,16 +1,20 @@
package org.thoughtcrime.securesms.contacts.paged package org.thoughtcrime.securesms.contacts.paged
import androidx.annotation.CheckResult
import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.database.GroupDatabase
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.DistributionListId import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.stories.Stories import org.thoughtcrime.securesms.stories.Stories
class ContactSearchRepository { class ContactSearchRepository {
@CheckResult
fun filterOutUnselectableContactSearchKeys(contactSearchKeys: Set<ContactSearchKey>): Single<Set<ContactSearchSelectionResult>> { fun filterOutUnselectableContactSearchKeys(contactSearchKeys: Set<ContactSearchKey>): Single<Set<ContactSearchSelectionResult>> {
return Single.fromCallable { return Single.fromCallable {
contactSearchKeys.map { contactSearchKeys.map {
@ -35,12 +39,25 @@ class ContactSearchRepository {
} }
} }
fun unmarkDisplayAsStory(groupId: GroupId): Completable { @CheckResult
fun markDisplayAsStory(recipientIds: Collection<RecipientId>): Completable {
return Completable.fromAction { return Completable.fromAction {
SignalDatabase.groups.markDisplayAsStory(groupId, false) SignalDatabase.groups.setShowAsStoryState(recipientIds, GroupDatabase.ShowAsStoryState.ALWAYS)
SignalDatabase.recipients.markNeedsSync(recipientIds)
StorageSyncHelper.scheduleSyncForDataChange()
}.subscribeOn(Schedulers.io()) }.subscribeOn(Schedulers.io())
} }
@CheckResult
fun unmarkDisplayAsStory(groupId: GroupId): Completable {
return Completable.fromAction {
SignalDatabase.groups.setShowAsStoryState(groupId, GroupDatabase.ShowAsStoryState.NEVER)
SignalDatabase.recipients.markNeedsSync(Recipient.externalGroupExact(groupId).id)
StorageSyncHelper.scheduleSyncForDataChange()
}.subscribeOn(Schedulers.io())
}
@CheckResult
fun deletePrivateStory(distributionListId: DistributionListId): Completable { fun deletePrivateStory(distributionListId: DistributionListId): Completable {
return Completable.fromAction { return Completable.fromAction {
SignalDatabase.distributionLists.deleteList(distributionListId) SignalDatabase.distributionLists.deleteList(distributionListId)

Wyświetl plik

@ -99,13 +99,15 @@ class ContactSearchViewModel(
} }
fun addToVisibleGroupStories(groupStories: Set<ContactSearchKey.RecipientSearchKey.Story>) { fun addToVisibleGroupStories(groupStories: Set<ContactSearchKey.RecipientSearchKey.Story>) {
configurationStore.update { state -> disposables += contactSearchRepository.markDisplayAsStory(groupStories.map { it.recipientId }).subscribe {
state.copy( configurationStore.update { state ->
groupStories = state.groupStories + groupStories.map { state.copy(
val recipient = Recipient.resolved(it.recipientId) groupStories = state.groupStories + groupStories.map {
ContactSearchData.Story(recipient, recipient.participantIds.size, DistributionListPrivacyMode.ALL) val recipient = Recipient.resolved(it.recipientId)
} ContactSearchData.Story(recipient, recipient.participantIds.size, DistributionListPrivacyMode.ALL)
) }
)
}
} }
} }

Wyświetl plik

@ -14,6 +14,7 @@ import com.annimon.stream.Stream;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import org.signal.core.util.CursorUtil; import org.signal.core.util.CursorUtil;
import org.signal.core.util.SQLiteDatabaseExtensionsKt;
import org.signal.core.util.SetUtil; import org.signal.core.util.SetUtil;
import org.signal.core.util.SqlUtil; import org.signal.core.util.SqlUtil;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
@ -37,6 +38,7 @@ import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil; import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct; import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct;
@ -45,6 +47,7 @@ import org.whispersystems.signalservice.api.push.ACI;
import org.whispersystems.signalservice.api.push.DistributionId; import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.push.exceptions.GroupNotFoundException;
import java.io.Closeable; import java.io.Closeable;
import java.security.SecureRandom; import java.security.SecureRandom;
@ -84,7 +87,7 @@ public class GroupDatabase extends Database {
private static final String EXPECTED_V2_ID = "expected_v2_id"; private static final String EXPECTED_V2_ID = "expected_v2_id";
private static final String UNMIGRATED_V1_MEMBERS = "former_v1_members"; private static final String UNMIGRATED_V1_MEMBERS = "former_v1_members";
private static final String DISTRIBUTION_ID = "distribution_id"; private static final String DISTRIBUTION_ID = "distribution_id";
private static final String DISPLAY_AS_STORY = "display_as_story"; private static final String SHOW_AS_STORY_STATE = "display_as_story";
/** Was temporarily used for PNP accept by pni but is no longer needed/updated */ /** Was temporarily used for PNP accept by pni but is no longer needed/updated */
@Deprecated @Deprecated
@ -118,7 +121,7 @@ public class GroupDatabase extends Database {
EXPECTED_V2_ID + " TEXT DEFAULT NULL, " + EXPECTED_V2_ID + " TEXT DEFAULT NULL, " +
UNMIGRATED_V1_MEMBERS + " TEXT DEFAULT NULL, " + UNMIGRATED_V1_MEMBERS + " TEXT DEFAULT NULL, " +
DISTRIBUTION_ID + " TEXT DEFAULT NULL, " + DISTRIBUTION_ID + " TEXT DEFAULT NULL, " +
DISPLAY_AS_STORY + " INTEGER DEFAULT 0, " + SHOW_AS_STORY_STATE + " INTEGER DEFAULT 0, " +
AUTH_SERVICE_ID + " TEXT DEFAULT NULL);"; AUTH_SERVICE_ID + " TEXT DEFAULT NULL);";
public static final String[] CREATE_INDEXS = { public static final String[] CREATE_INDEXS = {
@ -1462,11 +1465,20 @@ public class GroupDatabase extends Database {
} }
public @NonNull List<GroupId> getGroupsToDisplayAsStories() throws BadGroupIdException { public @NonNull List<GroupId> getGroupsToDisplayAsStories() throws BadGroupIdException {
String[] selection = SqlUtil.buildArgs(GROUP_ID); String query = "SELECT " + GROUP_ID + ", (" +
String where = DISPLAY_AS_STORY + " = ? AND " + ACTIVE + " = ?"; "SELECT " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_RECEIVED + " FROM " + MmsDatabase.TABLE_NAME +
String[] whereArgs = SqlUtil.buildArgs(1, 1); " WHERE " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.RECIPIENT_ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.RECIPIENT_ID +
" AND " + MmsDatabase.STORY_TYPE + " > 1 ORDER BY " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_RECEIVED + " DESC LIMIT 1" +
") as active_timestamp" +
" FROM " + TABLE_NAME +
" INNER JOIN " + ThreadDatabase.TABLE_NAME + " ON " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.RECIPIENT_ID + " = " + TABLE_NAME + "." + RECIPIENT_ID +
" WHERE " + ACTIVE + " = 1 " +
" AND (" +
SHOW_AS_STORY_STATE + " = " + ShowAsStoryState.ALWAYS.code +
" OR (" + SHOW_AS_STORY_STATE + " = " + ShowAsStoryState.IF_ACTIVE.code + " AND active_timestamp IS NOT NULL)" +
") ORDER BY active_timestamp DESC";
try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, selection, where, whereArgs, null, null, null, null)) { try (Cursor cursor = getReadableDatabase().query(query)) {
if (cursor == null || cursor.getCount() == 0) { if (cursor == null || cursor.getCount() == 0) {
return Collections.emptyList(); return Collections.emptyList();
} }
@ -1480,17 +1492,43 @@ public class GroupDatabase extends Database {
} }
} }
public void markDisplayAsStory(@NonNull GroupId groupId) { public @NonNull ShowAsStoryState getShowAsStoryState(@NonNull GroupId groupId) {
markDisplayAsStory(groupId, true); String[] projection = SqlUtil.buildArgs(SHOW_AS_STORY_STATE);
String where = GROUP_ID + " = ?";
String[] whereArgs = SqlUtil.buildArgs(groupId.toString());
try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, projection, where, whereArgs, null, null, null)) {
if (!cursor.moveToFirst()) {
throw new AssertionError("Group does not exist.");
}
int serializedState = CursorUtil.requireInt(cursor, SHOW_AS_STORY_STATE);
return ShowAsStoryState.deserialize(serializedState);
}
} }
public void markDisplayAsStory(@NonNull GroupId groupId, boolean displayAsStory) { public void setShowAsStoryState(@NonNull GroupId groupId, @NonNull ShowAsStoryState showAsStoryState) {
ContentValues contentValues = new ContentValues(1); ContentValues contentValues = new ContentValues(1);
contentValues.put(DISPLAY_AS_STORY, displayAsStory); contentValues.put(SHOW_AS_STORY_STATE, showAsStoryState.code);
getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", SqlUtil.buildArgs(groupId.toString())); getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", SqlUtil.buildArgs(groupId.toString()));
} }
public void setShowAsStoryState(@NonNull Collection<RecipientId> recipientIds, @NonNull ShowAsStoryState showAsStoryState) {
ContentValues contentValues = new ContentValues(1);
List<SqlUtil.Query> queries = SqlUtil.buildCollectionQuery(RECIPIENT_ID, recipientIds);
contentValues.put(SHOW_AS_STORY_STATE, showAsStoryState.code);
SQLiteDatabaseExtensionsKt.withinTransaction(getWritableDatabase(), db -> {
for (SqlUtil.Query query : queries) {
db.update(TABLE_NAME, contentValues, query.getWhere(), query.getWhereArgs());
}
return null;
});
}
public enum MemberSet { public enum MemberSet {
FULL_MEMBERS_INCLUDING_SELF(true, false), FULL_MEMBERS_INCLUDING_SELF(true, false),
FULL_MEMBERS_EXCLUDING_SELF(false, false), FULL_MEMBERS_EXCLUDING_SELF(false, false),
@ -1506,6 +1544,45 @@ public class GroupDatabase extends Database {
} }
} }
/**
* State object describing whether or not to display a story in a list.
*/
public enum ShowAsStoryState {
/**
* The default value. Display the group as a story if the group has stories in it currently.
*/
IF_ACTIVE(0),
/**
* Always display the group as a story unless explicitly removed. This state is entered if the
* user sends a story to a group or otherwise explicitly selects it to appear.
*/
ALWAYS(1),
/**
* Never display the story as a group. This state is entered if the user removes the group from
* their list, and is only navigated away from if the user explicitly adds the group again.
*/
NEVER(2);
private final int code;
ShowAsStoryState(int code) {
this.code = code;
}
private static @NonNull ShowAsStoryState deserialize(int code) {
switch (code) {
case 0:
return IF_ACTIVE;
case 1:
return ALWAYS;
case 2:
return NEVER;
default:
throw new IllegalArgumentException("Unknown code: " + code);
}
}
}
public enum MemberLevel { public enum MemberLevel {
NOT_A_MEMBER(false), NOT_A_MEMBER(false),
PENDING_MEMBER(false), PENDING_MEMBER(false),

Wyświetl plik

@ -50,6 +50,7 @@ import org.thoughtcrime.securesms.conversation.colors.ChatColorsMapper.getChatCo
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.database.GroupDatabase.LegacyGroupInsertException import org.thoughtcrime.securesms.database.GroupDatabase.LegacyGroupInsertException
import org.thoughtcrime.securesms.database.GroupDatabase.MissedGroupMigrationInsertException import org.thoughtcrime.securesms.database.GroupDatabase.MissedGroupMigrationInsertException
import org.thoughtcrime.securesms.database.GroupDatabase.ShowAsStoryState
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.distributionLists import org.thoughtcrime.securesms.database.SignalDatabase.Companion.distributionLists
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.groups import org.thoughtcrime.securesms.database.SignalDatabase.Companion.groups
@ -110,6 +111,7 @@ import org.whispersystems.signalservice.api.storage.SignalContactRecord
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record import org.whispersystems.signalservice.api.storage.SignalGroupV1Record
import org.whispersystems.signalservice.api.storage.SignalGroupV2Record import org.whispersystems.signalservice.api.storage.SignalGroupV2Record
import org.whispersystems.signalservice.api.storage.StorageId import org.whispersystems.signalservice.api.storage.StorageId
import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record
import java.io.Closeable import java.io.Closeable
import java.io.IOException import java.io.IOException
import java.util.Arrays import java.util.Arrays
@ -1076,6 +1078,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
.build() .build()
) )
groups.setShowAsStoryState(groupId, insert.storySendMode.toShowAsStoryState())
updateExtras(recipient.id) { updateExtras(recipient.id) {
it.setHideStory(insert.shouldHideStory()) it.setHideStory(insert.shouldHideStory())
} }
@ -1095,12 +1098,14 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
} }
val masterKey = update.old.masterKeyOrThrow val masterKey = update.old.masterKeyOrThrow
val recipient = Recipient.externalGroupExact(GroupId.v2(masterKey)) val groupId = GroupId.v2(masterKey)
val recipient = Recipient.externalGroupExact(groupId)
updateExtras(recipient.id) { updateExtras(recipient.id) {
it.setHideStory(update.new.shouldHideStory()) it.setHideStory(update.new.shouldHideStory())
} }
groups.setShowAsStoryState(groupId, update.new.storySendMode.toShowAsStoryState())
threads.applyStorageSyncUpdate(recipient.id, update.new) threads.applyStorageSyncUpdate(recipient.id, update.new)
recipient.live().refresh() recipient.live().refresh()
} }
@ -1209,6 +1214,15 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
} }
} }
private fun GroupV2Record.StorySendMode.toShowAsStoryState(): ShowAsStoryState {
return when (this) {
GroupV2Record.StorySendMode.DEFAULT -> ShowAsStoryState.IF_ACTIVE
GroupV2Record.StorySendMode.DISABLED -> ShowAsStoryState.NEVER
GroupV2Record.StorySendMode.ENABLED -> ShowAsStoryState.ALWAYS
GroupV2Record.StorySendMode.UNRECOGNIZED -> ShowAsStoryState.IF_ACTIVE
}
}
private fun getRecordForSync(query: String?, args: Array<String>?): List<RecipientRecord> { private fun getRecordForSync(query: String?, args: Array<String>?): List<RecipientRecord> {
val table = val table =
""" """

Wyświetl plik

@ -239,10 +239,6 @@ class MediaSelectionRepository(context: Context) {
val recipient = Recipient.resolved(contact.recipientId) val recipient = Recipient.resolved(contact.recipientId)
val isStory = contact.isStory || recipient.isDistributionList val isStory = contact.isStory || recipient.isDistributionList
if (isStory && recipient.isActiveGroup && recipient.isGroup) {
SignalDatabase.groups.markDisplayAsStory(recipient.requireGroupId())
}
if (isStory && !recipient.isMyStory) { if (isStory && !recipient.isMyStory) {
SignalStore.storyValues().setLatestStorySend(StorySend.newSend(recipient)) SignalStore.storyValues().setLatestStorySend(StorySend.newSend(recipient))
} }

Wyświetl plik

@ -68,10 +68,6 @@ class TextStoryPostSendRepository {
val recipient = Recipient.resolved(contact.requireShareContact().recipientId.get()) val recipient = Recipient.resolved(contact.requireShareContact().recipientId.get())
val isStory = contact is ContactSearchKey.RecipientSearchKey.Story || recipient.isDistributionList val isStory = contact is ContactSearchKey.RecipientSearchKey.Story || recipient.isDistributionList
if (isStory && recipient.isActiveGroup && recipient.isGroup) {
SignalDatabase.groups.markDisplayAsStory(recipient.requireGroupId())
}
if (isStory && !recipient.isMyStory) { if (isStory && !recipient.isMyStory) {
SignalStore.storyValues().setLatestStorySend(StorySend.newSend(recipient)) SignalStore.storyValues().setLatestStorySend(StorySend.newSend(recipient))
} }

Wyświetl plik

@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey;
import org.thoughtcrime.securesms.conversation.MessageSendType; import org.thoughtcrime.securesms.conversation.MessageSendType;
import org.thoughtcrime.securesms.conversation.colors.ChatColors; import org.thoughtcrime.securesms.conversation.colors.ChatColors;
import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.Mention; import org.thoughtcrime.securesms.database.model.Mention;
@ -232,10 +233,6 @@ public final class MultiShareSender {
storyType = StoryType.STORY_WITH_REPLIES; storyType = StoryType.STORY_WITH_REPLIES;
} }
if (recipient.isActiveGroup() && recipient.isGroup()) {
SignalDatabase.groups().markDisplayAsStory(recipient.requireGroupId());
}
if (!recipient.isMyStory()) { if (!recipient.isMyStory()) {
SignalStore.storyValues().setLatestStorySend(StorySend.newSend(recipient)); SignalStore.storyValues().setLatestStorySend(StorySend.newSend(recipient));
} }

Wyświetl plik

@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupsV1MigrationUtil; import org.thoughtcrime.securesms.groups.GroupsV1MigrationUtil;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.signalservice.api.storage.SignalGroupV2Record; import org.whispersystems.signalservice.api.storage.SignalGroupV2Record;
import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
@ -66,17 +67,18 @@ public final class GroupV2RecordProcessor extends DefaultStorageRecordProcessor<
@Override @Override
@NonNull SignalGroupV2Record merge(@NonNull SignalGroupV2Record remote, @NonNull SignalGroupV2Record local, @NonNull StorageKeyGenerator keyGenerator) { @NonNull SignalGroupV2Record merge(@NonNull SignalGroupV2Record remote, @NonNull SignalGroupV2Record local, @NonNull StorageKeyGenerator keyGenerator) {
byte[] unknownFields = remote.serializeUnknownFields(); byte[] unknownFields = remote.serializeUnknownFields();
boolean blocked = remote.isBlocked(); boolean blocked = remote.isBlocked();
boolean profileSharing = remote.isProfileSharingEnabled(); boolean profileSharing = remote.isProfileSharingEnabled();
boolean archived = remote.isArchived(); boolean archived = remote.isArchived();
boolean forcedUnread = remote.isForcedUnread(); boolean forcedUnread = remote.isForcedUnread();
long muteUntil = remote.getMuteUntil(); long muteUntil = remote.getMuteUntil();
boolean notifyForMentionsWhenMuted = remote.notifyForMentionsWhenMuted(); boolean notifyForMentionsWhenMuted = remote.notifyForMentionsWhenMuted();
boolean hideStory = remote.shouldHideStory(); boolean hideStory = remote.shouldHideStory();
GroupV2Record.StorySendMode storySendMode = remote.getStorySendMode();
boolean matchesRemote = doParamsMatch(remote, unknownFields, blocked, profileSharing, archived, forcedUnread, muteUntil, notifyForMentionsWhenMuted, hideStory); boolean matchesRemote = doParamsMatch(remote, unknownFields, blocked, profileSharing, archived, forcedUnread, muteUntil, notifyForMentionsWhenMuted, hideStory, storySendMode);
boolean matchesLocal = doParamsMatch(local, unknownFields, blocked, profileSharing, archived, forcedUnread, muteUntil, notifyForMentionsWhenMuted, hideStory); boolean matchesLocal = doParamsMatch(local, unknownFields, blocked, profileSharing, archived, forcedUnread, muteUntil, notifyForMentionsWhenMuted, hideStory, storySendMode);
if (matchesRemote) { if (matchesRemote) {
return remote; return remote;
@ -91,6 +93,7 @@ public final class GroupV2RecordProcessor extends DefaultStorageRecordProcessor<
.setMuteUntil(muteUntil) .setMuteUntil(muteUntil)
.setNotifyForMentionsWhenMuted(notifyForMentionsWhenMuted) .setNotifyForMentionsWhenMuted(notifyForMentionsWhenMuted)
.setHideStory(hideStory) .setHideStory(hideStory)
.setStorySendMode(storySendMode)
.build(); .build();
} }
} }
@ -139,7 +142,8 @@ public final class GroupV2RecordProcessor extends DefaultStorageRecordProcessor<
boolean forcedUnread, boolean forcedUnread,
long muteUntil, long muteUntil,
boolean notifyForMentionsWhenMuted, boolean notifyForMentionsWhenMuted,
boolean hideStory) boolean hideStory,
@NonNull GroupV2Record.StorySendMode storySendMode)
{ {
return Arrays.equals(unknownFields, group.serializeUnknownFields()) && return Arrays.equals(unknownFields, group.serializeUnknownFields()) &&
blocked == group.isBlocked() && blocked == group.isBlocked() &&
@ -148,6 +152,7 @@ public final class GroupV2RecordProcessor extends DefaultStorageRecordProcessor<
forcedUnread == group.isForcedUnread() && forcedUnread == group.isForcedUnread() &&
muteUntil == group.getMuteUntil() && muteUntil == group.getMuteUntil() &&
notifyForMentionsWhenMuted == group.notifyForMentionsWhenMuted() && notifyForMentionsWhenMuted == group.notifyForMentionsWhenMuted() &&
hideStory == group.shouldHideStory(); hideStory == group.shouldHideStory() &&
storySendMode == group.getStorySendMode();
} }
} }

Wyświetl plik

@ -6,6 +6,7 @@ import androidx.annotation.Nullable;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey; import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
@ -31,6 +32,7 @@ import org.whispersystems.signalservice.api.subscriptions.SubscriberId;
import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord; import org.whispersystems.signalservice.internal.storage.protos.AccountRecord;
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord.IdentityState; import org.whispersystems.signalservice.internal.storage.protos.ContactRecord.IdentityState;
import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -163,7 +165,20 @@ public final class StorageSyncModels {
throw new AssertionError("Group master key not on recipient record"); throw new AssertionError("Group master key not on recipient record");
} }
boolean hideStory = recipient.getExtras() != null && recipient.getExtras().hideStory(); boolean hideStory = recipient.getExtras() != null && recipient.getExtras().hideStory();
GroupDatabase.ShowAsStoryState showAsStoryState = SignalDatabase.groups().getShowAsStoryState(groupId);
GroupV2Record.StorySendMode storySendMode;
switch (showAsStoryState) {
case ALWAYS:
storySendMode = GroupV2Record.StorySendMode.ENABLED;
break;
case NEVER:
storySendMode = GroupV2Record.StorySendMode.DISABLED;
break;
default:
storySendMode = GroupV2Record.StorySendMode.DEFAULT;
}
return new SignalGroupV2Record.Builder(rawStorageId, groupMasterKey, recipient.getSyncExtras().getStorageProto()) return new SignalGroupV2Record.Builder(rawStorageId, groupMasterKey, recipient.getSyncExtras().getStorageProto())
.setBlocked(recipient.isBlocked()) .setBlocked(recipient.isBlocked())
@ -173,6 +188,7 @@ public final class StorageSyncModels {
.setMuteUntil(recipient.getMuteUntil()) .setMuteUntil(recipient.getMuteUntil())
.setNotifyForMentionsWhenMuted(recipient.getMentionSetting() == RecipientDatabase.MentionSetting.ALWAYS_NOTIFY) .setNotifyForMentionsWhenMuted(recipient.getMentionSetting() == RecipientDatabase.MentionSetting.ALWAYS_NOTIFY)
.setHideStory(hideStory) .setHideStory(hideStory)
.setStorySendMode(storySendMode)
.build(); .build();
} }

Wyświetl plik

@ -3,13 +3,18 @@ package org.thoughtcrime.securesms.stories.settings.group
import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.database.GroupDatabase
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.storage.StorageSyncHelper
class GroupStorySettingsRepository { class GroupStorySettingsRepository {
fun unmarkAsGroupStory(groupId: GroupId): Completable { fun unmarkAsGroupStory(groupId: GroupId): Completable {
return Completable.fromAction { return Completable.fromAction {
SignalDatabase.groups.markDisplayAsStory(groupId, false) SignalDatabase.groups.setShowAsStoryState(groupId, GroupDatabase.ShowAsStoryState.NEVER)
SignalDatabase.recipients.markNeedsSync(Recipient.externalGroupExact(groupId).id)
StorageSyncHelper.scheduleSyncForDataChange()
}.subscribeOn(Schedulers.io()) }.subscribeOn(Schedulers.io())
} }

Wyświetl plik

@ -2,18 +2,20 @@ package org.thoughtcrime.securesms.stories.settings.story
import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.database.GroupDatabase
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.stories.Stories import org.thoughtcrime.securesms.stories.Stories
class StoriesPrivacySettingsRepository { class StoriesPrivacySettingsRepository {
fun markGroupsAsStories(groups: List<RecipientId>): Completable { fun markGroupsAsStories(groups: List<RecipientId>): Completable {
return Completable.fromCallable { return Completable.fromCallable {
groups SignalDatabase.groups.setShowAsStoryState(groups, GroupDatabase.ShowAsStoryState.ALWAYS)
.map { Recipient.resolved(it) } SignalDatabase.recipients.markNeedsSync(groups)
.forEach { SignalDatabase.groups.markDisplayAsStory(it.requireGroupId()) } StorageSyncHelper.scheduleSyncForDataChange()
} }
} }

Wyświetl plik

@ -1,12 +1,9 @@
package org.signal.core.util package org.signal.core.util
import androidx.sqlite.db.SupportSQLiteDatabase
import android.content.ContentValues import android.content.ContentValues
import android.text.TextUtils import android.text.TextUtils
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import java.lang.NullPointerException import androidx.sqlite.db.SupportSQLiteDatabase
import java.lang.StringBuilder
import java.util.ArrayList
import java.util.LinkedList import java.util.LinkedList
import java.util.Locale import java.util.Locale
import java.util.stream.Collectors import java.util.stream.Collectors

Wyświetl plik

@ -82,6 +82,10 @@ public final class SignalGroupV2Record implements SignalRecord {
diff.add("HideStory"); diff.add("HideStory");
} }
if (!Objects.equals(this.getStorySendMode(), that.getStorySendMode())) {
diff.add("StorySendMode");
}
if (!Objects.equals(this.hasUnknownFields(), that.hasUnknownFields())) { if (!Objects.equals(this.hasUnknownFields(), that.hasUnknownFields())) {
diff.add("UnknownFields"); diff.add("UnknownFields");
} }
@ -140,6 +144,10 @@ public final class SignalGroupV2Record implements SignalRecord {
return proto.getHideStory(); return proto.getHideStory();
} }
public GroupV2Record.StorySendMode getStorySendMode() {
return proto.getStorySendMode();
}
public GroupV2Record toProto() { public GroupV2Record toProto() {
return proto; return proto;
} }
@ -213,6 +221,11 @@ public final class SignalGroupV2Record implements SignalRecord {
return this; return this;
} }
public Builder setStorySendMode(GroupV2Record.StorySendMode storySendMode) {
builder.setStorySendMode(storySendMode);
return this;
}
private static GroupV2Record.Builder parseUnknowns(byte[] serializedUnknowns) { private static GroupV2Record.Builder parseUnknowns(byte[] serializedUnknowns) {
try { try {
return GroupV2Record.parseFrom(serializedUnknowns).toBuilder(); return GroupV2Record.parseFrom(serializedUnknowns).toBuilder();

Wyświetl plik

@ -100,14 +100,22 @@ message GroupV1Record {
} }
message GroupV2Record { message GroupV2Record {
bytes masterKey = 1; enum StorySendMode {
bool blocked = 2; DEFAULT = 0;
bool whitelisted = 3; DISABLED = 1;
bool archived = 4; ENABLED = 2;
bool markedUnread = 5; }
uint64 mutedUntilTimestamp = 6;
bool dontNotifyForMentionsIfMuted = 7; bytes masterKey = 1;
bool hideStory = 8; bool blocked = 2;
bool whitelisted = 3;
bool archived = 4;
bool markedUnread = 5;
uint64 mutedUntilTimestamp = 6;
bool dontNotifyForMentionsIfMuted = 7;
bool hideStory = 8;
reserved /* storySendEnabled */ 9;
StorySendMode storySendMode = 10;
} }
message Payments { message Payments {