kopia lustrzana https://github.com/ryukoposting/Signal-Android
Add support for new group story display states.
rodzic
8ca94eb3d5
commit
c47a724654
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
)
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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 =
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Ładowanie…
Reference in New Issue