mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-25 19:29:54 +01:00
Update Mention UI/UX to match latest designs.
This commit is contained in:
committed by
Greyson Parrelli
parent
d63e5165eb
commit
724f3e872b
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user