New group avatar and name selection screen.

This commit is contained in:
Alex Hart
2020-04-28 13:27:09 -03:00
committed by Greyson Parrelli
parent 12b7d6c0e3
commit 5eb663aa1b
22 changed files with 493 additions and 189 deletions

View File

@@ -12,23 +12,34 @@ import androidx.navigation.Navigation;
import org.thoughtcrime.securesms.BaseActionBarActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.DynamicRegistrationTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
@SuppressLint("StaticFieldLeak")
public class EditProfileActivity extends BaseActionBarActivity implements EditProfileFragment.Controller {
public static final String NEXT_INTENT = "next_intent";
public static final String EXCLUDE_SYSTEM = "exclude_system";
public static final String DISPLAY_USERNAME = "display_username";
public static final String NEXT_BUTTON_TEXT = "next_button_text";
public static final String SHOW_TOOLBAR = "show_back_arrow";
public static final String NEXT_INTENT = "next_intent";
public static final String EXCLUDE_SYSTEM = "exclude_system";
public static final String DISPLAY_USERNAME = "display_username";
public static final String NEXT_BUTTON_TEXT = "next_button_text";
public static final String SHOW_TOOLBAR = "show_back_arrow";
public static final String GROUP_ID = "group_id";
private final DynamicTheme dynamicTheme = new DynamicRegistrationTheme();
public static @NonNull Intent getIntent(@NonNull Context context, boolean showToolbar) {
public static @NonNull Intent getIntentForUserProfile(@NonNull Context context) {
Intent intent = new Intent(context, EditProfileActivity.class);
intent.putExtra(EditProfileActivity.SHOW_TOOLBAR, showToolbar);
intent.putExtra(EditProfileActivity.SHOW_TOOLBAR, false);
return intent;
}
public static @NonNull Intent getIntentForGroupProfile(@NonNull Context context, @NonNull GroupId.Push groupId) {
Intent intent = new Intent(context, EditProfileActivity.class);
intent.putExtra(EditProfileActivity.SHOW_TOOLBAR, true);
intent.putExtra(EditProfileActivity.GROUP_ID, groupId.toString());
intent.putExtra(EditProfileActivity.NEXT_BUTTON_TEXT, R.string.save);
return intent;
}

View File

@@ -34,8 +34,7 @@ import com.google.android.gms.common.util.IOUtils;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.v2.RegistrationLockUtil;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity;
import org.thoughtcrime.securesms.mediasend.AvatarSelectionBottomSheetDialogFragment;
@@ -46,7 +45,6 @@ import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.registration.RegistrationUtil;
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
@@ -58,6 +56,7 @@ import java.io.InputStream;
import static android.app.Activity.RESULT_OK;
import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.DISPLAY_USERNAME;
import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.EXCLUDE_SYSTEM;
import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.GROUP_ID;
import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.NEXT_BUTTON_TEXT;
import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.NEXT_INTENT;
import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.SHOW_TOOLBAR;
@@ -122,10 +121,13 @@ public class EditProfileFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
initializeResources(view);
initializeViewModel(requireArguments().getBoolean(EXCLUDE_SYSTEM, false), savedInstanceState != null);
initializeProfileName();
final GroupId groupId = GroupId.parseNullableOrThrow(requireArguments().getString(GROUP_ID, null));
final GroupId.Push pushGroupId = groupId != null ? groupId.requirePush() : null;
initializeResources(view, pushGroupId != null);
initializeViewModel(requireArguments().getBoolean(EXCLUDE_SYSTEM, false), pushGroupId, savedInstanceState != null);
initializeProfileAvatar();
initializeProfileName();
initializeUsername();
requireActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
@@ -189,14 +191,21 @@ public class EditProfileFragment extends Fragment {
}
}
private void initializeViewModel(boolean excludeSystem, boolean hasSavedInstanceState) {
EditProfileRepository repository = new EditProfileRepository(requireContext(), excludeSystem);
EditProfileViewModel.Factory factory = new EditProfileViewModel.Factory(repository, hasSavedInstanceState);
private void initializeViewModel(boolean excludeSystem, @Nullable GroupId.Push groupId, boolean hasSavedInstanceState) {
EditProfileRepository repository;
if (groupId != null) {
repository = new EditPushGroupProfileRepository(requireContext(), groupId);
} else {
repository = new EditSelfProfileRepository(requireContext(), excludeSystem);
}
EditProfileViewModel.Factory factory = new EditProfileViewModel.Factory(repository, hasSavedInstanceState, groupId);
viewModel = ViewModelProviders.of(this, factory).get(EditProfileViewModel.class);
}
private void initializeResources(@NonNull View view) {
private void initializeResources(@NonNull View view, boolean isEditingGroup) {
Bundle arguments = requireArguments();
this.toolbar = view.findViewById(R.id.toolbar);
@@ -228,10 +237,21 @@ public class EditProfileFragment extends Fragment {
trimInPlace(s);
viewModel.setGivenName(s.toString());
}));
this.familyName.addTextChangedListener(new AfterTextChanged(s -> {
trimInPlace(s);
viewModel.setFamilyName(s.toString());
}));
if (isEditingGroup) {
givenName.setHint(R.string.EditProfileFragment__group_name);
toolbar.setTitle(R.string.EditProfileFragment__edit_group_name_and_photo);
preview.setVisibility(View.GONE);
familyName.setVisibility(View.GONE);
familyName.setEnabled(false);
view.findViewById(R.id.description_text).setVisibility(View.GONE);
view.<ImageView>findViewById(R.id.avatar_placeholder).setImageResource(R.drawable.ic_group_outline_40);
} else {
this.familyName.addTextChangedListener(new AfterTextChanged(s -> {
trimInPlace(s);
viewModel.setFamilyName(s.toString());
}));
}
this.finishButton.setOnClickListener(v -> {
this.finishButton.setIndeterminateProgressMode(true);
@@ -254,22 +274,20 @@ public class EditProfileFragment extends Fragment {
}
private void initializeProfileName() {
viewModel.givenName().observe(this, givenName -> updateFieldIfNeeded(this.givenName, givenName));
viewModel.familyName().observe(this, familyName -> updateFieldIfNeeded(this.familyName, familyName));
viewModel.profileName().observe(this, profileName -> {
preview.setText(profileName.toString());
boolean validEntry = !profileName.isGivenNameEmpty();
finishButton.setEnabled(validEntry);
finishButton.setAlpha(validEntry ? 1f : 0.5f);
viewModel.isFormValid().observe(getViewLifecycleOwner(), isValid -> {
finishButton.setEnabled(isValid);
finishButton.setAlpha(isValid ? 1f : 0.5f);
});
viewModel.givenName().observe(getViewLifecycleOwner(), givenName -> updateFieldIfNeeded(this.givenName, givenName));
viewModel.familyName().observe(getViewLifecycleOwner(), familyName -> updateFieldIfNeeded(this.familyName, familyName));
viewModel.profileName().observe(getViewLifecycleOwner(), profileName -> preview.setText(profileName.toString()));
}
private void initializeProfileAvatar() {
viewModel.avatar().observe(this, bytes -> {
viewModel.avatar().observe(getViewLifecycleOwner(), bytes -> {
if (bytes == null) return;
GlideApp.with(this)
@@ -280,7 +298,7 @@ public class EditProfileFragment extends Fragment {
}
private void initializeUsername() {
viewModel.username().observe(this, this::onUsernameChanged);
viewModel.username().observe(getViewLifecycleOwner(), this::onUsernameChanged);
}
private static void updateFieldIfNeeded(@NonNull EditText field, @NonNull String value) {
@@ -303,7 +321,11 @@ public class EditProfileFragment extends Fragment {
}
private void startAvatarSelection() {
AvatarSelectionBottomSheetDialogFragment.create(viewModel.hasAvatar(), true, REQUEST_CODE_SELECT_AVATAR).show(getChildFragmentManager(), null);
AvatarSelectionBottomSheetDialogFragment.create(viewModel.canRemoveProfilePhoto(),
true,
REQUEST_CODE_SELECT_AVATAR,
viewModel.isGroup())
.show(getChildFragmentManager(), null);
}
private void handleUpload() {

View File

@@ -1,146 +1,27 @@
package org.thoughtcrime.securesms.profiles.edit;
import android.content.Context;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.core.util.Consumer;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileContentUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob;
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.profiles.SystemProfileUtil;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.ProfileUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
interface EditProfileRepository {
class EditProfileRepository {
void getCurrentProfileName(@NonNull Consumer<ProfileName> profileNameConsumer);
private static final String TAG = Log.tag(EditProfileRepository.class);
void getCurrentAvatar(@NonNull Consumer<byte[]> avatarConsumer);
private final Context context;
private final boolean excludeSystem;
void getCurrentDisplayName(@NonNull Consumer<String> displayNameConsumer);
EditProfileRepository(@NonNull Context context, boolean excludeSystem) {
this.context = context.getApplicationContext();
this.excludeSystem = excludeSystem;
}
void uploadProfile(@NonNull ProfileName profileName, @NonNull String displayName, @Nullable byte[] avatar, @NonNull Consumer<UploadResult> uploadResultConsumer);
void getCurrentProfileName(@NonNull Consumer<ProfileName> profileNameConsumer) {
ProfileName storedProfileName = Recipient.self().getProfileName();
if (!storedProfileName.isEmpty()) {
profileNameConsumer.accept(storedProfileName);
} else if (!excludeSystem) {
SystemProfileUtil.getSystemProfileName(context).addListener(new ListenableFuture.Listener<String>() {
@Override
public void onSuccess(String result) {
if (!TextUtils.isEmpty(result)) {
profileNameConsumer.accept(ProfileName.fromSerialized(result));
} else {
profileNameConsumer.accept(storedProfileName);
}
}
void getCurrentUsername(@NonNull Consumer<Optional<String>> callback);
@Override
public void onFailure(ExecutionException e) {
Log.w(TAG, e);
profileNameConsumer.accept(storedProfileName);
}
});
} else {
profileNameConsumer.accept(storedProfileName);
}
}
void getCurrentAvatar(@NonNull Consumer<byte[]> avatarConsumer) {
RecipientId selfId = Recipient.self().getId();
if (AvatarHelper.hasAvatar(context, selfId)) {
SimpleTask.run(() -> {
try {
return Util.readFully(AvatarHelper.getAvatar(context, selfId));
} catch (IOException e) {
Log.w(TAG, e);
return null;
}
}, avatarConsumer::accept);
} else if (!excludeSystem) {
SystemProfileUtil.getSystemProfileAvatar(context, new ProfileMediaConstraints()).addListener(new ListenableFuture.Listener<byte[]>() {
@Override
public void onSuccess(byte[] result) {
avatarConsumer.accept(result);
}
@Override
public void onFailure(ExecutionException e) {
Log.w(TAG, e);
avatarConsumer.accept(null);
}
});
}
}
void uploadProfile(@NonNull ProfileName profileName, @Nullable byte[] avatar, @NonNull Consumer<UploadResult> uploadResultConsumer) {
SimpleTask.run(() -> {
DatabaseFactory.getRecipientDatabase(context).setProfileName(Recipient.self().getId(), profileName);
try {
AvatarHelper.setAvatar(context, Recipient.self().getId(), avatar != null ? new ByteArrayInputStream(avatar) : null);
} catch (IOException e) {
return UploadResult.ERROR_FILE_IO;
}
ApplicationDependencies.getJobManager()
.startChain(new ProfileUploadJob())
.then(Arrays.asList(new MultiDeviceProfileKeyUpdateJob(), new MultiDeviceProfileContentUpdateJob()))
.enqueue();
return UploadResult.SUCCESS;
}, uploadResultConsumer::accept);
}
void getCurrentUsername(@NonNull Consumer<Optional<String>> callback) {
callback.accept(Optional.fromNullable(TextSecurePreferences.getLocalUsername(context)));
SignalExecutors.UNBOUNDED.execute(() -> callback.accept(getUsernameInternal()));
}
@WorkerThread
private @NonNull Optional<String> getUsernameInternal() {
try {
SignalServiceProfile profile = ProfileUtil.retrieveProfile(context, Recipient.self(), SignalServiceProfile.RequestType.PROFILE).getProfile();
TextSecurePreferences.setLocalUsername(context, profile.getUsername());
DatabaseFactory.getRecipientDatabase(context).setUsername(Recipient.self().getId(), profile.getUsername());
} catch (IOException e) {
Log.w(TAG, "Failed to retrieve username remotely! Using locally-cached version.");
}
return Optional.fromNullable(TextSecurePreferences.getLocalUsername(context));
}
public enum UploadResult {
enum UploadResult {
SUCCESS,
ERROR_FILE_IO
ERROR_IO,
ERROR_BAD_RECIPIENT
}
}

View File

@@ -1,7 +1,10 @@
package org.thoughtcrime.securesms.profiles.edit;
import android.view.animation.Transformation;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Consumer;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
@@ -9,6 +12,7 @@ import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.util.livedata.LiveDataPair;
import org.whispersystems.libsignal.util.guava.Optional;
@@ -21,18 +25,26 @@ class EditProfileViewModel extends ViewModel {
pair -> ProfileName.fromParts(pair.first(), pair.second()));
private final MutableLiveData<byte[]> internalAvatar = new MutableLiveData<>();
private final MutableLiveData<Optional<String>> internalUsername = new MutableLiveData<>();
private final LiveData<Boolean> isFormValid = Transformations.map(givenName, name -> !name.isEmpty());
private final EditProfileRepository repository;
private final GroupId groupId;
private EditProfileViewModel(@NonNull EditProfileRepository repository, boolean hasInstanceState) {
this.repository = repository;
private EditProfileViewModel(@NonNull EditProfileRepository repository, boolean hasInstanceState, @Nullable GroupId groupId) {
this.repository = repository;
this.groupId = groupId;
repository.getCurrentUsername(internalUsername::postValue);
if (!hasInstanceState) {
repository.getCurrentProfileName(name -> {
givenName.setValue(name.getGivenName());
familyName.setValue(name.getFamilyName());
});
if (groupId != null) {
repository.getCurrentDisplayName(givenName::setValue);
} else {
repository.getCurrentProfileName(name -> {
givenName.setValue(name.getGivenName());
familyName.setValue(name.getFamilyName());
});
}
repository.getCurrentAvatar(internalAvatar::setValue);
}
}
@@ -49,6 +61,10 @@ class EditProfileViewModel extends ViewModel {
return Transformations.distinctUntilChanged(internalProfileName);
}
public LiveData<Boolean> isFormValid() {
return Transformations.distinctUntilChanged(isFormValid);
}
public LiveData<byte[]> avatar() {
return Transformations.distinctUntilChanged(internalAvatar);
}
@@ -61,6 +77,14 @@ class EditProfileViewModel extends ViewModel {
return internalAvatar.getValue() != null;
}
public boolean isGroup() {
return groupId != null;
}
public boolean canRemoveProfilePhoto() {
return (!isGroup() || groupId.isV1()) && hasAvatar();
}
@MainThread
public byte[] getAvatarSnapshot() {
return internalAvatar.getValue();
@@ -79,29 +103,33 @@ class EditProfileViewModel extends ViewModel {
}
public void submitProfile(Consumer<EditProfileRepository.UploadResult> uploadResultConsumer) {
ProfileName profileName = internalProfileName.getValue();
if (profileName == null) {
ProfileName profileName = isGroup() ? ProfileName.EMPTY : internalProfileName.getValue();
String displayName = isGroup() ? givenName.getValue() : "";
if (profileName == null || displayName == null) {
return;
}
repository.uploadProfile(profileName, internalAvatar.getValue(), uploadResultConsumer);
repository.uploadProfile(profileName, displayName, internalAvatar.getValue(), uploadResultConsumer);
}
static class Factory implements ViewModelProvider.Factory {
private final EditProfileRepository repository;
private final boolean hasInstanceState;
private final boolean hasInstanceState;
private final GroupId groupId;
Factory(@NonNull EditProfileRepository repository, boolean hasInstanceState) {
Factory(@NonNull EditProfileRepository repository, boolean hasInstanceState, @Nullable GroupId groupId) {
this.repository = repository;
this.hasInstanceState = hasInstanceState;
this.groupId = groupId;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection unchecked
return (T) new EditProfileViewModel(repository, hasInstanceState);
return (T) new EditProfileViewModel(repository, hasInstanceState, groupId);
}
}
}

View File

@@ -0,0 +1,95 @@
package org.thoughtcrime.securesms.profiles.edit;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.core.util.Consumer;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import java.io.IOException;
class EditPushGroupProfileRepository implements EditProfileRepository {
private static final String TAG = Log.tag(EditPushGroupProfileRepository.class);
private final Context context;
private final GroupId.Push groupId;
EditPushGroupProfileRepository(@NonNull Context context, @NonNull GroupId.Push groupId) {
this.context = context.getApplicationContext();
this.groupId = groupId;
}
@Override
public void getCurrentProfileName(@NonNull Consumer<ProfileName> profileNameConsumer) {
profileNameConsumer.accept(ProfileName.EMPTY);
}
@Override
public void getCurrentAvatar(@NonNull Consumer<byte[]> avatarConsumer) {
SimpleTask.run(() -> {
final RecipientId recipientId = getRecipientId();
if (AvatarHelper.hasAvatar(context, recipientId)) {
try {
return Util.readFully(AvatarHelper.getAvatar(context, recipientId));
} catch (IOException e) {
Log.w(TAG, e);
return null;
}
} else {
return null;
}
}, avatarConsumer::accept);
}
@Override
public void getCurrentDisplayName(@NonNull Consumer<String> displayNameConsumer) {
SimpleTask.run(() -> Recipient.resolved(getRecipientId()).getDisplayName(context), displayNameConsumer::accept);
}
@Override
public void uploadProfile(@NonNull ProfileName profileName,
@NonNull String displayName,
@Nullable byte[] avatar,
@NonNull Consumer<UploadResult> uploadResultConsumer)
{
SimpleTask.run(() -> {
try {
GroupManager.updateGroup(context, groupId, avatar, displayName);
return UploadResult.SUCCESS;
} catch (InvalidNumberException e) {
return UploadResult.ERROR_IO;
}
}, uploadResultConsumer::accept);
}
@Override
public void getCurrentUsername(@NonNull Consumer<Optional<String>> callback) {
callback.accept(Optional.absent());
}
@WorkerThread
private RecipientId getRecipientId() {
return DatabaseFactory.getRecipientDatabase(context).getByGroupId(groupId.toString())
.or(() -> {
throw new AssertionError("Recipient ID for Group ID does not exist.");
});
}
}

View File

@@ -0,0 +1,147 @@
package org.thoughtcrime.securesms.profiles.edit;
import android.content.Context;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.core.util.Consumer;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileContentUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob;
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.profiles.SystemProfileUtil;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.ProfileUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
class EditSelfProfileRepository implements EditProfileRepository {
private static final String TAG = Log.tag(EditSelfProfileRepository.class);
private final Context context;
private final boolean excludeSystem;
EditSelfProfileRepository(@NonNull Context context, boolean excludeSystem) {
this.context = context.getApplicationContext();
this.excludeSystem = excludeSystem;
}
@Override
public void getCurrentProfileName(@NonNull Consumer<ProfileName> profileNameConsumer) {
ProfileName storedProfileName = Recipient.self().getProfileName();
if (!storedProfileName.isEmpty()) {
profileNameConsumer.accept(storedProfileName);
} else if (!excludeSystem) {
SystemProfileUtil.getSystemProfileName(context).addListener(new ListenableFuture.Listener<String>() {
@Override
public void onSuccess(String result) {
if (!TextUtils.isEmpty(result)) {
profileNameConsumer.accept(ProfileName.fromSerialized(result));
} else {
profileNameConsumer.accept(storedProfileName);
}
}
@Override
public void onFailure(ExecutionException e) {
Log.w(TAG, e);
profileNameConsumer.accept(storedProfileName);
}
});
} else {
profileNameConsumer.accept(storedProfileName);
}
}
@Override
public void getCurrentAvatar(@NonNull Consumer<byte[]> avatarConsumer) {
RecipientId selfId = Recipient.self().getId();
if (AvatarHelper.hasAvatar(context, selfId)) {
SimpleTask.run(() -> {
try {
return Util.readFully(AvatarHelper.getAvatar(context, selfId));
} catch (IOException e) {
Log.w(TAG, e);
return null;
}
}, avatarConsumer::accept);
} else if (!excludeSystem) {
SystemProfileUtil.getSystemProfileAvatar(context, new ProfileMediaConstraints()).addListener(new ListenableFuture.Listener<byte[]>() {
@Override
public void onSuccess(byte[] result) {
avatarConsumer.accept(result);
}
@Override
public void onFailure(ExecutionException e) {
Log.w(TAG, e);
avatarConsumer.accept(null);
}
});
}
}
@Override
public void getCurrentDisplayName(@NonNull Consumer<String> displayNameConsumer) {
displayNameConsumer.accept("");
}
@Override
public void uploadProfile(@NonNull ProfileName profileName, @NonNull String displayName, @Nullable byte[] avatar, @NonNull Consumer<UploadResult> uploadResultConsumer) {
SimpleTask.run(() -> {
DatabaseFactory.getRecipientDatabase(context).setProfileName(Recipient.self().getId(), profileName);
try {
AvatarHelper.setAvatar(context, Recipient.self().getId(), avatar != null ? new ByteArrayInputStream(avatar) : null);
} catch (IOException e) {
return UploadResult.ERROR_IO;
}
ApplicationDependencies.getJobManager()
.startChain(new ProfileUploadJob())
.then(Arrays.asList(new MultiDeviceProfileKeyUpdateJob(), new MultiDeviceProfileContentUpdateJob()))
.enqueue();
return UploadResult.SUCCESS;
}, uploadResultConsumer::accept);
}
@Override
public void getCurrentUsername(@NonNull Consumer<Optional<String>> callback) {
callback.accept(Optional.fromNullable(TextSecurePreferences.getLocalUsername(context)));
SignalExecutors.UNBOUNDED.execute(() -> callback.accept(getUsernameInternal()));
}
@WorkerThread
private @NonNull Optional<String> getUsernameInternal() {
try {
SignalServiceProfile profile = ProfileUtil.retrieveProfile(context, Recipient.self(), SignalServiceProfile.RequestType.PROFILE).getProfile();
TextSecurePreferences.setLocalUsername(context, profile.getUsername());
DatabaseFactory.getRecipientDatabase(context).setUsername(Recipient.self().getId(), profile.getUsername());
} catch (IOException e) {
Log.w(TAG, "Failed to retrieve username remotely! Using locally-cached version.");
}
return Optional.fromNullable(TextSecurePreferences.getLocalUsername(context));
}
}