package io.github.luizgrp.sectionedrecyclerviewadapter; import android.view.View; import android.view.ViewGroup; import java.util.List; import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; /** * Abstract Section to be used with {@link SectionedRecyclerViewAdapter}. */ @SuppressWarnings("WeakerAccess") public abstract class Section { public enum State { LOADING, LOADED, FAILED, EMPTY } private State state = State.LOADED; private boolean visible = true; @SuppressWarnings({"PMD.BeanMembersShouldSerialize", "PMD.AvoidFieldNameMatchingMethodName"}) private boolean hasHeader; @SuppressWarnings({"PMD.BeanMembersShouldSerialize", "PMD.AvoidFieldNameMatchingMethodName"}) private boolean hasFooter; @LayoutRes private final Integer itemResourceId; @LayoutRes private final Integer headerResourceId; @LayoutRes private final Integer footerResourceId; @LayoutRes private final Integer loadingResourceId; @LayoutRes private final Integer failedResourceId; @LayoutRes private final Integer emptyResourceId; private final boolean itemViewWillBeProvided; private final boolean headerViewWillBeProvided; private final boolean footerViewWillBeProvided; private final boolean loadingViewWillBeProvided; private final boolean failedViewWillBeProvided; private final boolean emptyViewWillBeProvided; /** * Create a Section object based on {@link SectionParameters}. * * @param sectionParameters section parameters */ public Section(@NonNull final SectionParameters sectionParameters) { this.itemResourceId = sectionParameters.itemResourceId; this.headerResourceId = sectionParameters.headerResourceId; this.footerResourceId = sectionParameters.footerResourceId; this.loadingResourceId = sectionParameters.loadingResourceId; this.failedResourceId = sectionParameters.failedResourceId; this.emptyResourceId = sectionParameters.emptyResourceId; this.itemViewWillBeProvided = sectionParameters.itemViewWillBeProvided; this.headerViewWillBeProvided = sectionParameters.headerViewWillBeProvided; this.footerViewWillBeProvided = sectionParameters.footerViewWillBeProvided; this.loadingViewWillBeProvided = sectionParameters.loadingViewWillBeProvided; this.failedViewWillBeProvided = sectionParameters.failedViewWillBeProvided; this.emptyViewWillBeProvided = sectionParameters.emptyViewWillBeProvided; this.hasHeader = this.headerResourceId != null || this.headerViewWillBeProvided; this.hasFooter = this.footerResourceId != null || this.footerViewWillBeProvided; } /** * Set the State of this Section. * * @param state state of this section */ public final void setState(final State state) { switch (state) { case LOADING: if (loadingResourceId == null && !loadingViewWillBeProvided) { throw new IllegalStateException( "Resource id for 'loading state' should be provided or 'loadingViewWillBeProvided' should be set"); } break; case FAILED: if (failedResourceId == null && !failedViewWillBeProvided) { throw new IllegalStateException("Resource id for 'failed state' should be provided or 'failedViewWillBeProvided' should be set"); } break; case EMPTY: if (emptyResourceId == null && !emptyViewWillBeProvided) { throw new IllegalStateException("Resource id for 'empty state' should be provided or 'emptyViewWillBeProvided' should be set"); } break; default: break; } this.state = state; } /** * Return the current State of this Section. * * @return current state of this section */ public final State getState() { return state; } /** * Check if this Section is visible. * * @return true if this Section is visible */ public final boolean isVisible() { return visible; } /** * Set if this Section is visible. * * @param visible true if this Section is visible */ public final void setVisible(final boolean visible) { this.visible = visible; } /** * Check if this Section has a header. * * @return true if this Section has a header */ public final boolean hasHeader() { return hasHeader; } /** * Set if this Section has header. * * @param hasHeader true if this Section has a header */ public final void setHasHeader(final boolean hasHeader) { this.hasHeader = hasHeader; } /** * Check if this Section has a footer. * * @return true if this Section has a footer */ public final boolean hasFooter() { return hasFooter; } /** * Set if this Section has footer. * * @param hasFooter true if this Section has a footer */ public final void setHasFooter(final boolean hasFooter) { this.hasFooter = hasFooter; } /** * Return whether the item view is provided through {@link #getItemView(ViewGroup)}. * If false, the item view is inflated using the resource from {@link #getItemResourceId()}. * * @return whether the item view is provided through {@link #getItemView(ViewGroup)}. */ public final boolean isItemViewWillBeProvided() { return itemViewWillBeProvided; } /** * Return the layout resource id of the item. * * @return layout resource id of the item */ public final Integer getItemResourceId() { return itemResourceId; } /** * Return whether the header view is provided through {@link #getHeaderView(ViewGroup)}. * If false, the header view is inflated using the resource from * {@link #getHeaderResourceId()}. * * @return whether the header view is provided through {@link #getHeaderView(ViewGroup)}. */ public final boolean isHeaderViewWillBeProvided() { return headerViewWillBeProvided; } /** * Return the layout resource id of the header. * * @return layout resource id of the header */ public final Integer getHeaderResourceId() { return headerResourceId; } /** * Return whether the footer view is provided through {@link #getFooterView(ViewGroup)}. * If false, the footer view is inflated using the resource from * {@link #getFooterResourceId()}. * * @return whether the footer view is provided through {@link #getFooterView(ViewGroup)}. */ public final boolean isFooterViewWillBeProvided() { return footerViewWillBeProvided; } /** * Return the layout resource id of the footer. * * @return layout resource id of the footer */ public final Integer getFooterResourceId() { return footerResourceId; } /** * Return whether the loading view is provided through {@link #getLoadingView(ViewGroup)}. * If false, the loading view is inflated using the resource from * {@link #getLoadingResourceId()}. * * @return whether the loading view is provided through {@link #getLoadingView(ViewGroup)}. */ public final boolean isLoadingViewWillBeProvided() { return loadingViewWillBeProvided; } /** * Return the layout resource id of the loading view. * * @return layout resource id of the loading view */ public final Integer getLoadingResourceId() { return loadingResourceId; } /** * Return whether the failed view is provided through {@link #getFailedView(ViewGroup)}. * If false, the failed view is inflated using the resource from * {@link #getFailedResourceId()}. * * @return whether the failed view is provided through {@link #getFailedView(ViewGroup)}. */ public final boolean isFailedViewWillBeProvided() { return failedViewWillBeProvided; } /** * Return the layout resource id of the failed view. * * @return layout resource id of the failed view */ public final Integer getFailedResourceId() { return failedResourceId; } /** * Return whether the empty view is provided through {@link #getEmptyView(ViewGroup)}. * If false, the empty view is inflated using the resource from * {@link #getEmptyResourceId()}. * * @return whether the empty view is provided through {@link #getEmptyView(ViewGroup)}. */ public final boolean isEmptyViewWillBeProvided() { return emptyViewWillBeProvided; } /** * Return the layout resource id of the empty view. * * @return layout resource id of the empty view */ public final Integer getEmptyResourceId() { return emptyResourceId; } /** * Return the total of items of this Section, including content items (according to the section * state) plus header and footer. * * @return total of items of this section */ public final int getSectionItemsTotal() { int contentItemsTotal; switch (state) { case LOADED: contentItemsTotal = getContentItemsTotal(); break; case LOADING: case FAILED: case EMPTY: contentItemsTotal = 1; break; default: throw new IllegalStateException("Invalid state"); } return contentItemsTotal + (hasHeader ? 1 : 0) + (hasFooter ? 1 : 0); } /** * Return the total of items of this Section. * * @return total of items of this Section */ public abstract int getContentItemsTotal(); /** * Creates the View for a single Item. This must be implemented if and only if * {@link #isItemViewWillBeProvided()} is true. * * @param parent The parent view. Note that there is no need to attach the new view. * @return View for an Item of this Section. */ public View getItemView(@SuppressWarnings("unused") final ViewGroup parent) { throw new UnsupportedOperationException( "You need to implement getItemView() if you set itemViewWillBeProvided"); } /** * Return the ViewHolder for a single Item of this Section. * * @param view View created by getItemView or inflated resource returned by getItemResourceId * @return ViewHolder for the Item of this Section */ public abstract RecyclerView.ViewHolder getItemViewHolder(View view); /** * Bind the data to the ViewHolder for an Item of this Section. * * @param holder ViewHolder for the Item of this Section * @param position position of the item in the Section, not in the RecyclerView */ public abstract void onBindItemViewHolder(RecyclerView.ViewHolder holder, int position); /** * Bind the data to the ViewHolder for an Item of this Section. * * @param holder ViewHolder for the Item of this Section * @param position position of the item in the Section, not in the RecyclerView * @param payloads A non-null and non-empty list of merged payloads. */ public void onBindItemViewHolder(final RecyclerView.ViewHolder holder, final int position, @SuppressWarnings("unused") final List<Object> payloads) { this.onBindItemViewHolder(holder, position); } /** * Creates the View for the Header. This must be implemented if and only if * {@link #isHeaderViewWillBeProvided()} is true. * * @param parent The parent view. Note that there is no need to attach the new view. * @return View for the Header of this Section. */ public View getHeaderView(@SuppressWarnings("unused") final ViewGroup parent) { throw new UnsupportedOperationException( "You need to implement getHeaderView() if you set headerViewWillBeProvided"); } /** * Return the ViewHolder for the Header of this Section. * * @param view View inflated by resource returned by getHeaderResourceId * @return ViewHolder for the Header of this Section */ public RecyclerView.ViewHolder getHeaderViewHolder(final View view) { throw new UnsupportedOperationException( "You need to implement getHeaderViewHolder() if you set headerResourceId"); } /** * Bind the data to the ViewHolder for the Header of this Section. * * @param holder ViewHolder for the Header of this Section */ @SuppressWarnings("PMD.EmptyMethodInAbstractClassShouldBeAbstract") public void onBindHeaderViewHolder(final RecyclerView.ViewHolder holder) { // Nothing to bind here. } /** * Bind the data to the ViewHolder for the Header of this Section. * * @param holder ViewHolder for the Header of this Section * @param payloads A non-null and non-empty list of merged payloads. */ @SuppressWarnings("PMD.EmptyMethodInAbstractClassShouldBeAbstract") public void onBindHeaderViewHolder(final RecyclerView.ViewHolder holder, @SuppressWarnings("unused") final List<Object> payloads) { this.onBindHeaderViewHolder(holder); } /** * Creates the View for the Footer. This must be implemented if and only if * {@link #isFooterViewWillBeProvided()} is true. * * @param parent The parent view. Note that there is no need to attach the new view. * @return View for the Footer of this Section. */ public View getFooterView(@SuppressWarnings("unused") final ViewGroup parent) { throw new UnsupportedOperationException( "You need to implement getFooterView() if you set footerViewWillBeProvided"); } /** * Return the ViewHolder for the Footer of this Section. * * @param view View inflated by resource returned by getFooterResourceId * @return ViewHolder for the Footer of this Section */ public RecyclerView.ViewHolder getFooterViewHolder(final View view) { throw new UnsupportedOperationException( "You need to implement getFooterViewHolder() if you set footerResourceId"); } /** * Bind the data to the ViewHolder for the Footer of this Section. * * @param holder ViewHolder for the Footer of this Section */ @SuppressWarnings("PMD.EmptyMethodInAbstractClassShouldBeAbstract") public void onBindFooterViewHolder(final RecyclerView.ViewHolder holder) { // Nothing to bind here. } /** * Bind the data to the ViewHolder for the Footer of this Section. * * @param holder ViewHolder for the Footer of this Section * @param payloads A non-null and non-empty list of merged payloads. */ @SuppressWarnings("PMD.EmptyMethodInAbstractClassShouldBeAbstract") public void onBindFooterViewHolder(final RecyclerView.ViewHolder holder, @SuppressWarnings("unused") final List<Object> payloads) { this.onBindFooterViewHolder(holder); } /** * Creates the View for the Loading state. This must be implemented if and only if * {@link #isLoadingViewWillBeProvided()} is true. * * @param parent The parent view. Note that there is no need to attach the new view. * @return View for the Loading state of this Section. */ public View getLoadingView(@SuppressWarnings("unused") final ViewGroup parent) { throw new UnsupportedOperationException( "You need to implement getLoadingView() if you set loadingViewWillBeProvided"); } /** * Return the ViewHolder for the Loading state of this Section. * * @param view View inflated by resource returned by getItemResourceId * @return ViewHolder for the Loading state of this Section */ public RecyclerView.ViewHolder getLoadingViewHolder(final View view) { throw new UnsupportedOperationException( "You need to implement getLoadingViewHolder() if you set loadingResourceId"); } /** * Bind the data to the ViewHolder for Loading state of this Section. * * @param holder ViewHolder for the Loading state of this Section */ @SuppressWarnings({"EmptyMethod", "PMD.EmptyMethodInAbstractClassShouldBeAbstract"}) public void onBindLoadingViewHolder(@SuppressWarnings("unused") final RecyclerView.ViewHolder holder) { // Nothing to bind here. } /** * Bind the data to the ViewHolder for Loading state of this Section. * * @param holder ViewHolder for the Loading state of this Section * @param payloads A non-null and non-empty list of merged payloads. */ @SuppressWarnings({"EmptyMethod", "PMD.EmptyMethodInAbstractClassShouldBeAbstract"}) public void onBindLoadingViewHolder(final RecyclerView.ViewHolder holder, @SuppressWarnings("unused") final List<Object> payloads) { this.onBindLoadingViewHolder(holder); } /** * Creates the View for the Failed state. This must be implemented if and only if * {@link #isFailedViewWillBeProvided()} is true. * * @param parent The parent view. Note that there is no need to attach the new view. * @return View for the Failed state of this Section. */ public View getFailedView(@SuppressWarnings("unused") final ViewGroup parent) { throw new UnsupportedOperationException( "You need to implement getFailedView() if you set failedViewWillBeProvided"); } /** * Return the ViewHolder for the Failed state of this Section. * * @param view View inflated by resource returned by getItemResourceId * @return ViewHolder for the Failed of this Section */ public RecyclerView.ViewHolder getFailedViewHolder(final View view) { throw new UnsupportedOperationException( "You need to implement getFailedViewHolder() if you set failedResourceId"); } /** * Bind the data to the ViewHolder for the Failed state of this Section. * * @param holder ViewHolder for the Failed state of this Section */ @SuppressWarnings({"EmptyMethod", "PMD.EmptyMethodInAbstractClassShouldBeAbstract"}) public void onBindFailedViewHolder(final RecyclerView.ViewHolder holder) { // Nothing to bind here. } /** * Bind the data to the ViewHolder for the Failed state of this Section. * * @param holder ViewHolder for the Failed state of this Section * @param payloads A non-null and non-empty list of merged payloads. */ @SuppressWarnings({"EmptyMethod", "PMD.EmptyMethodInAbstractClassShouldBeAbstract"}) public void onBindFailedViewHolder(final RecyclerView.ViewHolder holder, @SuppressWarnings("unused") final List<Object> payloads) { this.onBindFailedViewHolder(holder); } /** * Creates the View for the Empty state. This must be implemented if and only if * {@link #isEmptyViewWillBeProvided()} is true. * * @param parent The parent view. Note that there is no need to attach the new view. * @return View for the Empty state of this Section. */ public View getEmptyView(@SuppressWarnings("unused") final ViewGroup parent) { throw new UnsupportedOperationException( "You need to implement getEmptyView() if you set emptyViewWillBeProvided"); } /** * Return the ViewHolder for the Empty state of this Section. * * @param view View inflated by resource returned by getItemResourceId * @return ViewHolder for the Empty of this Section */ public RecyclerView.ViewHolder getEmptyViewHolder(final View view) { throw new UnsupportedOperationException( "You need to implement getEmptyViewHolder() if you set emptyResourceId"); } /** * Bind the data to the ViewHolder for the Empty state of this Section. * * @param holder ViewHolder for the Empty state of this Section */ @SuppressWarnings({"EmptyMethod", "PMD.EmptyMethodInAbstractClassShouldBeAbstract"}) public void onBindEmptyViewHolder(@SuppressWarnings("unused") final RecyclerView.ViewHolder holder) { // Nothing to bind here. } /** * Bind the data to the ViewHolder for the Empty state of this Section. * * @param holder ViewHolder for the Empty state of this Section * @param payloads A non-null and non-empty list of merged payloads. */ @SuppressWarnings({"EmptyMethod", "PMD.EmptyMethodInAbstractClassShouldBeAbstract"}) public void onBindEmptyViewHolder(final RecyclerView.ViewHolder holder, @SuppressWarnings("unused") final List<Object> payloads) { this.onBindEmptyViewHolder(holder); } }