Implement ability to view badges and modify whether they appear.

Note: this is available in staging only.
This commit is contained in:
Alex Hart
2021-09-20 17:05:31 -03:00
parent 556ca5a573
commit 77cf029fdc
48 changed files with 1880 additions and 100 deletions

View File

@@ -0,0 +1,7 @@
package org.thoughtcrime.securesms.util
import org.thoughtcrime.securesms.BuildConfig
object Environment {
const val IS_STAGING: Boolean = BuildConfig.BUILD_ENVIRONMENT_TYPE == "Staging"
}

View File

@@ -84,6 +84,7 @@ public final class FeatureFlags {
private static final String MAX_GROUP_CALL_RING_SIZE = "global.calling.maxGroupCallRingSize";
private static final String GROUP_CALL_RINGING = "android.calling.groupCallRinging";
private static final String CHANGE_NUMBER_ENABLED = "android.changeNumber";
private static final String DONOR_BADGES = "android.donorBadges";
/**
* We will only store remote values for flags in this set. If you want a flag to be controllable
@@ -126,7 +127,8 @@ public final class FeatureFlags {
@VisibleForTesting
static final Set<String> NOT_REMOTE_CAPABLE = SetUtil.newHashSet(
PHONE_NUMBER_PRIVACY_VERSION,
CHANGE_NUMBER_ENABLED
CHANGE_NUMBER_ENABLED,
DONOR_BADGES
);
/**
@@ -394,11 +396,20 @@ public final class FeatureFlags {
return getBoolean(GROUP_CALL_RINGING, false);
}
/** Weather or not to show change number in the UI. */
/** Whether or not to show change number in the UI. */
public static boolean changeNumber() {
return getBoolean(CHANGE_NUMBER_ENABLED, false);
}
/** Whether or not to show donor badges in the UI. */
public static boolean donorBadges() {
if (Environment.IS_STAGING) {
return true;
} else {
return getBoolean(DONOR_BADGES, false);
}
}
/** Only for rendering debug info. */
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
return new TreeMap<>(REMOTE_VALUES);

View File

@@ -9,6 +9,7 @@ import androidx.annotation.WorkerThread;
import org.signal.core.util.logging.Log;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.profiles.ProfileKey;
import org.thoughtcrime.securesms.badges.models.Badge;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
@@ -42,6 +43,8 @@ import org.whispersystems.signalservice.internal.ServiceResponse;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import io.reactivex.rxjava3.core.Single;
@@ -60,12 +63,22 @@ public final class ProfileUtil {
@NonNull Recipient recipient,
@NonNull SignalServiceProfile.RequestType requestType)
throws IOException
{
return retrieveProfileSync(context, recipient, requestType, true);
}
@WorkerThread
public static @NonNull ProfileAndCredential retrieveProfileSync(@NonNull Context context,
@NonNull Recipient recipient,
@NonNull SignalServiceProfile.RequestType requestType,
boolean allowUnidentifiedAccess)
throws IOException
{
ProfileService profileService = new ProfileService(ApplicationDependencies.getGroupsV2Operations().getProfileOperations(),
ApplicationDependencies.getSignalServiceMessageReceiver(),
ApplicationDependencies.getSignalWebSocket());
Pair<Recipient, ServiceResponse<ProfileAndCredential>> response = retrieveProfile(context, recipient, requestType, profileService).blockingGet();
Pair<Recipient, ServiceResponse<ProfileAndCredential>> response = retrieveProfile(context, recipient, requestType, profileService, allowUnidentifiedAccess).blockingGet();
return new ProfileService.ProfileResponseProcessor(response.second()).getResultOrThrow();
}
@@ -74,7 +87,16 @@ public final class ProfileUtil {
@NonNull SignalServiceProfile.RequestType requestType,
@NonNull ProfileService profileService)
{
Optional<UnidentifiedAccess> unidentifiedAccess = getUnidentifiedAccess(context, recipient);
return retrieveProfile(context, recipient, requestType, profileService, true);
}
private static Single<Pair<Recipient, ServiceResponse<ProfileAndCredential>>> retrieveProfile(@NonNull Context context,
@NonNull Recipient recipient,
@NonNull SignalServiceProfile.RequestType requestType,
@NonNull ProfileService profileService,
boolean allowUnidentifiedAccess)
{
Optional<UnidentifiedAccess> unidentifiedAccess = allowUnidentifiedAccess ? getUnidentifiedAccess(context, recipient) : Optional.absent();
Optional<ProfileKey> profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey());
return Single.fromCallable(() -> toSignalServiceAddress(context, recipient))
@@ -163,6 +185,23 @@ public final class ProfileUtil {
return profileKey;
}
/**
* Uploads the profile based on all state that's written to disk, except we'll use the provided
* list of badges instead. This is useful when you want to ensure that the profile has been uploaded
* successfully before persisting the change to disk.
*/
public static void uploadProfileWithBadges(@NonNull Context context, @NonNull List<Badge> badges) throws IOException {
try (StreamDetails avatar = AvatarHelper.getSelfProfileAvatarStream(context)) {
uploadProfile(context,
Recipient.self().getProfileName(),
Optional.fromNullable(Recipient.self().getAbout()).or(""),
Optional.fromNullable(Recipient.self().getAboutEmoji()).or(""),
getSelfPaymentsAddressProtobuf(),
avatar,
badges);
}
}
/**
* Uploads the profile based on all state that's written to disk, except we'll use the provided
* profile name instead. This is useful when you want to ensure that the profile has been uploaded
@@ -175,7 +214,8 @@ public final class ProfileUtil {
Optional.fromNullable(Recipient.self().getAbout()).or(""),
Optional.fromNullable(Recipient.self().getAboutEmoji()).or(""),
getSelfPaymentsAddressProtobuf(),
avatar);
avatar,
Recipient.self().getBadges());
}
}
@@ -191,7 +231,8 @@ public final class ProfileUtil {
about,
emoji,
getSelfPaymentsAddressProtobuf(),
avatar);
avatar,
Recipient.self().getBadges());
}
}
@@ -215,7 +256,8 @@ public final class ProfileUtil {
Optional.fromNullable(Recipient.self().getAbout()).or(""),
Optional.fromNullable(Recipient.self().getAboutEmoji()).or(""),
getSelfPaymentsAddressProtobuf(),
avatar);
avatar,
Recipient.self().getBadges());
}
private static void uploadProfile(@NonNull Context context,
@@ -223,13 +265,21 @@ public final class ProfileUtil {
@Nullable String about,
@Nullable String aboutEmoji,
@Nullable SignalServiceProtos.PaymentAddress paymentsAddress,
@Nullable StreamDetails avatar)
@Nullable StreamDetails avatar,
@NonNull List<Badge> badges)
throws IOException
{
List<String> badgeIds = badges.stream()
.filter(Badge::getVisible)
.map(Badge::getId)
.collect(Collectors.toList());
Log.d(TAG, "Uploading " + (!Util.isEmpty(about) ? "non-" : "") + "empty about.");
Log.d(TAG, "Uploading " + (!Util.isEmpty(aboutEmoji) ? "non-" : "") + "empty emoji.");
Log.d(TAG, "Uploading " + (paymentsAddress != null ? "non-" : "") + "empty payments address.");
Log.d(TAG, "Uploading " + (avatar != null && avatar.getLength() != 0 ? "non-" : "") + "empty avatar.");
Log.d(TAG, "Uploading " + ((!badgeIds.isEmpty()) ? "non-" : "") + "empty badge list");
ProfileKey profileKey = ProfileKeyUtil.getSelfProfileKey();
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
@@ -239,7 +289,8 @@ public final class ProfileUtil {
about,
aboutEmoji,
Optional.fromNullable(paymentsAddress),
avatar).orNull();
avatar,
badgeIds).orNull();
DatabaseFactory.getRecipientDatabase(context).setProfileAvatar(Recipient.self().getId(), avatarPath);
}