mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 09:49:30 +01:00
Implement Stories feature behind flag.
Co-Authored-By: Greyson Parrelli <37311915+greyson-signal@users.noreply.github.com> Co-Authored-By: Rashad Sookram <95182499+rashad-signal@users.noreply.github.com>
This commit is contained in:
@@ -13,11 +13,13 @@ import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.DistributionListDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.RecipientRecord;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListRecord;
|
||||
import org.thoughtcrime.securesms.database.model.RecipientRecord;
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
@@ -40,16 +42,18 @@ public final class LiveRecipient {
|
||||
private final AtomicReference<Recipient> recipient;
|
||||
private final RecipientDatabase recipientDatabase;
|
||||
private final GroupDatabase groupDatabase;
|
||||
private final DistributionListDatabase distributionListDatabase;
|
||||
private final MutableLiveData<Object> refreshForceNotify;
|
||||
|
||||
LiveRecipient(@NonNull Context context, @NonNull Recipient defaultRecipient) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.liveData = new MutableLiveData<>(defaultRecipient);
|
||||
this.recipient = new AtomicReference<>(defaultRecipient);
|
||||
this.recipientDatabase = SignalDatabase.recipients();
|
||||
this.groupDatabase = SignalDatabase.groups();
|
||||
this.observers = new CopyOnWriteArraySet<>();
|
||||
this.foreverObserver = recipient -> {
|
||||
this.context = context.getApplicationContext();
|
||||
this.liveData = new MutableLiveData<>(defaultRecipient);
|
||||
this.recipient = new AtomicReference<>(defaultRecipient);
|
||||
this.recipientDatabase = SignalDatabase.recipients();
|
||||
this.groupDatabase = SignalDatabase.groups();
|
||||
this.distributionListDatabase = SignalDatabase.distributionLists();
|
||||
this.observers = new CopyOnWriteArraySet<>();
|
||||
this.foreverObserver = recipient -> {
|
||||
ThreadUtil.postToMain(() -> {
|
||||
for (RecipientForeverObserver o : observers) {
|
||||
o.onRecipientChanged(recipient);
|
||||
@@ -192,9 +196,15 @@ public final class LiveRecipient {
|
||||
}
|
||||
|
||||
private @NonNull Recipient fetchAndCacheRecipientFromDisk(@NonNull RecipientId id) {
|
||||
RecipientRecord settings = recipientDatabase.getRecord(id);
|
||||
RecipientDetails details = settings.getGroupId() != null ? getGroupRecipientDetails(settings)
|
||||
: RecipientDetails.forIndividual(context, settings);
|
||||
RecipientRecord record = recipientDatabase.getRecord(id);
|
||||
RecipientDetails details;
|
||||
if (record.getGroupId() != null) {
|
||||
details = getGroupRecipientDetails(record);
|
||||
} else if (record.getDistributionListId() != null) {
|
||||
details = getDistributionListRecipientDetails(record);
|
||||
} else {
|
||||
details = RecipientDetails.forIndividual(context, record);
|
||||
}
|
||||
|
||||
Recipient recipient = new Recipient(id, details, true);
|
||||
RecipientIdCache.INSTANCE.put(recipient);
|
||||
@@ -202,8 +212,8 @@ public final class LiveRecipient {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private @NonNull RecipientDetails getGroupRecipientDetails(@NonNull RecipientRecord settings) {
|
||||
Optional<GroupRecord> groupRecord = groupDatabase.getGroup(settings.getId());
|
||||
private @NonNull RecipientDetails getGroupRecipientDetails(@NonNull RecipientRecord record) {
|
||||
Optional<GroupRecord> groupRecord = groupDatabase.getGroup(record.getId());
|
||||
|
||||
if (groupRecord.isPresent()) {
|
||||
String title = groupRecord.get().getTitle();
|
||||
@@ -214,10 +224,25 @@ public final class LiveRecipient {
|
||||
avatarId = Optional.of(groupRecord.get().getAvatarId());
|
||||
}
|
||||
|
||||
return new RecipientDetails(title, null, avatarId, false, false, settings.getRegistered(), settings, members, false);
|
||||
return new RecipientDetails(title, null, avatarId, false, false, record.getRegistered(), record, members, false);
|
||||
}
|
||||
|
||||
return new RecipientDetails(null, null, Optional.absent(), false, false, settings.getRegistered(), settings, null, false);
|
||||
return new RecipientDetails(null, null, Optional.absent(), false, false, record.getRegistered(), record, null, false);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private @NonNull RecipientDetails getDistributionListRecipientDetails(@NonNull RecipientRecord record) {
|
||||
DistributionListRecord groupRecord = distributionListDatabase.getList(Objects.requireNonNull(record.getDistributionListId()));
|
||||
|
||||
// TODO [stories] We'll have to see what the perf is like for very large distribution lists. We may not be able to support fetching all the members.
|
||||
if (groupRecord != null) {
|
||||
String title = groupRecord.getName();
|
||||
List<Recipient> members = Stream.of(groupRecord.getMembers()).filterNot(RecipientId::isUnknown).map(this::fetchAndCacheRecipientFromDisk).toList();
|
||||
|
||||
return RecipientDetails.forDistributionList(title, members, record);
|
||||
}
|
||||
|
||||
return RecipientDetails.forDistributionList(null, null, record);
|
||||
}
|
||||
|
||||
synchronized void set(@NonNull Recipient recipient) {
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.RecipientExtras;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
@@ -48,8 +49,8 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.libsignal.util.guava.Preconditions;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.PNI;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
|
||||
@@ -70,7 +71,7 @@ public class Recipient {
|
||||
|
||||
private static final String TAG = Log.tag(Recipient.class);
|
||||
|
||||
public static final Recipient UNKNOWN = new Recipient(RecipientId.UNKNOWN, new RecipientDetails(), true);
|
||||
public static final Recipient UNKNOWN = new Recipient(RecipientId.UNKNOWN, RecipientDetails.forUnknown(), true);
|
||||
|
||||
public static final FallbackPhotoProvider DEFAULT_FALLBACK_PHOTO_PROVIDER = new FallbackPhotoProvider();
|
||||
|
||||
@@ -84,6 +85,7 @@ public class Recipient {
|
||||
private final String e164;
|
||||
private final String email;
|
||||
private final GroupId groupId;
|
||||
private final DistributionListId distributionListId;
|
||||
private final List<Recipient> participants;
|
||||
private final Optional<Long> groupAvatarId;
|
||||
private final boolean isSelf;
|
||||
@@ -115,6 +117,7 @@ public class Recipient {
|
||||
private final Capability senderKeyCapability;
|
||||
private final Capability announcementGroupCapability;
|
||||
private final Capability changeNumberCapability;
|
||||
private final Capability storiesCapability;
|
||||
private final InsightsBannerTier insightsBannerTier;
|
||||
private final byte[] storageId;
|
||||
private final MentionSetting mentionSetting;
|
||||
@@ -162,6 +165,12 @@ public class Recipient {
|
||||
return recipients;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static @NonNull Recipient distributionList(@NonNull DistributionListId distributionListId) {
|
||||
RecipientId id = SignalDatabase.recipients().getOrInsertFromDistributionListId(distributionListId);
|
||||
return resolved(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a fully-populated {@link Recipient} and associates it with the provided username.
|
||||
*/
|
||||
@@ -343,6 +352,7 @@ public class Recipient {
|
||||
this.e164 = null;
|
||||
this.email = null;
|
||||
this.groupId = null;
|
||||
this.distributionListId = null;
|
||||
this.participants = Collections.emptyList();
|
||||
this.groupAvatarId = Optional.absent();
|
||||
this.isSelf = false;
|
||||
@@ -375,6 +385,7 @@ public class Recipient {
|
||||
this.senderKeyCapability = Capability.UNKNOWN;
|
||||
this.announcementGroupCapability = Capability.UNKNOWN;
|
||||
this.changeNumberCapability = Capability.UNKNOWN;
|
||||
this.storiesCapability = Capability.UNKNOWN;
|
||||
this.storageId = null;
|
||||
this.mentionSetting = MentionSetting.ALWAYS_NOTIFY;
|
||||
this.wallpaper = null;
|
||||
@@ -399,6 +410,7 @@ public class Recipient {
|
||||
this.e164 = details.e164;
|
||||
this.email = details.email;
|
||||
this.groupId = details.groupId;
|
||||
this.distributionListId = details.distributionListId;
|
||||
this.participants = details.participants;
|
||||
this.groupAvatarId = details.groupAvatarId;
|
||||
this.isSelf = details.isSelf;
|
||||
@@ -431,6 +443,7 @@ public class Recipient {
|
||||
this.senderKeyCapability = details.senderKeyCapability;
|
||||
this.announcementGroupCapability = details.announcementGroupCapability;
|
||||
this.changeNumberCapability = details.changeNumberCapability;
|
||||
this.storiesCapability = details.storiesCapability;
|
||||
this.storageId = details.storageId;
|
||||
this.mentionSetting = details.mentionSetting;
|
||||
this.wallpaper = details.wallpaper;
|
||||
@@ -492,6 +505,8 @@ public class Recipient {
|
||||
}
|
||||
|
||||
return Util.join(names, ", ");
|
||||
} else if (isMyStory()) {
|
||||
return context.getString(R.string.Recipient_my_story);
|
||||
} else {
|
||||
return this.groupName;
|
||||
}
|
||||
@@ -642,6 +657,10 @@ public class Recipient {
|
||||
return Optional.fromNullable(groupId);
|
||||
}
|
||||
|
||||
public @NonNull Optional<DistributionListId> getDistributionListId() {
|
||||
return Optional.fromNullable(distributionListId);
|
||||
}
|
||||
|
||||
public @NonNull Optional<String> getSmsAddress() {
|
||||
return Optional.fromNullable(e164).or(Optional.fromNullable(email));
|
||||
}
|
||||
@@ -704,6 +723,10 @@ public class Recipient {
|
||||
return hasServiceId() && !hasSmsAddress();
|
||||
}
|
||||
|
||||
public boolean shouldHideStory() {
|
||||
return extras.transform(Extras::hideStory).or(false);
|
||||
}
|
||||
|
||||
public @NonNull GroupId requireGroupId() {
|
||||
GroupId resolved = resolving ? resolve().groupId : groupId;
|
||||
|
||||
@@ -714,6 +737,16 @@ public class Recipient {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public @NonNull DistributionListId requireDistributionListId() {
|
||||
DistributionListId resolved = resolving ? resolve().distributionListId : distributionListId;
|
||||
|
||||
if (resolved == null) {
|
||||
throw new MissingAddressError(id);
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link ServiceId} of the user if available, otherwise throw.
|
||||
*/
|
||||
@@ -796,6 +829,14 @@ public class Recipient {
|
||||
return groupId != null && groupId.isV2();
|
||||
}
|
||||
|
||||
public boolean isDistributionList() {
|
||||
return resolve().distributionListId != null;
|
||||
}
|
||||
|
||||
public boolean isMyStory() {
|
||||
return Objects.equals(resolve().distributionListId, DistributionListId.from(DistributionListId.MY_STORY_ID));
|
||||
}
|
||||
|
||||
public boolean isActiveGroup() {
|
||||
return Stream.of(getParticipants()).anyMatch(Recipient::isSelf);
|
||||
}
|
||||
@@ -835,6 +876,7 @@ public class Recipient {
|
||||
public @NonNull FallbackContactPhoto getFallbackContactPhoto(@NonNull FallbackPhotoProvider fallbackPhotoProvider, int targetSize) {
|
||||
if (isSelf) return fallbackPhotoProvider.getPhotoForLocalNumber();
|
||||
else if (isResolving()) return fallbackPhotoProvider.getPhotoForResolvingRecipient();
|
||||
else if (isDistributionList()) return fallbackPhotoProvider.getPhotoForDistributionList();
|
||||
else if (isGroupInternal()) return fallbackPhotoProvider.getPhotoForGroup();
|
||||
else if (isGroup()) return fallbackPhotoProvider.getPhotoForGroup();
|
||||
else if (!TextUtils.isEmpty(groupName)) return fallbackPhotoProvider.getPhotoForRecipientWithName(groupName, targetSize);
|
||||
@@ -947,6 +989,10 @@ public class Recipient {
|
||||
return changeNumberCapability;
|
||||
}
|
||||
|
||||
public @NonNull Capability getStoriesCapability() {
|
||||
return storiesCapability;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if this recipient supports the message retry system, or false if we should use the legacy session reset system.
|
||||
*/
|
||||
@@ -1170,17 +1216,21 @@ public class Recipient {
|
||||
return recipientExtras.getManuallyShownAvatar();
|
||||
}
|
||||
|
||||
public boolean hideStory() {
|
||||
return recipientExtras.getHideStory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
final Extras that = (Extras) o;
|
||||
return manuallyShownAvatar() == that.manuallyShownAvatar();
|
||||
return manuallyShownAvatar() == that.manuallyShownAvatar() && hideStory() == that.hideStory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(manuallyShownAvatar());
|
||||
return Objects.hash(manuallyShownAvatar(), hideStory());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1269,6 +1319,10 @@ public class Recipient {
|
||||
public @NonNull FallbackContactPhoto getPhotoForRecipientWithoutName() {
|
||||
return new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20, R.drawable.ic_profile_outline_48);
|
||||
}
|
||||
|
||||
public @NonNull FallbackContactPhoto getPhotoForDistributionList() {
|
||||
return new ResourceContactPhoto(R.drawable.ic_group_outline_34, R.drawable.ic_group_outline_20, R.drawable.ic_group_outline_48);
|
||||
}
|
||||
}
|
||||
|
||||
private static class MissingAddressError extends AssertionError {
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase.InsightsBannerTier;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase.MentionSetting;
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId;
|
||||
import org.thoughtcrime.securesms.database.model.RecipientRecord;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode;
|
||||
@@ -39,6 +40,7 @@ public class RecipientDetails {
|
||||
final String e164;
|
||||
final String email;
|
||||
final GroupId groupId;
|
||||
final DistributionListId distributionListId;
|
||||
final String groupName;
|
||||
final String systemContactName;
|
||||
final String customLabel;
|
||||
@@ -72,6 +74,7 @@ public class RecipientDetails {
|
||||
final Recipient.Capability senderKeyCapability;
|
||||
final Recipient.Capability announcementGroupCapability;
|
||||
final Recipient.Capability changeNumberCapability;
|
||||
final Recipient.Capability storiesCapability;
|
||||
final InsightsBannerTier insightsBannerTier;
|
||||
final byte[] storageId;
|
||||
final MentionSetting mentionSetting;
|
||||
@@ -106,6 +109,7 @@ public class RecipientDetails {
|
||||
this.e164 = record.getE164();
|
||||
this.email = record.getEmail();
|
||||
this.groupId = record.getGroupId();
|
||||
this.distributionListId = record.getDistributionListId();
|
||||
this.messageRingtone = record.getMessageRingtone();
|
||||
this.callRingtone = record.getCallRingtone();
|
||||
this.mutedUntil = record.getMuteUntil();
|
||||
@@ -133,6 +137,7 @@ public class RecipientDetails {
|
||||
this.senderKeyCapability = record.getSenderKeyCapability();
|
||||
this.announcementGroupCapability = record.getAnnouncementGroupCapability();
|
||||
this.changeNumberCapability = record.getChangeNumberCapability();
|
||||
this.storiesCapability = record.getStoriesCapability();
|
||||
this.insightsBannerTier = record.getInsightsBannerTier();
|
||||
this.storageId = record.getStorageId();
|
||||
this.mentionSetting = record.getMentionSetting();
|
||||
@@ -150,10 +155,7 @@ public class RecipientDetails {
|
||||
this.isReleaseChannel = isReleaseChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only used for {@link Recipient#UNKNOWN}.
|
||||
*/
|
||||
RecipientDetails() {
|
||||
private RecipientDetails() {
|
||||
this.groupAvatarId = null;
|
||||
this.systemContactPhoto = null;
|
||||
this.customLabel = null;
|
||||
@@ -164,6 +166,7 @@ public class RecipientDetails {
|
||||
this.e164 = null;
|
||||
this.email = null;
|
||||
this.groupId = null;
|
||||
this.distributionListId = null;
|
||||
this.messageRingtone = null;
|
||||
this.callRingtone = null;
|
||||
this.mutedUntil = 0;
|
||||
@@ -193,6 +196,7 @@ public class RecipientDetails {
|
||||
this.senderKeyCapability = Recipient.Capability.UNKNOWN;
|
||||
this.announcementGroupCapability = Recipient.Capability.UNKNOWN;
|
||||
this.changeNumberCapability = Recipient.Capability.UNKNOWN;
|
||||
this.storiesCapability = Recipient.Capability.UNKNOWN;
|
||||
this.storageId = null;
|
||||
this.mentionSetting = MentionSetting.ALWAYS_NOTIFY;
|
||||
this.wallpaper = null;
|
||||
@@ -226,4 +230,12 @@ public class RecipientDetails {
|
||||
|
||||
return new RecipientDetails(null, settings.getSystemDisplayName(), Optional.absent(), systemContact, isSelf, registeredState, settings, null, isReleaseChannel);
|
||||
}
|
||||
|
||||
public static @NonNull RecipientDetails forDistributionList(String title, @Nullable List<Recipient> members, @NonNull RecipientRecord record) {
|
||||
return new RecipientDetails(title, null, Optional.absent(), false, false, record.getRegistered(), record, members, false);
|
||||
}
|
||||
|
||||
public static @NonNull RecipientDetails forUnknown() {
|
||||
return new RecipientDetails();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.database.model.DatabaseId;
|
||||
import org.thoughtcrime.securesms.util.DelimiterUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
@@ -22,7 +23,7 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class RecipientId implements Parcelable, Comparable<RecipientId> {
|
||||
public class RecipientId implements Parcelable, Comparable<RecipientId>, DatabaseId {
|
||||
|
||||
private static final long UNKNOWN_ID = -1;
|
||||
private static final char DELIMITER = ',';
|
||||
@@ -141,6 +142,7 @@ public class RecipientId implements Parcelable, Comparable<RecipientId> {
|
||||
return id == UNKNOWN_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String serialize() {
|
||||
return String.valueOf(id);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.avatar.view.AvatarView;
|
||||
import org.thoughtcrime.securesms.badges.BadgeImageView;
|
||||
import org.thoughtcrime.securesms.badges.view.ViewBadgeBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
@@ -69,7 +70,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
|
||||
private static final String ARGS_GROUP_ID = "GROUP_ID";
|
||||
|
||||
private RecipientDialogViewModel viewModel;
|
||||
private AvatarImageView avatar;
|
||||
private AvatarView avatar;
|
||||
private TextView fullName;
|
||||
private TextView about;
|
||||
private TextView usernameNumber;
|
||||
@@ -160,7 +161,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
|
||||
return new FallbackPhoto80dp(R.drawable.ic_note_80, recipient.getAvatarColor());
|
||||
}
|
||||
});
|
||||
avatar.setAvatar(recipient);
|
||||
avatar.displayChatAvatar(recipient);
|
||||
|
||||
if (!recipient.isSelf()) {
|
||||
badgeImageView.setBadgeFromRecipient(recipient);
|
||||
|
||||
Reference in New Issue
Block a user