Add avatar picker and defaults.

This commit is contained in:
Alex Hart
2021-07-20 13:08:52 -03:00
committed by Greyson Parrelli
parent 0093e1d3eb
commit ed23c3fe7c
133 changed files with 4935 additions and 859 deletions

View File

@@ -9,6 +9,7 @@ import androidx.core.util.Consumer;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.groups.GroupChangeException;
import org.thoughtcrime.securesms.groups.GroupId;
@@ -34,6 +35,11 @@ class EditGroupProfileRepository implements EditProfileRepository {
this.groupId = groupId;
}
@Override
public void getCurrentAvatarColor(@NonNull Consumer<AvatarColor> avatarColorConsumer) {
SimpleTask.run(() -> Recipient.resolved(getRecipientId()).getAvatarColor(), avatarColorConsumer::accept);
}
@Override
public void getCurrentProfileName(@NonNull Consumer<ProfileName> profileNameConsumer) {
profileNameConsumer.accept(ProfileName.EMPTY);

View File

@@ -10,6 +10,7 @@ import androidx.annotation.NonNull;
import androidx.navigation.NavDirections;
import androidx.navigation.NavGraph;
import androidx.navigation.Navigation;
import androidx.navigation.fragment.NavHostFragment;
import org.thoughtcrime.securesms.BaseActivity;
import org.thoughtcrime.securesms.R;
@@ -61,10 +62,10 @@ public class EditProfileActivity extends BaseActivity implements EditProfileFrag
setContentView(R.layout.profile_create_activity);
if (bundle == null) {
Bundle extras = getIntent().getExtras();
NavGraph graph = Navigation.findNavController(this, R.id.nav_host_fragment).getGraph();
Navigation.findNavController(this, R.id.nav_host_fragment).setGraph(graph, extras != null ? extras : new Bundle());
NavHostFragment fragment = NavHostFragment.create(R.navigation.edit_profile, getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, fragment)
.commit();
}
}

View File

@@ -5,6 +5,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.View;
@@ -20,7 +21,9 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.ViewModelProviders;
import androidx.navigation.Navigation;
import com.airbnb.lottie.SimpleColorFilter;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.dd.CircularProgressButton;
@@ -29,11 +32,10 @@ import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.avatar.Avatars;
import org.thoughtcrime.securesms.avatar.picker.AvatarPickerFragment;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity;
import org.thoughtcrime.securesms.mediasend.AvatarSelectionBottomSheetDialogFragment;
import org.thoughtcrime.securesms.groups.ParcelableGroupId;
import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.profiles.manage.EditProfileNameFragment;
@@ -47,7 +49,6 @@ import org.thoughtcrime.securesms.util.views.LearnMoreTextView;
import java.io.IOException;
import java.io.InputStream;
import static android.app.Activity.RESULT_OK;
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;
@@ -57,7 +58,6 @@ import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.SHOW_
public class EditProfileFragment extends LoggingFragment {
private static final String TAG = Log.tag(EditProfileFragment.class);
private static final short REQUEST_CODE_SELECT_AVATAR = 31726;
private static final int MAX_DESCRIPTION_GLYPHS = 480;
private static final int MAX_DESCRIPTION_BYTES = 8192;
@@ -69,6 +69,8 @@ public class EditProfileFragment extends LoggingFragment {
private EditText familyName;
private View reveal;
private TextView preview;
private ImageView avatarPreviewBackground;
private ImageView avatarPreview;
private Intent nextIntent;
@@ -100,45 +102,38 @@ public class EditProfileFragment extends LoggingFragment {
initializeResources(view, groupId);
initializeProfileAvatar();
initializeProfileName();
getParentFragmentManager().setFragmentResultListener(AvatarPickerFragment.REQUEST_KEY_SELECT_AVATAR, getViewLifecycleOwner(), (key, bundle) -> {
Media media = bundle.getParcelable(AvatarPickerFragment.SELECT_AVATAR_MEDIA);
handleMediaFromResult(media);
});
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
private void handleMediaFromResult(@NonNull Media media) {
SimpleTask.run(() -> {
try {
InputStream stream = BlobProvider.getInstance().getStream(requireContext(), media.getUri());
if (requestCode == REQUEST_CODE_SELECT_AVATAR && resultCode == RESULT_OK) {
if (data != null && data.getBooleanExtra("delete", false)) {
viewModel.setAvatar(null);
avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_camera_solid_white_24).asDrawable(requireActivity(), AvatarColor.UNKNOWN.colorInt()));
return;
return StreamUtil.readFully(stream);
} catch (IOException ioException) {
Log.w(TAG, ioException);
return null;
}
SimpleTask.run(() -> {
try {
Media result = data.getParcelableExtra(AvatarSelectionActivity.EXTRA_MEDIA);
InputStream stream = BlobProvider.getInstance().getStream(requireContext(), result.getUri());
return StreamUtil.readFully(stream);
} catch (IOException ioException) {
Log.w(TAG, ioException);
return null;
}
},
(avatarBytes) -> {
if (avatarBytes != null) {
viewModel.setAvatar(avatarBytes);
GlideApp.with(EditProfileFragment.this)
.load(avatarBytes)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.circleCrop()
.into(avatar);
} else {
Toast.makeText(requireActivity(), R.string.CreateProfileActivity_error_setting_profile_photo, Toast.LENGTH_LONG).show();
}
});
}
},
(avatarBytes) -> {
if (avatarBytes != null) {
viewModel.setAvatarMedia(media);
viewModel.setAvatar(avatarBytes);
GlideApp.with(EditProfileFragment.this)
.load(avatarBytes)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.circleCrop()
.into(avatar);
} else {
Toast.makeText(requireActivity(), R.string.CreateProfileActivity_error_setting_profile_photo, Toast.LENGTH_LONG).show();
}
});
}
private void initializeViewModel(boolean excludeSystem, @Nullable GroupId groupId, boolean hasSavedInstanceState) {
@@ -160,15 +155,17 @@ public class EditProfileFragment extends LoggingFragment {
Bundle arguments = requireArguments();
boolean isEditingGroup = groupId != null;
this.toolbar = view.findViewById(R.id.toolbar);
this.title = view.findViewById(R.id.title);
this.avatar = view.findViewById(R.id.avatar);
this.givenName = view.findViewById(R.id.given_name);
this.familyName = view.findViewById(R.id.family_name);
this.finishButton = view.findViewById(R.id.finish_button);
this.reveal = view.findViewById(R.id.reveal);
this.preview = view.findViewById(R.id.name_preview);
this.nextIntent = arguments.getParcelable(NEXT_INTENT);
this.toolbar = view.findViewById(R.id.toolbar);
this.title = view.findViewById(R.id.title);
this.avatar = view.findViewById(R.id.avatar);
this.givenName = view.findViewById(R.id.given_name);
this.familyName = view.findViewById(R.id.family_name);
this.finishButton = view.findViewById(R.id.finish_button);
this.reveal = view.findViewById(R.id.reveal);
this.preview = view.findViewById(R.id.name_preview);
this.avatarPreviewBackground = view.findViewById(R.id.avatar_background);
this.avatarPreview = view.findViewById(R.id.avatar_placeholder);
this.nextIntent = arguments.getParcelable(NEXT_INTENT);
this.avatar.setOnClickListener(v -> startAvatarSelection());
@@ -255,6 +252,13 @@ public class EditProfileFragment extends LoggingFragment {
.circleCrop()
.into(avatar);
});
viewModel.avatarColor().observe(getViewLifecycleOwner(), avatarColor -> {
Avatars.ForegroundColor foregroundColor = Avatars.getForegroundColor(avatarColor);
avatarPreview.getDrawable().setColorFilter(new SimpleColorFilter(foregroundColor.getColorInt()));
avatarPreviewBackground.getDrawable().setColorFilter(new SimpleColorFilter(avatarColor.colorInt()));
});
}
private static void updateFieldIfNeeded(@NonNull EditText field, @NonNull String value) {
@@ -273,11 +277,12 @@ public class EditProfileFragment extends LoggingFragment {
}
private void startAvatarSelection() {
AvatarSelectionBottomSheetDialogFragment.create(viewModel.canRemoveProfilePhoto(),
true,
REQUEST_CODE_SELECT_AVATAR,
viewModel.isGroup())
.show(getChildFragmentManager(), null);
if (viewModel.isGroup()) {
Parcelable groupId = ParcelableGroupId.from(viewModel.getGroupId());
Navigation.findNavController(requireView()).navigate(EditProfileFragmentDirections.actionCreateProfileFragmentToAvatarPicker((ParcelableGroupId) groupId, viewModel.getAvatarMedia()));
} else {
Navigation.findNavController(requireView()).navigate(EditProfileFragmentDirections.actionCreateProfileFragmentToAvatarPicker(null, null));
}
}
private void handleUpload() {

View File

@@ -4,11 +4,14 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Consumer;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.whispersystems.libsignal.util.guava.Optional;
interface EditProfileRepository {
void getCurrentAvatarColor(@NonNull Consumer<AvatarColor> avatarColorConsumer);
void getCurrentProfileName(@NonNull Consumer<ProfileName> profileNameConsumer);
void getCurrentAvatar(@NonNull Consumer<byte[]> avatarConsumer);

View File

@@ -8,7 +8,9 @@ import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.profiles.edit.EditProfileRepository.UploadResult;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
@@ -29,10 +31,12 @@ class EditProfileViewModel extends ViewModel {
private final MutableLiveData<byte[]> originalAvatar = new MutableLiveData<>();
private final MutableLiveData<String> originalDisplayName = new MutableLiveData<>();
private final SingleLiveEvent<UploadResult> uploadResult = new SingleLiveEvent<>();
private final MutableLiveData<AvatarColor> avatarColor = new MutableLiveData<>();
private final LiveData<Boolean> isFormValid;
private final EditProfileRepository repository;
private final GroupId groupId;
private String originalDescription;
private Media avatarMedia;
private EditProfileViewModel(@NonNull EditProfileRepository repository, boolean hasInstanceState, @Nullable GroupId groupId) {
this.repository = repository;
@@ -59,9 +63,15 @@ class EditProfileViewModel extends ViewModel {
internalAvatar.setValue(value);
originalAvatar.setValue(value);
});
repository.getCurrentAvatarColor(avatarColor::setValue);
}
}
public LiveData<AvatarColor> avatarColor() {
return Transformations.distinctUntilChanged(avatarColor);
}
public LiveData<String> givenName() {
return Transformations.distinctUntilChanged(givenName);
}
@@ -90,6 +100,18 @@ class EditProfileViewModel extends ViewModel {
return groupId != null;
}
public @Nullable Media getAvatarMedia() {
return avatarMedia;
}
public void setAvatarMedia(@Nullable Media avatarMedia) {
this.avatarMedia = avatarMedia;
}
public @Nullable GroupId getGroupId() {
return groupId;
}
public boolean canRemoveProfilePhoto() {
return hasAvatar();
}

View File

@@ -9,6 +9,7 @@ import androidx.core.util.Consumer;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileContentUpdateJob;
@@ -42,6 +43,11 @@ public class EditSelfProfileRepository implements EditProfileRepository {
this.excludeSystem = excludeSystem;
}
@Override
public void getCurrentAvatarColor(@NonNull Consumer<AvatarColor> avatarColorConsumer) {
SimpleTask.run(() -> Recipient.self().getAvatarColor(), avatarColorConsumer::accept);
}
@Override
public void getCurrentProfileName(@NonNull Consumer<ProfileName> profileNameConsumer) {
ProfileName storedProfileName = Recipient.self().getProfileName();

View File

@@ -23,9 +23,9 @@ import com.bumptech.glide.Glide;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.avatar.picker.AvatarPickerFragment;
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity;
import org.thoughtcrime.securesms.mediasend.AvatarSelectionBottomSheetDialogFragment;
import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.profiles.manage.ManageProfileViewModel.AvatarState;
@@ -35,8 +35,7 @@ import static android.app.Activity.RESULT_OK;
public class ManageProfileFragment extends LoggingFragment {
private static final String TAG = Log.tag(ManageProfileFragment.class);
private static final short REQUEST_CODE_SELECT_AVATAR = 31726;
private static final String TAG = Log.tag(ManageProfileFragment.class);
private Toolbar toolbar;
private ImageView avatarView;
@@ -86,22 +85,11 @@ public class ManageProfileFragment extends LoggingFragment {
this.aboutContainer.setOnClickListener(v -> {
Navigation.findNavController(v).navigate(ManageProfileFragmentDirections.actionManageAbout());
});
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_SELECT_AVATAR && resultCode == RESULT_OK) {
if (data != null && data.getBooleanExtra("delete", false)) {
viewModel.onAvatarSelected(requireContext(), null);
return;
}
Media result = data.getParcelableExtra(AvatarSelectionActivity.EXTRA_MEDIA);
getParentFragmentManager().setFragmentResultListener(AvatarPickerFragment.REQUEST_KEY_SELECT_AVATAR, getViewLifecycleOwner(), (key, bundle) -> {
Media result = bundle.getParcelable(AvatarPickerFragment.SELECT_AVATAR_MEDIA);
viewModel.onAvatarSelected(requireContext(), result);
}
});
}
private void initializeViewModel() {
@@ -193,10 +181,6 @@ public class ManageProfileFragment extends LoggingFragment {
}
private void onAvatarClicked() {
AvatarSelectionBottomSheetDialogFragment.create(viewModel.canRemoveAvatar(),
true,
REQUEST_CODE_SELECT_AVATAR,
false)
.show(getChildFragmentManager(), null);
Navigation.findNavController(requireView()).navigate(ManageProfileFragmentDirections.actionManageProfileFragmentToAvatarPicker(null, null));
}
}

View File

@@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto20dp;
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -122,7 +123,7 @@ public class ReviewBannerView extends LinearLayout {
}
@Override
protected Drawable newFallbackDrawable(@NonNull Context context, int color, boolean inverted) {
protected Drawable newFallbackDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return new FallbackPhoto20dp(getFallbackResId()).asDrawable(context, color, inverted);
}
}