mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 00:59:49 +01:00
New group avatar and name selection screen.
This commit is contained in:
committed by
Greyson Parrelli
parent
12b7d6c0e3
commit
5eb663aa1b
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user