Fix responsiveness of profile photo edit UI.

There were various issues around the profile photo updating correctly in
the edit view. We want to make sure that what the user sees there is
what other people are seeing.

So I made some changes to make sure that when you remove your profile
photo the UI updates right away, as well as fixed most flickering
issues.
This commit is contained in:
Greyson Parrelli
2022-03-11 18:35:43 -05:00
committed by Cody Henthorne
parent e7a370a549
commit 78de70881f
6 changed files with 70 additions and 36 deletions

View File

@@ -83,6 +83,7 @@ public class AvatarHelper {
*/
public static void delete(@NonNull Context context, @NonNull RecipientId recipientId) {
getAvatarFile(context, recipientId).delete();
Recipient.live(recipientId).refresh();
}
/**

View File

@@ -39,10 +39,13 @@ import org.thoughtcrime.securesms.profiles.manage.ManageProfileViewModel.AvatarS
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.NameUtil;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import org.thoughtcrime.securesms.util.navigation.SafeNavigation;
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.Arrays;
public class ManageProfileFragment extends LoggingFragment {
private static final String TAG = Log.tag(ManageProfileFragment.class);
@@ -137,7 +140,8 @@ public class ManageProfileFragment extends LoggingFragment {
private void initializeViewModel() {
viewModel = ViewModelProviders.of(this, new ManageProfileViewModel.Factory()).get(ManageProfileViewModel.class);
LiveData<Optional<byte[]>> avatarImage = Transformations.distinctUntilChanged(Transformations.map(viewModel.getAvatar(), avatar -> Optional.fromNullable(avatar.getAvatar())));
LiveData<Optional<byte[]>> avatarImage = Transformations.map(LiveDataUtil.distinctUntilChanged(viewModel.getAvatar(), (b1, b2) -> Arrays.equals(b1.getAvatar(), b2.getAvatar())),
b -> Optional.fromNullable(b.getAvatar()));
avatarImage.observe(getViewLifecycleOwner(), this::presentAvatarImage);
viewModel.getAvatar().observe(getViewLifecycleOwner(), this::presentAvatarPlaceholder);
@@ -161,7 +165,7 @@ public class ManageProfileFragment extends LoggingFragment {
.circleCrop()
.into(avatarView);
} else {
avatarView.setImageDrawable(null);
Glide.with(this).load((Drawable) null).into(avatarView);
}
}

View File

@@ -57,7 +57,7 @@ final class ManageProfileRepository {
public void setAvatar(@NonNull Context context, @NonNull byte[] data, @NonNull String contentType, @NonNull Consumer<Result> callback) {
SignalExecutors.UNBOUNDED.execute(() -> {
try {
ProfileUtil.uploadProfileWithAvatar(context, new StreamDetails(new ByteArrayInputStream(data), contentType, data.length));
ProfileUtil.uploadProfileWithAvatar(new StreamDetails(new ByteArrayInputStream(data), contentType, data.length));
AvatarHelper.setAvatar(context, Recipient.self().getId(), new ByteArrayInputStream(data));
SignalStore.misc().markHasEverHadAnAvatar();
ApplicationDependencies.getJobManager().add(new MultiDeviceProfileContentUpdateJob());
@@ -73,7 +73,7 @@ final class ManageProfileRepository {
public void clearAvatar(@NonNull Context context, @NonNull Consumer<Result> callback) {
SignalExecutors.UNBOUNDED.execute(() -> {
try {
ProfileUtil.uploadProfileWithAvatar(context, null);
ProfileUtil.uploadProfileWithAvatar(null);
AvatarHelper.delete(context, Recipient.self().getId());
ApplicationDependencies.getJobManager().add(new MultiDeviceProfileContentUpdateJob());

View File

@@ -6,6 +6,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
@@ -30,6 +31,7 @@ import org.whispersystems.signalservice.api.util.StreamDetails;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Objects;
class ManageProfileViewModel extends ViewModel {
@@ -63,19 +65,6 @@ class ManageProfileViewModel extends ViewModel {
SignalExecutors.BOUNDED.execute(() -> {
onRecipientChanged(Recipient.self().fresh());
StreamDetails details = AvatarHelper.getSelfProfileAvatarStream(ApplicationDependencies.getApplication());
if (details != null) {
try {
internalAvatarState.postValue(InternalAvatarState.loaded(StreamUtil.readFully(details.getStream())));
} catch (IOException e) {
Log.w(TAG, "Failed to read avatar!");
internalAvatarState.postValue(InternalAvatarState.none());
}
} else {
internalAvatarState.postValue(InternalAvatarState.none());
}
ApplicationDependencies.getJobManager().add(RetrieveProfileJob.forRecipient(Recipient.self().getId()));
});
@@ -83,7 +72,7 @@ class ManageProfileViewModel extends ViewModel {
}
public @NonNull LiveData<AvatarState> getAvatar() {
return avatarState;
return Transformations.distinctUntilChanged(avatarState);
}
public @NonNull LiveData<ProfileName> getProfileName() {
@@ -169,6 +158,20 @@ class ManageProfileViewModel extends ViewModel {
about.postValue(recipient.getAbout());
aboutEmoji.postValue(recipient.getAboutEmoji());
badge.postValue(Optional.fromNullable(recipient.getFeaturedBadge()));
renderAvatar(AvatarHelper.getSelfProfileAvatarStream(ApplicationDependencies.getApplication()));
}
private void renderAvatar(@Nullable StreamDetails details) {
if (details != null) {
try {
internalAvatarState.postValue(InternalAvatarState.loaded(StreamUtil.readFully(details.getStream())));
} catch (IOException e) {
Log.w(TAG, "Failed to read avatar!");
internalAvatarState.postValue(InternalAvatarState.none());
}
} else {
internalAvatarState.postValue(InternalAvatarState.none());
}
}
@Override
@@ -198,6 +201,19 @@ class ManageProfileViewModel extends ViewModel {
public @NonNull Recipient getSelf() {
return self;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final AvatarState that = (AvatarState) o;
return Objects.equals(internalAvatarState, that.internalAvatarState) && Objects.equals(self, that.self);
}
@Override
public int hashCode() {
return Objects.hash(internalAvatarState, self);
}
}
private final static class InternalAvatarState {
@@ -228,6 +244,21 @@ class ManageProfileViewModel extends ViewModel {
public LoadingState getLoadingState() {
return loadingState;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final InternalAvatarState that = (InternalAvatarState) o;
return Arrays.equals(avatar, that.avatar) && loadingState == that.loadingState;
}
@Override
public int hashCode() {
int result = Objects.hash(loadingState);
result = 31 * result + Arrays.hashCode(avatar);
return result;
}
}
public enum LoadingState {