package com.wayfair.brickkit.brick; import android.util.SparseArray; import android.view.View; import com.wayfair.brickkit.viewholder.BrickViewHolder; import com.wayfair.brickkit.padding.BrickPadding; import com.wayfair.brickkit.size.BrickSize; import androidx.annotation.LayoutRes; import androidx.databinding.DataBindingUtil; import androidx.databinding.ViewDataBinding; /** * This class is used as a Generic Brick that can take in any XML Layout and use DataBinding to * insert information from a {@link ViewModel}. */ public final class ViewModelBrick extends BaseBrick implements ViewModel.ViewModelUpdateListener { @LayoutRes private final int layoutId; @LayoutRes private final int placeholderLayoutId; private PlaceholderBinder placeholderBinder; protected final SparseArray<ViewModel> viewModels; protected SwipeListener onDismiss; /** * Private constructor that the {@link Builder} class uses. * * @param builder the builder */ private ViewModelBrick(Builder builder) { super(builder.spanSize, builder.padding); this.layoutId = builder.layoutId; this.placeholderLayoutId = builder.placeholderLayoutId; this.placeholderBinder = builder.placeholderBinder; this.onDismiss = builder.onDismiss; this.viewModels = builder.viewModels; for (int i = 0; i < viewModels.size(); i++) { viewModels.valueAt(i).addUpdateListener(this); } } /** * Gets the appropriate {@link ViewModel} for the given binding id. * * @param bindId the binding id * @return a {@link ViewModel} for the binding id */ public ViewModel getViewModel(int bindId) { return viewModels.get(bindId); } /** * Gets all {@link ViewModel}s. * * @return a {@link ViewModel} for the binding id */ public SparseArray<ViewModel> getViewModels() { return viewModels; } /** * Add a view model to the Brick. * * @param bindingId the binding ID of the view model * @param viewModel the view model */ public void addViewModel(int bindingId, ViewModel viewModel) { viewModel.addUpdateListener(this); this.viewModels.put(bindingId, viewModel); onChange(); } /** * Replace all the view models with these. * * @param viewModels the view models to replace existing view models */ public void setViewModels(SparseArray<ViewModel> viewModels) { this.viewModels.clear(); for (int i = 0; i < viewModels.size(); i++) { viewModels.valueAt(i).addUpdateListener(this); this.viewModels.put(viewModels.keyAt(i), viewModels.valueAt(i)); } onChange(); } /** * {@inheritDoc} */ @Override public void onBindData(BrickViewHolder holder) { ViewModelBrickViewHolder viewModelBrickViewHolder = (ViewModelBrickViewHolder) holder; for (int i = 0; i < viewModels.size(); i++) { viewModelBrickViewHolder.bind(viewModels.keyAt(i), viewModels.valueAt(i)); } viewModelBrickViewHolder.getViewDataBinding().executePendingBindings(); } /** * {@inheritDoc} */ @Override public void onBindPlaceholder(BrickViewHolder holder) { if (placeholderBinder != null) { placeholderBinder.onBindPlaceholder(holder); } } /** * {@inheritDoc} */ @Override public int getLayout() { return layoutId; } /** * {@inheritDoc} */ @Override public int getPlaceholderLayout() { return placeholderLayoutId; } /** * {@inheritDoc} */ @Override public BrickViewHolder createViewHolder(View itemView) { return new ViewModelBrickViewHolder(DataBindingUtil.bind(itemView)); } /** * Run the {@link SwipeListener} when a swipe happens on this brick. * * @param direction one of {@link androidx.recyclerview.widget.ItemTouchHelper.UP}, * {@link androidx.recyclerview.widget.ItemTouchHelper.RIGHT}, * {@link androidx.recyclerview.widget.ItemTouchHelper.DOWN}, * {@link androidx.recyclerview.widget.ItemTouchHelper.LEFT} */ @SuppressWarnings("JavadocReference") @Override public void dismissed(int direction) { if (onDismiss != null) { onDismiss.swiped(direction); } } /** * Set the action for this brick being swiped. * * @param onDismiss the {@link SwipeListener} to be run on swipe */ public void setOnDismiss(SwipeListener onDismiss) { this.onDismiss = onDismiss; } /** * {@inheritDoc} */ @Override public void onChange() { setHidden(!isDataReady()); refreshItem(); } /** * {@inheritDoc} */ @Override public boolean isDataReady() { boolean isDataReady = viewModels.size() != 0; for (int i = 0; isDataReady && i < viewModels.size(); i++) { isDataReady = viewModels.valueAt(i).isDataModelReady(); } return isDataReady; } @Override public int hashCode() { return super.hashCode(); } @Override public boolean equals(Object obj) { boolean areContentsTheSame = true; if (obj instanceof ViewModelBrick) { for (int i = 0; i < getViewModels().size(); i++) { for (int j = 0; j < ((ViewModelBrick) obj).getViewModels().size(); j++) { if (getViewModels().keyAt(i) == ((ViewModelBrick) obj).getViewModels().keyAt(j)) { if (!getViewModels().valueAt(i).equals(((ViewModelBrick) obj).getViewModels().valueAt(j))) { areContentsTheSame = false; } } } } } else { areContentsTheSame = false; } return areContentsTheSame; } /** * A builder class for {@link ViewModelBrick}, this makes it clearer what is required and what you are actually doing when creating * {@link ViewModelBrick}s. */ public static class Builder { @LayoutRes int layoutId; @LayoutRes int placeholderLayoutId; PlaceholderBinder placeholderBinder = null; SparseArray<ViewModel> viewModels = new SparseArray<>(); BrickSize spanSize = getDefaultSize(); BrickPadding padding = getDefaultPadding(); SwipeListener onDismiss = null; /** * Builder constructor, requires only a {@link LayoutRes} to work. * * @param layoutId a {@link LayoutRes} to use as a brick */ public Builder(@LayoutRes int layoutId) { this.layoutId = layoutId; } /** * Set the placeholder for this brick. * * @param placeholderLayoutId the placeholder layout id to be used * @param placeholderBinder the object that helps bind the place holder * @return the builder */ public Builder setPlaceholder(@LayoutRes int placeholderLayoutId, PlaceholderBinder placeholderBinder) { this.placeholderLayoutId = placeholderLayoutId; this.placeholderBinder = placeholderBinder; return this; } /** * Add a {@link ViewModel} with a binding Id for the layout already defined. * * @param bindingId the binding Id of the view model * @param viewModel the view model to be bound, extends {@link ViewModel} * @return the builder */ public Builder addViewModel(int bindingId, ViewModel viewModel) { if (viewModel != null) { this.viewModels.put(bindingId, viewModel); } return this; } /** * Add a set of {@link ViewModel}s and their binding Ids. * * @param viewModels a {@link SparseArray} of binding Ids and {@link ViewModel}s * @return the builder */ public Builder setViewModels(SparseArray<ViewModel> viewModels) { this.viewModels = viewModels; return this; } /** * Set the {@link BrickSize}. * * @param spanSize the {@link BrickSize} * @return the builder */ public Builder setSpanSize(BrickSize spanSize) { this.spanSize = spanSize; return this; } /** * Set the {@link BrickPadding}. * * @param padding the {@link BrickPadding} * @return the builder */ public Builder setPadding(BrickPadding padding) { this.padding = padding; return this; } /** * Set the {@link SwipeListener}. * * @param onDismiss the {@link SwipeListener} * @return the builder */ public Builder setOnDismiss(SwipeListener onDismiss) { this.onDismiss = onDismiss; return this; } /** * Assemble the {@link ViewModelBrick}. * * @return the complete {@link ViewModelBrick} */ public ViewModelBrick build() { return new ViewModelBrick(this); } /** * Get the default size. * * @return the default {@link BrickSize} */ protected BrickSize getDefaultSize() { return DEFAULT_SIZE_FULL_WIDTH; } /** * Get the default padding. * * @return the default {@link BrickPadding} */ protected BrickPadding getDefaultPadding() { return DEFAULT_PADDING_NONE; } } /** * A special {@link BrickViewHolder} that can handle binding {@link ViewModel}s to layouts. */ public static final class ViewModelBrickViewHolder extends BrickViewHolder { public final ViewDataBinding viewDataBinding; /** * Constructor to set up the {@link BrickViewHolder} with the {@link ViewDataBinding} * from the right item view. * * @param viewDataBinding the {@link ViewDataBinding} object * from {@link #createViewHolder(View)} */ public ViewModelBrickViewHolder(ViewDataBinding viewDataBinding) { super(viewDataBinding.getRoot()); this.viewDataBinding = viewDataBinding; } /** * Sets the {@link ViewModel} to be bound for the given id. * * @param bindId the id * @param viewModel the {@link ViewModel} */ void bind(int bindId, ViewModel viewModel) { viewDataBinding.setVariable(bindId, viewModel); } /** * gets the {@link ViewDataBinding} object in order to execute them * {@link #onBindData(BrickViewHolder)}. * * @return the {@link ViewDataBinding} */ ViewDataBinding getViewDataBinding() { return viewDataBinding; } } }