Add React With Any Search and update UX.

This commit is contained in:
Cody Henthorne
2021-06-24 15:14:34 -04:00
parent da2ee33dff
commit 2a1e5e4471
52 changed files with 1014 additions and 608 deletions

View File

@@ -6,19 +6,25 @@ import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Arrays;
import org.thoughtcrime.securesms.util.Util;
import java.util.LinkedList;
import java.util.List;
public class CompositeEmojiPageModel implements EmojiPageModel {
@AttrRes private final int iconAttr;
@NonNull private final List<EmojiPageModel> models;
@AttrRes private final int iconAttr;
@NonNull private final List<EmojiPageModel> models;
public CompositeEmojiPageModel(@AttrRes int iconAttr, @NonNull List<EmojiPageModel> models) {
this.iconAttr = iconAttr;
this.models = models;
}
@Override
public String getKey() {
return Util.hasItems(models) ? models.get(0).getKey() : "";
}
public int getIconAttr() {
return iconAttr;
}

View File

@@ -22,4 +22,8 @@ public class Emoji {
public List<String> getVariations() {
return variations;
}
public boolean hasMultipleVariations() {
return variations.size() > 1;
}
}

View File

@@ -0,0 +1,51 @@
package org.thoughtcrime.securesms.components.emoji
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.view.View
import androidx.appcompat.widget.AppCompatTextView
import androidx.recyclerview.widget.RecyclerView
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.EmojiModel
import org.thoughtcrime.securesms.util.InsetItemDecoration
import org.thoughtcrime.securesms.util.ViewUtil
private val EDGE_LENGTH: Int = ViewUtil.dpToPx(7)
private val HORIZONTAL_INSET: Int = ViewUtil.dpToPx(11)
private val VERTICAL_INSET: Int = ViewUtil.dpToPx(8)
/**
* Use super class to add insets to the emojis and use the [onDrawOver] to draw the variation
* hint if the emoji has more than one variation.
*/
class EmojiItemDecoration(private val allowVariations: Boolean, private val variationsDrawable: Drawable) : InsetItemDecoration(SetInset()) {
override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(canvas, parent, state)
val adapter: EmojiPageViewGridAdapter? = parent.adapter as? EmojiPageViewGridAdapter
if (allowVariations && adapter != null) {
for (i in 0 until parent.childCount) {
val child: View = parent.getChildAt(i)
val position: Int = parent.getChildAdapterPosition(child)
if (position >= 0 && position <= adapter.itemCount) {
val model = adapter.currentList[position]
if (model is EmojiModel && model.emoji.hasMultipleVariations()) {
variationsDrawable.setBounds(child.right, child.bottom - EDGE_LENGTH, child.right + EDGE_LENGTH, child.bottom)
variationsDrawable.draw(canvas)
}
}
}
}
}
private class SetInset : InsetItemDecoration.SetInset() {
override fun setInset(outRect: Rect, view: View, parent: RecyclerView) {
val isFirstHeader = view.javaClass == AppCompatTextView::class.java && getPosition(view, parent) == 0
outRect.left = HORIZONTAL_INSET
outRect.right = HORIZONTAL_INSET
outRect.top = if (isFirstHeader) 0 else VERTICAL_INSET
outRect.bottom = VERTICAL_INSET
}
}
}

View File

@@ -7,6 +7,7 @@ import androidx.annotation.Nullable;
import java.util.List;
public interface EmojiPageModel {
String getKey();
int getIconAttr();
List<String> getEmoji();
List<Emoji> getDisplayEmoji();

View File

@@ -1,91 +1,143 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.view.LayoutInflater;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.RecyclerView;
import com.annimon.stream.Stream;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider.EmojiEventListener;
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.EmojiHeader;
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.EmojiNoResultsModel;
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.VariationSelectorListener;
import org.thoughtcrime.securesms.emoji.EmojiCategory;
import org.thoughtcrime.securesms.util.ContextUtil;
import org.thoughtcrime.securesms.util.DrawableUtil;
import org.thoughtcrime.securesms.util.MappingModel;
import org.thoughtcrime.securesms.util.MappingModelList;
import org.thoughtcrime.securesms.util.ViewUtil;
public class EmojiPageView extends FrameLayout implements VariationSelectorListener {
private static final String TAG = Log.tag(EmojiPageView.class);
import java.util.Optional;
public class EmojiPageView extends RecyclerView implements VariationSelectorListener {
private EmojiPageModel model;
private AdapterFactory adapterFactory;
private RecyclerView recyclerView;
private RecyclerView.LayoutManager layoutManager;
private LinearLayoutManager layoutManager;
private RecyclerView.OnItemTouchListener scrollDisabler;
private VariationSelectorListener variationSelectorListener;
private EmojiVariationSelectorPopup popup;
public EmojiPageView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public EmojiPageView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public EmojiPageView(@NonNull Context context,
@NonNull EmojiEventListener emojiSelectionListener,
@NonNull VariationSelectorListener variationSelectorListener,
boolean allowVariations)
{
this(context, emojiSelectionListener, variationSelectorListener, allowVariations, new GridLayoutManager(context, 8), R.layout.emoji_display_item);
super(context);
initialize(emojiSelectionListener, variationSelectorListener, allowVariations);
}
public EmojiPageView(@NonNull Context context,
@NonNull EmojiEventListener emojiSelectionListener,
@NonNull VariationSelectorListener variationSelectorListener,
boolean allowVariations,
@NonNull RecyclerView.LayoutManager layoutManager,
@NonNull LinearLayoutManager layoutManager,
@LayoutRes int displayItemLayoutResId)
{
super(context);
final View view = LayoutInflater.from(getContext()).inflate(R.layout.emoji_grid_layout, this, true);
initialize(emojiSelectionListener, variationSelectorListener, allowVariations, layoutManager, displayItemLayoutResId);
}
public void initialize(@NonNull EmojiEventListener emojiSelectionListener,
@NonNull VariationSelectorListener variationSelectorListener,
boolean allowVariations)
{
initialize(emojiSelectionListener, variationSelectorListener, allowVariations, new GridLayoutManager(getContext(), 8), R.layout.emoji_display_item);
Drawable drawable = DrawableUtil.tint(ContextUtil.requireDrawable(getContext(), R.drawable.triangle_bottom_right_corner), ContextCompat.getColor(getContext(), R.color.signal_button_secondary_text_disabled));
addItemDecoration(new EmojiItemDecoration(allowVariations, drawable));
}
public void initialize(@NonNull EmojiEventListener emojiSelectionListener,
@NonNull VariationSelectorListener variationSelectorListener,
boolean allowVariations,
@NonNull LinearLayoutManager layoutManager,
@LayoutRes int displayItemLayoutResId)
{
this.variationSelectorListener = variationSelectorListener;
this.recyclerView = view.findViewById(R.id.emoji);
this.layoutManager = layoutManager;
this.scrollDisabler = new ScrollDisabler();
this.popup = new EmojiVariationSelectorPopup(context, emojiSelectionListener);
this.popup = new EmojiVariationSelectorPopup(getContext(), emojiSelectionListener);
this.adapterFactory = () -> new EmojiPageViewGridAdapter(popup,
emojiSelectionListener,
this,
allowVariations,
displayItemLayoutResId);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setItemAnimator(null);
if (this.layoutManager instanceof GridLayoutManager) {
GridLayoutManager gridLayout = (GridLayoutManager) this.layoutManager;
gridLayout.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (getAdapter() != null) {
Optional<MappingModel<?>> model = getAdapter().getModel(position);
if (model.isPresent() && (model.get() instanceof EmojiHeader || model.get() instanceof EmojiNoResultsModel)) {
return gridLayout.getSpanCount();
}
}
return 1;
}
});
}
setLayoutManager(layoutManager);
}
public void presentForEmojiKeyboard() {
recyclerView.setPadding(recyclerView.getPaddingLeft(),
recyclerView.getPaddingTop(),
recyclerView.getPaddingRight(),
recyclerView.getPaddingBottom() + ViewUtil.dpToPx(56));
setPadding(getPaddingLeft(),
getPaddingTop(),
getPaddingRight(),
getPaddingBottom() + ViewUtil.dpToPx(56));
recyclerView.setClipToPadding(false);
setClipToPadding(false);
}
public void onSelected() {
if (model.isDynamic() && recyclerView.getAdapter() != null) {
recyclerView.getAdapter().notifyDataSetChanged();
if (getAdapter() != null && (model == null || model.isDynamic())) {
getAdapter().notifyDataSetChanged();
}
}
public void setList(@NonNull MappingModelList list) {
this.model = null;
EmojiPageViewGridAdapter adapter = adapterFactory.create();
setAdapter(adapter);
adapter.submitList(list);
}
public void setModel(@Nullable EmojiPageModel model) {
this.model = model;
EmojiPageViewGridAdapter adapter = adapterFactory.create();
recyclerView.setAdapter(adapter);
setAdapter(adapter);
adapter.submitList(getMappingModelList());
}
@@ -93,18 +145,21 @@ public class EmojiPageView extends FrameLayout implements VariationSelectorListe
this.model = model;
EmojiPageViewGridAdapter adapter = adapterFactory.create();
recyclerView.setAdapter(adapter);
setAdapter(adapter);
adapter.submitList(getMappingModelList());
}
private @NonNull MappingModelList getMappingModelList() {
MappingModelList mappingModels = new MappingModelList();
if (model != null) {
mappingModels.addAll(Stream.of(model.getDisplayEmoji()).map(EmojiPageViewGridAdapter.EmojiModel::new).toList());
boolean emoticonPage = EmojiCategory.EMOTICONS.getKey().equals(model.getKey());
return model.getDisplayEmoji()
.stream()
.map(e -> emoticonPage ? new EmojiPageViewGridAdapter.EmojiTextModel(model.getKey(), e)
: new EmojiPageViewGridAdapter.EmojiModel(model.getKey(), e))
.collect(MappingModelList.collect());
}
return mappingModels;
return new MappingModelList();
}
@Override
@@ -117,8 +172,8 @@ public class EmojiPageView extends FrameLayout implements VariationSelectorListe
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (layoutManager instanceof GridLayoutManager) {
int idealWidth = getContext().getResources().getDimensionPixelOffset(R.dimen.emoji_drawer_item_width);
int spanCount = Math.max(w / idealWidth, 1);
int idealWidth = getContext().getResources().getDimensionPixelOffset(R.dimen.emoji_drawer_item_width);
int spanCount = Math.max(w / idealWidth, 1);
((GridLayoutManager) layoutManager).setSpanCount(spanCount);
}
@@ -127,9 +182,9 @@ public class EmojiPageView extends FrameLayout implements VariationSelectorListe
@Override
public void onVariationSelectorStateChanged(boolean open) {
if (open) {
recyclerView.addOnItemTouchListener(scrollDisabler);
addOnItemTouchListener(scrollDisabler);
} else {
post(() -> recyclerView.removeOnItemTouchListener(scrollDisabler));
post(() -> removeOnItemTouchListener(scrollDisabler));
}
if (variationSelectorListener != null) {
@@ -138,7 +193,29 @@ public class EmojiPageView extends FrameLayout implements VariationSelectorListe
}
public void setRecyclerNestedScrollingEnabled(boolean enabled) {
recyclerView.setNestedScrollingEnabled(enabled);
setNestedScrollingEnabled(enabled);
}
public void smoothScrollToPositionTop(int position) {
int currentPosition = layoutManager.findFirstCompletelyVisibleItemPosition();
boolean shortTrip = Math.abs(currentPosition - position) < 475;
if (shortTrip) {
RecyclerView.SmoothScroller smoothScroller = new LinearSmoothScroller(getContext()) {
@Override
protected int getVerticalSnapPreference() {
return LinearSmoothScroller.SNAP_TO_START;
}
};
smoothScroller.setTargetPosition(position);
layoutManager.startSmoothScroll(smoothScroller);
} else {
layoutManager.scrollToPositionWithOffset(position, 0);
}
}
public @Nullable EmojiPageViewGridAdapter getAdapter() {
return (EmojiPageViewGridAdapter) super.getAdapter();
}
private static class ScrollDisabler implements RecyclerView.OnItemTouchListener {

View File

@@ -2,26 +2,22 @@ package org.thoughtcrime.securesms.components.emoji;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.Space;
import android.widget.TextView;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider.EmojiEventListener;
import org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView;
import org.thoughtcrime.securesms.util.MappingAdapter;
import org.thoughtcrime.securesms.util.MappingModel;
import org.thoughtcrime.securesms.util.MappingViewHolder;
public class EmojiPageViewGridAdapter extends MappingAdapter implements PopupWindow.OnDismissListener {
private final VariationSelectorListener variationSelectorListener;
private final VariationSelectorListener variationSelectorListener;
public EmojiPageViewGridAdapter(@NonNull EmojiVariationSelectorPopup popup,
@NonNull EmojiEventListener emojiEventListener,
@@ -33,7 +29,10 @@ public class EmojiPageViewGridAdapter extends MappingAdapter implements PopupWin
popup.setOnDismissListener(this);
registerFactory(EmojiHeader.class, new LayoutFactory<>(EmojiHeaderViewHolder::new, R.layout.emoji_grid_header));
registerFactory(EmojiModel.class, new LayoutFactory<>(v -> new EmojiViewHolder(v, emojiEventListener, variationSelectorListener, popup, allowVariations), displayItemLayoutResId));
registerFactory(EmojiTextModel.class, new LayoutFactory<>(v -> new EmojiTextViewHolder(v, emojiEventListener), R.layout.emoji_text_display_item));
registerFactory(EmojiNoResultsModel.class, new LayoutFactory<>(MappingViewHolder.SimpleViewHolder::new, R.layout.emoji_grid_no_results));
}
@Override
@@ -41,21 +40,73 @@ public class EmojiPageViewGridAdapter extends MappingAdapter implements PopupWin
variationSelectorListener.onVariationSelectorStateChanged(false);
}
static class EmojiModel implements MappingModel<EmojiModel> {
public static class EmojiHeader implements MappingModel<EmojiHeader>, HasKey {
private final Emoji emoji;
private final String key;
private final int title;
EmojiModel(@NonNull Emoji emoji) {
public EmojiHeader(@NonNull String key, int title) {
this.key = key;
this.title = title;
}
@Override
public @NonNull String getKey() {
return key;
}
@Override
public boolean areItemsTheSame(@NonNull EmojiHeader newItem) {
return title == newItem.title;
}
@Override
public boolean areContentsTheSame(@NonNull EmojiHeader newItem) {
return areItemsTheSame(newItem);
}
}
static class EmojiHeaderViewHolder extends MappingViewHolder<EmojiHeader> {
private final TextView title;
public EmojiHeaderViewHolder(@NonNull View itemView) {
super(itemView);
title = findViewById(R.id.emoji_grid_header_title);
}
@Override
public void bind(@NonNull EmojiHeader model) {
title.setText(model.title);
}
}
public static class EmojiModel implements MappingModel<EmojiModel>, HasKey {
private final String key;
private final Emoji emoji;
public EmojiModel(@NonNull String key, @NonNull Emoji emoji) {
this.key = key;
this.emoji = emoji;
}
@Override
public boolean areItemsTheSame(@NonNull @NotNull EmojiModel newItem) {
public @NonNull String getKey() {
return key;
}
public @NonNull Emoji getEmoji() {
return emoji;
}
@Override
public boolean areItemsTheSame(@NonNull EmojiModel newItem) {
return newItem.emoji.getValue().equals(emoji.getValue());
}
@Override
public boolean areContentsTheSame(@NonNull @NotNull EmojiModel newItem) {
public boolean areContentsTheSame(@NonNull EmojiModel newItem) {
return areItemsTheSame(newItem);
}
}
@@ -67,9 +118,8 @@ public class EmojiPageViewGridAdapter extends MappingAdapter implements PopupWin
private final EmojiEventListener emojiEventListener;
private final boolean allowVariations;
private final ImageView imageView;
private final AsciiEmojiView textView;
private final ImageView hintCorner;
private final ImageView imageView;
private final ImageView hintCorner;
public EmojiViewHolder(@NonNull View itemView,
@NonNull EmojiEventListener emojiEventListener,
@@ -85,31 +135,26 @@ public class EmojiPageViewGridAdapter extends MappingAdapter implements PopupWin
this.allowVariations = allowVariations;
this.imageView = itemView.findViewById(R.id.emoji_image);
this.textView = itemView.findViewById(R.id.emoji_text);
this.hintCorner = itemView.findViewById(R.id.emoji_variation_hint);
}
@Override
public void bind(@NonNull @NotNull EmojiModel model) {
public void bind(@NonNull EmojiModel model) {
final Drawable drawable = EmojiProvider.getEmojiDrawable(imageView.getContext(), model.emoji.getValue());
if (drawable != null) {
textView.setVisibility(View.GONE);
imageView.setVisibility(View.VISIBLE);
imageView.setImageDrawable(drawable);
} else {
textView.setVisibility(View.VISIBLE);
imageView.setVisibility(View.GONE);
textView.setEmoji(model.emoji.getValue());
}
itemView.setOnClickListener(v -> {
emojiEventListener.onEmojiSelected(model.emoji.getValue());
});
if (allowVariations && model.emoji.getVariations().size() > 1) {
if (allowVariations && model.emoji.hasMultipleVariations()) {
if (hintCorner != null) {
hintCorner.setVisibility(View.VISIBLE);
}
itemView.setOnLongClickListener(v -> {
popup.dismiss();
popup.setVariations(model.emoji.getVariations());
@@ -117,14 +162,84 @@ public class EmojiPageViewGridAdapter extends MappingAdapter implements PopupWin
variationSelectorListener.onVariationSelectorStateChanged(true);
return true;
});
hintCorner.setVisibility(View.VISIBLE);
} else {
if (hintCorner != null) {
hintCorner.setVisibility(View.GONE);
}
itemView.setOnLongClickListener(null);
hintCorner.setVisibility(View.GONE);
}
}
}
public static class EmojiTextModel implements MappingModel<EmojiTextModel>, HasKey {
private final String key;
private final Emoji emoji;
public EmojiTextModel(@NonNull String key, @NonNull Emoji emoji) {
this.key = key;
this.emoji = emoji;
}
@Override
public @NonNull String getKey() {
return key;
}
public @NonNull Emoji getEmoji() {
return emoji;
}
@Override
public boolean areItemsTheSame(@NonNull EmojiTextModel newItem) {
return newItem.emoji.getValue().equals(emoji.getValue());
}
@Override
public boolean areContentsTheSame(@NonNull EmojiTextModel newItem) {
return areItemsTheSame(newItem);
}
}
static class EmojiTextViewHolder extends MappingViewHolder<EmojiTextModel> {
private final EmojiEventListener emojiEventListener;
private final AsciiEmojiView textView;
public EmojiTextViewHolder(@NonNull View itemView,
@NonNull EmojiEventListener emojiEventListener)
{
super(itemView);
this.emojiEventListener = emojiEventListener;
this.textView = itemView.findViewById(R.id.emoji_text);
}
@Override
public void bind(@NonNull EmojiTextModel model) {
textView.setEmoji(model.emoji.getValue());
itemView.setOnClickListener(v -> {
emojiEventListener.onEmojiSelected(model.emoji.getValue());
});
}
}
public static class EmojiNoResultsModel implements MappingModel<EmojiNoResultsModel> {
@Override
public boolean areItemsTheSame(@NonNull EmojiNoResultsModel newItem) {
return true;
}
@Override
public boolean areContentsTheSame(@NonNull EmojiNoResultsModel newItem) {
return true;
}
}
public interface HasKey {
@NonNull String getKey();
}
public interface VariationSelectorListener {
void onVariationSelectorStateChanged(boolean open);
}

View File

@@ -28,6 +28,7 @@ import java.util.List;
public class RecentEmojiPageModel implements EmojiPageModel {
private static final String TAG = Log.tag(RecentEmojiPageModel.class);
private static final int EMOJI_LRU_SIZE = 50;
public static final String KEY = "Recents";
private final SharedPreferences prefs;
private final String preferenceName;
@@ -55,6 +56,11 @@ public class RecentEmojiPageModel implements EmojiPageModel {
}
}
@Override
public String getKey() {
return KEY;
}
@Override public int getIconAttr() {
return R.attr.emoji_category_recent;
}
@@ -100,13 +106,4 @@ public class RecentEmojiPageModel implements EmojiPageModel {
}
});
}
private String[] toReversePrimitiveArray(@NonNull LinkedHashSet<String> emojiSet) {
String[] emojis = new String[emojiSet.size()];
int i = emojiSet.size() - 1;
for (String emoji : emojiSet) {
emojis[i--] = emoji;
}
return emojis;
}
}

View File

@@ -2,39 +2,39 @@ package org.thoughtcrime.securesms.components.emoji;
import android.net.Uri;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import org.thoughtcrime.securesms.emoji.EmojiCategory;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
public class StaticEmojiPageModel implements EmojiPageModel {
@AttrRes private final int iconAttr;
@NonNull private final List<Emoji> emoji;
@Nullable private final Uri sprite;
private final @NonNull EmojiCategory category;
private final @NonNull List<Emoji> emoji;
private final @Nullable Uri sprite;
public StaticEmojiPageModel(@AttrRes int iconAttr, @NonNull String[] strings, @Nullable Uri sprite) {
List<Emoji> emoji = new ArrayList<>(strings.length);
for (String s : strings) {
emoji.add(new Emoji(Collections.singletonList(s)));
}
public StaticEmojiPageModel(@NonNull EmojiCategory category, @NonNull String[] strings, @Nullable Uri sprite) {
this(category, Arrays.stream(strings).map(s -> new Emoji(Collections.singletonList(s))).collect(Collectors.toList()), sprite);
}
this.iconAttr = iconAttr;
this.emoji = emoji;
public StaticEmojiPageModel(@NonNull EmojiCategory category, @NonNull List<Emoji> emoji, @Nullable Uri sprite) {
this.category = category;
this.emoji = Collections.unmodifiableList(emoji);
this.sprite = sprite;
}
public StaticEmojiPageModel(@AttrRes int iconAttr, @NonNull List<Emoji> emoji, @Nullable Uri sprite) {
this.iconAttr = iconAttr;
this.emoji = Collections.unmodifiableList(emoji);
this.sprite = sprite;
@Override
public String getKey() {
return category.getKey();
}
public int getIconAttr() {
return iconAttr;
return category.getIcon();
}
@Override