Made setting a profile photo a synchronous operation.

fork-5.53.8
Greyson Parrelli 2021-01-22 14:18:57 -05:00
rodzic 5649c906a5
commit 9d5a52a980
4 zmienionych plików z 113 dodań i 36 usunięć

Wyświetl plik

@ -182,8 +182,13 @@ public class ManageProfileFragment extends LoggingFragment {
}
private void presentEvent(@NonNull ManageProfileViewModel.Event event) {
if (event == ManageProfileViewModel.Event.AVATAR_FAILURE) {
Toast.makeText(requireContext(), R.string.ManageProfileFragment_failed_to_set_avatar, Toast.LENGTH_LONG).show();
switch (event) {
case AVATAR_DISK_FAILURE:
Toast.makeText(requireContext(), R.string.ManageProfileFragment_failed_to_set_avatar, Toast.LENGTH_LONG).show();
break;
case AVATAR_NETWORK_FAILURE:
Toast.makeText(requireContext(), R.string.EditProfileNameFragment_failed_to_save_due_to_network_issues_try_again_later, Toast.LENGTH_LONG).show();
break;
}
}

Wyświetl plik

@ -3,15 +3,19 @@ package org.thoughtcrime.securesms.profiles.manage;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Consumer;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.ProfileUtil;
import org.whispersystems.signalservice.api.util.StreamDetails;
import java.io.ByteArrayInputStream;
import java.io.IOException;
final class ManageProfileRepository {
@ -36,6 +40,33 @@ final class ManageProfileRepository {
try {
ProfileUtil.uploadProfileWithAbout(context, about, emoji);
DatabaseFactory.getRecipientDatabase(context).setAbout(Recipient.self().getId(), about, emoji);
callback.accept(Result.SUCCESS);
} catch (IOException e) {
Log.w(TAG, "Failed to upload profile during about change.", e);
callback.accept(Result.FAILURE_NETWORK);
}
});
}
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));
AvatarHelper.setAvatar(context, Recipient.self().getId(), new ByteArrayInputStream(data));
callback.accept(Result.SUCCESS);
} catch (IOException e) {
Log.w(TAG, "Failed to upload profile during avatar change.", e);
callback.accept(Result.FAILURE_NETWORK);
}
});
}
public void clearAvatar(@NonNull Context context, @NonNull Consumer<Result> callback) {
SignalExecutors.UNBOUNDED.execute(() -> {
try {
ProfileUtil.uploadProfileWithAvatar(context, null);
AvatarHelper.delete(context, Recipient.self().getId());
callback.accept(Result.SUCCESS);
} catch (IOException e) {
Log.w(TAG, "Failed to upload profile during name change.", e);

Wyświetl plik

@ -13,7 +13,6 @@ import org.signal.core.util.StreamUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
@ -25,7 +24,6 @@ import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import org.whispersystems.signalservice.api.util.StreamDetails;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
@ -41,6 +39,9 @@ class ManageProfileViewModel extends ViewModel {
private final MutableLiveData<String> aboutEmoji;
private final SingleLiveEvent<Event> events;
private final RecipientForeverObserver observer;
private final ManageProfileRepository repository;
private byte[] previousAvatar;
public ManageProfileViewModel() {
this.avatar = new MutableLiveData<>();
@ -49,6 +50,7 @@ class ManageProfileViewModel extends ViewModel {
this.about = new MutableLiveData<>();
this.aboutEmoji = new MutableLiveData<>();
this.events = new SingleLiveEvent<>();
this.repository = new ManageProfileRepository();
this.observer = this::onRecipientChanged;
SignalExecutors.BOUNDED.execute(() -> {
@ -101,11 +103,21 @@ class ManageProfileViewModel extends ViewModel {
}
public void onAvatarSelected(@NonNull Context context, @Nullable Media media) {
previousAvatar = avatar.getValue() != null ? avatar.getValue().getAvatar() : null;
if (media == null) {
SignalExecutors.BOUNDED.execute(() -> {
AvatarHelper.delete(context, Recipient.self().getId());
avatar.postValue(AvatarState.none());
ApplicationDependencies.getJobManager().add(new ProfileUploadJob());
avatar.postValue(AvatarState.loading(null));
repository.clearAvatar(context, result -> {
switch (result) {
case SUCCESS:
avatar.postValue(AvatarState.loaded(null));
previousAvatar = null;
break;
case FAILURE_NETWORK:
avatar.postValue(AvatarState.loaded(previousAvatar));
events.postValue(Event.AVATAR_NETWORK_FAILURE);
break;
}
});
} else {
SignalExecutors.BOUNDED.execute(() -> {
@ -113,13 +125,23 @@ class ManageProfileViewModel extends ViewModel {
InputStream stream = BlobProvider.getInstance().getStream(context, media.getUri());
byte[] data = StreamUtil.readFully(stream);
AvatarHelper.setAvatar(context, Recipient.self().getId(), new ByteArrayInputStream(data));
avatar.postValue(AvatarState.loaded(data));
avatar.postValue(AvatarState.loading(data));
ApplicationDependencies.getJobManager().add(new ProfileUploadJob());
repository.setAvatar(context, data, media.getMimeType(), result -> {
switch (result) {
case SUCCESS:
avatar.postValue(AvatarState.loaded(data));
previousAvatar = data;
break;
case FAILURE_NETWORK:
avatar.postValue(AvatarState.loaded(previousAvatar));
events.postValue(Event.AVATAR_NETWORK_FAILURE);
break;
}
});
} catch (IOException e) {
Log.w(TAG, "Failed to save avatar!", e);
events.postValue(Event.AVATAR_FAILURE);
events.postValue(Event.AVATAR_DISK_FAILURE);
}
});
}
@ -176,7 +198,7 @@ class ManageProfileViewModel extends ViewModel {
}
enum Event {
AVATAR_FAILURE
AVATAR_NETWORK_FAILURE, AVATAR_DISK_FAILURE
}
static class Factory extends ViewModelProvider.NewInstanceFactory {

Wyświetl plik

@ -107,10 +107,13 @@ public final class ProfileUtil {
* successfully before persisting the change to disk.
*/
public static void uploadProfileWithName(@NonNull Context context, @NonNull ProfileName profileName) throws IOException {
uploadProfile(context,
profileName,
Optional.fromNullable(Recipient.self().getAbout()).or(""),
Optional.fromNullable(Recipient.self().getAboutEmoji()).or(""));
try (StreamDetails avatar = AvatarHelper.getSelfProfileAvatarStream(context)) {
uploadProfile(context,
profileName,
Optional.fromNullable(Recipient.self().getAbout()).or(""),
Optional.fromNullable(Recipient.self().getAboutEmoji()).or(""),
avatar);
}
}
/**
@ -119,35 +122,51 @@ public final class ProfileUtil {
* successfully before persisting the change to disk.
*/
public static void uploadProfileWithAbout(@NonNull Context context, @NonNull String about, @NonNull String emoji) throws IOException {
uploadProfile(context,
Recipient.self().getProfileName(),
about,
emoji);
try (StreamDetails avatar = AvatarHelper.getSelfProfileAvatarStream(context)) {
uploadProfile(context,
Recipient.self().getProfileName(),
about,
emoji,
avatar);
}
}
/**
* Uploads the profile based on all state that's written to disk, except we'll use the provided
* avatar 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 uploadProfileWithAvatar(@NonNull Context context, @Nullable StreamDetails avatar) throws IOException {
uploadProfile(context,
Recipient.self().getProfileName(),
Optional.fromNullable(Recipient.self().getAbout()).or(""),
Optional.fromNullable(Recipient.self().getAboutEmoji()).or(""),
avatar);
}
/**
* Uploads the profile based on all state that's already written to disk.
*/
public static void uploadProfile(@NonNull Context context) throws IOException {
uploadProfile(context,
Recipient.self().getProfileName(),
Optional.fromNullable(Recipient.self().getAbout()).or(""),
Optional.fromNullable(Recipient.self().getAboutEmoji()).or(""));
try (StreamDetails avatar = AvatarHelper.getSelfProfileAvatarStream(context)) {
uploadProfile(context,
Recipient.self().getProfileName(),
Optional.fromNullable(Recipient.self().getAbout()).or(""),
Optional.fromNullable(Recipient.self().getAboutEmoji()).or(""),
avatar);
}
}
public static void uploadProfile(@NonNull Context context,
@NonNull ProfileName profileName,
@Nullable String about,
@Nullable String aboutEmoji)
private static void uploadProfile(@NonNull Context context,
@NonNull ProfileName profileName,
@Nullable String about,
@Nullable String aboutEmoji,
@Nullable StreamDetails avatar)
throws IOException
{
ProfileKey profileKey = ProfileKeyUtil.getSelfProfileKey();
String avatarPath;
try (StreamDetails avatar = AvatarHelper.getSelfProfileAvatarStream(context)) {
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
avatarPath = accountManager.setVersionedProfile(Recipient.self().getUuid().get(), profileKey, profileName.serialize(), about, aboutEmoji, avatar).orNull();
}
ProfileKey profileKey = ProfileKeyUtil.getSelfProfileKey();
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
String avatarPath = accountManager.setVersionedProfile(Recipient.self().getUuid().get(), profileKey, profileName.serialize(), about, aboutEmoji, avatar).orNull();
DatabaseFactory.getRecipientDatabase(context).setProfileAvatar(Recipient.self().getId(), avatarPath);
}