Update Mention UI/UX to match latest designs.

This commit is contained in:
Cody Henthorne
2020-08-13 09:54:33 -04:00
committed by Greyson Parrelli
parent d63e5165eb
commit 724f3e872b
30 changed files with 353 additions and 201 deletions

View File

@@ -52,6 +52,7 @@ import androidx.annotation.DimenRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import com.annimon.stream.Stream;
@@ -63,6 +64,7 @@ import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.components.AlertView;
import org.thoughtcrime.securesms.components.AudioView;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.BorderlessImageView;
import org.thoughtcrime.securesms.components.ConversationItemFooter;
import org.thoughtcrime.securesms.components.ConversationItemThumbnail;
import org.thoughtcrime.securesms.components.DocumentView;
@@ -70,7 +72,6 @@ import org.thoughtcrime.securesms.components.LinkPreviewView;
import org.thoughtcrime.securesms.components.Outliner;
import org.thoughtcrime.securesms.components.QuoteView;
import org.thoughtcrime.securesms.components.SharedContactView;
import org.thoughtcrime.securesms.components.BorderlessImageView;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
import org.thoughtcrime.securesms.contactshare.Contact;
@@ -113,6 +114,7 @@ import org.thoughtcrime.securesms.util.LongClickMovementMethod;
import org.thoughtcrime.securesms.util.SearchUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.VibrateUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.Stub;
import org.whispersystems.libsignal.util.guava.Optional;
@@ -123,6 +125,8 @@ import java.util.List;
import java.util.Locale;
import java.util.Set;
import static org.thoughtcrime.securesms.util.ThemeUtil.isDarkTheme;
/**
* A view that displays an individual conversation item within a conversation
* thread. Used by ComposeMessageActivity's ListActivity via a ConversationAdapter.
@@ -564,6 +568,12 @@ public class ConversationItem extends LinearLayout implements BindableConversati
bodyText.setOverflowText(null);
}
if (messageRecord.isOutgoing()) {
bodyText.setMentionBackgroundTint(ContextCompat.getColor(context, isDarkTheme(context) ? R.color.core_grey_60 : R.color.core_grey_20));
} else {
bodyText.setMentionBackgroundTint(ContextCompat.getColor(context, R.color.transparent_black_40));
}
bodyText.setText(styledText);
bodyText.setVisibility(View.VISIBLE);
}
@@ -1423,6 +1433,7 @@ public class ConversationItem extends LinearLayout implements BindableConversati
@Override
public void onClick(@NonNull View widget) {
if (eventListener != null && !Recipient.resolved(mentionedRecipientId).isLocalNumber()) {
VibrateUtil.vibrateTick(context);
eventListener.onGroupMemberClicked(mentionedRecipientId, conversationRecipient.get().requireGroupId());
}
}

View File

@@ -46,6 +46,6 @@ public class MentionViewHolder extends MappingViewHolder<MentionViewState> {
}
public static MappingAdapter.Factory<MentionViewState> createFactory(@Nullable MentionEventsListener mentionEventsListener) {
return new MappingAdapter.LayoutFactory<>(view -> new MentionViewHolder(view, mentionEventsListener), R.layout.mentions_recipient_list_item);
return new MappingAdapter.LayoutFactory<>(view -> new MentionViewHolder(view, mentionEventsListener), R.layout.mentions_picker_recipient_list_item);
}
}

View File

@@ -1,12 +1,25 @@
package org.thoughtcrime.securesms.conversation.ui.mentions;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.conversation.ui.mentions.MentionViewHolder.MentionEventsListener;
import org.thoughtcrime.securesms.util.MappingAdapter;
import org.thoughtcrime.securesms.util.MappingModel;
import java.util.List;
public class MentionsPickerAdapter extends MappingAdapter {
public MentionsPickerAdapter(@Nullable MentionEventsListener mentionEventsListener) {
private final Runnable currentListChangedListener;
public MentionsPickerAdapter(@Nullable MentionEventsListener mentionEventsListener, @NonNull Runnable currentListChangedListener) {
this.currentListChangedListener = currentListChangedListener;
registerFactory(MentionViewState.class, MentionViewHolder.createFactory(mentionEventsListener));
}
@Override
public void onCurrentListChanged(@NonNull List<MappingModel<?>> previousList, @NonNull List<MappingModel<?>> currentList) {
super.onCurrentListChanged(previousList, currentList);
currentListChangedListener.run();
}
}

View File

@@ -17,25 +17,30 @@ import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.MappingModel;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.VibrateUtil;
import java.util.Collections;
import java.util.List;
public class MentionsPickerFragment extends LoggingFragment {
private MentionsPickerAdapter adapter;
private RecyclerView list;
private View topDivider;
private View bottomDivider;
private BottomSheetBehavior<View> behavior;
private MentionsPickerViewModel viewModel;
private int defaultPeekHeight;
@Override
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.mentions_picker_fragment, container, false);
list = view.findViewById(R.id.mentions_picker_list);
behavior = BottomSheetBehavior.from(view.findViewById(R.id.mentions_picker_bottom_sheet));
defaultPeekHeight = view.getContext().getResources().getDimensionPixelSize(R.dimen.mentions_picker_peek_height);
list = view.findViewById(R.id.mentions_picker_list);
topDivider = view.findViewById(R.id.mentions_picker_top_divider);
bottomDivider = view.findViewById(R.id.mentions_picker_bottom_divider);
behavior = BottomSheetBehavior.from(view.findViewById(R.id.mentions_picker_bottom_sheet));
initializeBehavior();
return view;
}
@@ -43,24 +48,43 @@ public class MentionsPickerFragment extends LoggingFragment {
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
viewModel = ViewModelProviders.of(requireActivity()).get(MentionsPickerViewModel.class);
initializeList();
viewModel = ViewModelProviders.of(requireActivity()).get(MentionsPickerViewModel.class);
viewModel.getMentionList().observe(getViewLifecycleOwner(), this::updateList);
viewModel.isShowing().observe(getViewLifecycleOwner(), isShowing -> {
if (isShowing) {
VibrateUtil.vibrateTick(requireContext());
}
});
}
private void initializeBehavior() {
behavior.setState(BottomSheetBehavior.STATE_HIDDEN);
behavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
adapter.submitList(Collections.emptyList());
} else {
showDividers(true);
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
showDividers(Float.isNaN(slideOffset) || slideOffset > -0.8f);
}
});
}
private void initializeList() {
adapter = new MentionsPickerAdapter(this::handleMentionClicked);
adapter = new MentionsPickerAdapter(this::handleMentionClicked, () -> updateBottomSheetBehavior(adapter.getItemCount()));
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(requireContext()) {
@Override
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
updateBottomSheetBehavior(adapter.getItemCount());
}
};
list.setLayoutManager(layoutManager);
list.setLayoutManager(new LinearLayoutManager(requireContext()));
list.setAdapter(adapter);
list.setItemAnimator(null);
}
@@ -70,24 +94,31 @@ public class MentionsPickerFragment extends LoggingFragment {
}
private void updateList(@NonNull List<MappingModel<?>> mappingModels) {
adapter.submitList(mappingModels);
if (mappingModels.isEmpty()) {
if (adapter.getItemCount() > 0 && mappingModels.isEmpty()) {
updateBottomSheetBehavior(0);
} else {
adapter.submitList(mappingModels);
}
list.scrollToPosition(0);
}
private void updateBottomSheetBehavior(int count) {
if (count > 0) {
if (behavior.getPeekHeight() == 0) {
behavior.setPeekHeight(defaultPeekHeight, true);
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
} else {
list.scrollToPosition(0);
}
} else {
boolean isShowing = count > 0;
viewModel.setIsShowing(isShowing);
if (isShowing) {
list.scrollToPosition(0);
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
behavior.setPeekHeight(0);
list.post(() -> behavior.setHideable(false));
showDividers(true);
} else {
behavior.setHideable(true);
behavior.setState(BottomSheetBehavior.STATE_HIDDEN);
}
}
private void showDividers(boolean showDividers) {
topDivider.setVisibility(showDividers ? View.VISIBLE : View.GONE);
bottomDivider.setVisibility(showDividers ? View.VISIBLE : View.GONE);
}
}

View File

@@ -29,11 +29,13 @@ public class MentionsPickerViewModel extends ViewModel {
private final LiveData<List<MappingModel<?>>> mentionList;
private final MutableLiveData<LiveGroup> group;
private final MutableLiveData<Query> liveQuery;
private final MutableLiveData<Boolean> isShowing;
MentionsPickerViewModel(@NonNull MentionsPickerRepository mentionsPickerRepository) {
group = new MutableLiveData<>();
liveQuery = new MutableLiveData<>(Query.NONE);
selectedRecipient = new SingleLiveEvent<>();
isShowing = new MutableLiveData<>(false);
LiveData<List<FullMember>> fullMembers = Transformations.distinctUntilChanged(Transformations.switchMap(group, LiveGroup::getFullMembers));
LiveData<Query> query = Transformations.distinctUntilChanged(liveQuery);
@@ -50,10 +52,21 @@ public class MentionsPickerViewModel extends ViewModel {
selectedRecipient.setValue(recipient);
}
void setIsShowing(boolean isShowing) {
if (Objects.equals(this.isShowing.getValue(), isShowing)) {
return;
}
this.isShowing.setValue(isShowing);
}
public @NonNull LiveData<Recipient> getSelectedRecipient() {
return selectedRecipient;
}
public @NonNull LiveData<Boolean> isShowing() {
return isShowing;
}
public void onQueryChange(@Nullable String query) {
liveQuery.setValue(query == null ? Query.NONE : new Query(query));
}