diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java index 99ee943..6df5095 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java @@ -18,12 +18,11 @@ import ru.touchin.roboswag.components.utils.UiUtils; * Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders. * Default {@link #getItemViewType} is generating on construction of object. * - * @param Type of {@link BindableViewHolder} of delegate; - * @param Type of items to bind to {@link BindableViewHolder}s. + * @param Type of {@link BindableViewHolder} of delegate. */ @SuppressWarnings("PMD.TooManyMethods") -//TooManyMethods: it's ok as it is LifecycleBindable -public abstract class AdapterDelegate implements LifecycleBindable { +//TooManyMethods: it's ok +public abstract class AdapterDelegate implements LifecycleBindable { @NonNull private final LifecycleBindable parentLifecycleBindable; @@ -53,29 +52,6 @@ public abstract class AdapterDelegate payloads, - final int adapterPosition, final int itemCollectionPosition) { - //do nothing by default - } - @SuppressWarnings("CPD-START") //CPD: it is same as in other implementation based on BaseLifecycleBindable @NonNull diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java new file mode 100644 index 0000000..20bad88 --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java @@ -0,0 +1,81 @@ +package ru.touchin.roboswag.components.adapters; + +import android.support.annotation.NonNull; +import android.view.ViewGroup; + +import java.util.List; + +import ru.touchin.roboswag.components.utils.LifecycleBindable; + +/** + * Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders. + * Such delegates are creating and binding ViewHolders for specific items. + * Default {@link #getItemViewType} is generating on construction of object. + * + * @param Type of {@link BindableViewHolder} of delegate; + * @param Type of items to bind to {@link BindableViewHolder}s. + */ +public abstract class ItemAdapterDelegate extends AdapterDelegate { + + public ItemAdapterDelegate(@NonNull final LifecycleBindable parentLifecycleBindable) { + super(parentLifecycleBindable); + } + + /** + * Returns if object is processable by this delegate. + * This item will be casted to {@link TItem} and passes to {@link #onBindViewHolder(TViewHolder, TItem, int, int)}. + * + * @param item Item to check; + * @param positionInAdapter Position of item in adapter; + * @param itemCollectionPosition Position of item in collection that contains item; + * @return True if item is processable by this delegate. + */ + public abstract boolean isForViewType(@NonNull final Object item, final int positionInAdapter, final int itemCollectionPosition); + + /** + * Returns unique ID of item to support stable ID's logic of RecyclerView's adapter. + * + * @param item Item to check; + * @param positionInAdapter Position of item in adapter; + * @param positionInCollection Position of item in collection that contains item; + * @return Unique item ID. + */ + public long getItemId(@NonNull final TItem item, final int positionInAdapter, final int positionInCollection) { + return 0; + } + + /** + * Creates ViewHolder to bind item to it later. + * + * @param parent Container of ViewHolder's view. + * @return New ViewHolder. + */ + @NonNull + public abstract TViewHolder onCreateViewHolder(@NonNull final ViewGroup parent); + + /** + * Binds item to created by this object ViewHolder. + * + * @param holder ViewHolder to bind item to; + * @param item Item to check; + * @param positionInAdapter Position of item in adapter; + * @param positionInCollection Position of item in collection that contains item; + */ + public abstract void onBindViewHolder(@NonNull final TViewHolder holder, @NonNull final TItem item, + final int positionInAdapter, final int positionInCollection); + + /** + * Binds item with payloads to created by this object ViewHolder. + * + * @param holder ViewHolder to bind item to; + * @param item Item to check; + * @param payloads Payloads; + * @param positionInAdapter Position of item in adapter; + * @param positionInCollection Position of item in collection that contains item; + */ + public void onBindViewHolder(@NonNull final TViewHolder holder, @NonNull final TItem item, @NonNull final List payloads, + final int positionInAdapter, final int positionInCollection) { + //do nothing by default + } + +} diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java index 68fb759..bc4a4e3 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java @@ -30,6 +30,8 @@ import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -56,12 +58,22 @@ import ru.touchin.roboswag.core.utils.ShouldNotHappenException; * @param Type of items to bind to ViewHolders; * @param Type of ViewHolders to show items. */ -@SuppressWarnings("unchecked") +@SuppressWarnings({"unchecked", "PMD.TooManyMethods"}) +//TooManyMethods: it's ok public abstract class ObservableCollectionAdapter extends RecyclerView.Adapter { private static final int PRE_LOADING_COUNT = 20; + private static boolean inDebugMode; + + /** + * Enables debugging features like checking concurrent delegates. + */ + public static void setInDebugMode() { + inDebugMode = true; + } + @NonNull private final BehaviorSubject>> observableCollectionSubject = BehaviorSubject.createDefault(new Optional<>(null)); @@ -80,7 +92,7 @@ public abstract class ObservableCollectionAdapter attachedRecyclerViews = new LinkedList<>(); @NonNull - private final List> delegates = new ArrayList<>(); + private final List> delegates = new ArrayList<>(); public ObservableCollectionAdapter(@NonNull final LifecycleBindable lifecycleBindable) { super(); @@ -302,23 +314,38 @@ public abstract class ObservableCollectionAdapter> getDelegates() { + public List> getDelegates() { return Collections.unmodifiableList(delegates); } /** - * Adds {@link AdapterDelegate} to adapter. + * Adds {@link ItemAdapterDelegate} to adapter. * * @param delegate Delegate to add. */ - public void addDelegate(@NonNull final AdapterDelegate delegate) { - for (final AdapterDelegate addedDelegate : delegates) { - if (addedDelegate.getItemViewType() == delegate.getItemViewType()) { - Lc.assertion("AdapterDelegate with viewType=" + delegate.getItemViewType() + " already added"); - return; + public void addDelegate(@NonNull final ItemAdapterDelegate delegate) { + addDelegateInternal(delegate); + } + + /** + * Adds {@link PositionAdapterDelegate} to adapter. + * + * @param delegate Delegate to add. + */ + public void addDelegate(@NonNull final PositionAdapterDelegate delegate) { + addDelegateInternal(delegate); + } + + private void addDelegateInternal(@NonNull final AdapterDelegate delegate) { + if (inDebugMode) { + for (final AdapterDelegate addedDelegate : delegates) { + if (addedDelegate.getItemViewType() == delegate.getItemViewType()) { + Lc.assertion("AdapterDelegate with viewType=" + delegate.getItemViewType() + " already added"); + return; + } } } - delegates.add((AdapterDelegate) delegate); + delegates.add(delegate); notifyDataSetChanged(); } @@ -327,41 +354,88 @@ public abstract class ObservableCollectionAdapter delegate) { - delegates.remove((AdapterDelegate) delegate); + public void removeDelegate(@NonNull final AdapterDelegate delegate) { + delegates.remove(delegate); notifyDataSetChanged(); } + private void checkDelegates(@Nullable final AdapterDelegate alreadyPickedDelegate, @NonNull final AdapterDelegate currentDelegate) { + if (alreadyPickedDelegate != null) { + throw new ShouldNotHappenException("Concurrent delegates: " + currentDelegate + " and " + alreadyPickedDelegate); + } + } + + private int getItemPositionInCollection(final int positionInAdapter) { + final int shiftedPosition = positionInAdapter - getHeadersCount(); + return shiftedPosition >= 0 && shiftedPosition < innerCollection.size() ? shiftedPosition : -1; + } + + @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.ModifiedCyclomaticComplexity", "PMD.StdCyclomaticComplexity", "PMD.NPathComplexity"}) + //Complexity: because of debug code @Override public int getItemViewType(final int positionInAdapter) { - final int positionInCollection = positionInAdapter - getHeadersCount(); - if (positionInCollection < 0 || positionInCollection >= innerCollection.size()) { - return super.getItemViewType(positionInAdapter); - } - final TItem item = innerCollection.get(positionInCollection); - for (final AdapterDelegate delegate : delegates) { - if (delegate.isForViewType(item, positionInAdapter, positionInCollection)) { - return delegate.getItemViewType(); + AdapterDelegate delegateOfViewType = null; + final int positionInCollection = getItemPositionInCollection(positionInAdapter); + final TItem item = positionInCollection >= 0 ? innerCollection.get(positionInCollection) : null; + for (final AdapterDelegate delegate : delegates) { + if (delegate instanceof ItemAdapterDelegate) { + if (item != null && ((ItemAdapterDelegate) delegate).isForViewType(item, positionInAdapter, positionInCollection)) { + checkDelegates(delegateOfViewType, delegate); + delegateOfViewType = delegate; + if (!inDebugMode) { + break; + } + } + } else if (delegate instanceof PositionAdapterDelegate) { + if (((PositionAdapterDelegate) delegate).isForViewType(positionInAdapter)) { + checkDelegates(delegateOfViewType, delegate); + delegateOfViewType = delegate; + if (!inDebugMode) { + break; + } + } + } else { + Lc.assertion("Delegate of type " + delegate.getClass()); } } - return super.getItemViewType(positionInAdapter); + + return delegateOfViewType != null ? delegateOfViewType.getItemViewType() : super.getItemViewType(positionInAdapter); } @Override public long getItemId(final int positionInAdapter) { - final int positionInCollection = positionInAdapter - getHeadersCount(); - if (positionInCollection < 0 || positionInCollection >= innerCollection.size()) { - return super.getItemId(positionInAdapter); - } + final LongContainer result = new LongContainer(); + tryDelegateAction(positionInAdapter, + (itemAdapterDelegate, positionInCollection) -> + result.value = itemAdapterDelegate.getItemId(innerCollection.get(positionInCollection), + positionInAdapter, positionInCollection), + positionAdapterDelegate -> result.value = positionAdapterDelegate.getItemId(positionInAdapter), + (positionInCollection) -> result.value = super.getItemId(positionInAdapter)); + return result.value; + } - final int itemViewType = getItemViewType(positionInAdapter); - final TItem item = innerCollection.get(positionInCollection); - for (final AdapterDelegate delegate : delegates) { - if (delegate.getItemViewType() == itemViewType) { - return delegate.getItemId(item, positionInAdapter, positionInCollection); + private void tryDelegateAction(final int positionInAdapter, + @NonNull final BiConsumer itemAdapterDelegateAction, + @NonNull final Consumer positionAdapterDelegateAction, + @NonNull final Consumer defaultAction) { + final int viewType = getItemViewType(positionInAdapter); + final int positionInCollection = getItemPositionInCollection(positionInAdapter); + for (final AdapterDelegate delegate : delegates) { + if (delegate instanceof ItemAdapterDelegate) { + if (positionInCollection >= 0 && viewType == delegate.getItemViewType()) { + itemAdapterDelegateAction.accept((ItemAdapterDelegate) delegate, positionInCollection); + return; + } + } else if (delegate instanceof PositionAdapterDelegate) { + if (viewType == delegate.getItemViewType()) { + positionAdapterDelegateAction.accept((PositionAdapterDelegate) delegate); + return; + } + } else { + Lc.assertion("Delegate of type " + delegate.getClass()); } } - return super.getItemId(positionInAdapter); + defaultAction.accept(positionInCollection); } @Override @@ -372,7 +446,7 @@ public abstract class ObservableCollectionAdapter delegate : delegates) { + for (final AdapterDelegate delegate : delegates) { if (delegate.getItemViewType() == viewType) { return delegate.onCreateViewHolder(parent); } @@ -384,26 +458,40 @@ public abstract class ObservableCollectionAdapter= innerCollection.size()) { - return; - } - - updateMoreAutoLoadingRequest(positionInCollection); - bindItemViewHolder(holder, null, positionInAdapter, positionInCollection); + tryDelegateAction(positionInAdapter, + (itemAdapterDelegate, positionInCollection) -> { + bindItemViewHolder(itemAdapterDelegate, holder, innerCollection.get(positionInCollection), + null, positionInAdapter, positionInCollection); + updateMoreAutoLoadingRequest(positionInCollection); + }, + positionAdapterDelegate -> positionAdapterDelegate.onBindViewHolder(holder, positionInAdapter), + (positionInCollection) -> { + if (positionInCollection >= 0) { + bindItemViewHolder(null, holder, innerCollection.get(positionInCollection), null, positionInAdapter, positionInCollection); + } + }); } @Override public void onBindViewHolder(@NonNull final BindableViewHolder holder, final int positionInAdapter, @NonNull final List payloads) { super.onBindViewHolder(holder, positionInAdapter, payloads); - final int positionInCollection = positionInAdapter - getHeadersCount(); - if (positionInCollection < 0 || positionInCollection >= innerCollection.size()) { - return; - } - bindItemViewHolder(holder, payloads, positionInAdapter, positionInCollection); + tryDelegateAction(positionInAdapter, + (itemAdapterDelegate, positionInCollection) -> { + bindItemViewHolder(itemAdapterDelegate, holder, innerCollection.get(positionInCollection), + payloads, positionInAdapter, positionInCollection); + updateMoreAutoLoadingRequest(positionInCollection); + }, + positionAdapterDelegate -> positionAdapterDelegate.onBindViewHolder(holder, positionInAdapter), + (positionInCollection) -> { + if (positionInCollection >= 0) { + bindItemViewHolder(null, holder, innerCollection.get(positionInCollection), + payloads, positionInAdapter, positionInCollection); + } + }); } - private void bindItemViewHolder(@NonNull final BindableViewHolder holder, @Nullable final List payloads, + private void bindItemViewHolder(@Nullable final ItemAdapterDelegate itemAdapterDelegate, + @NonNull final BindableViewHolder holder, @NonNull final TItem item, @Nullable final List payloads, final int positionInAdapter, final int positionInCollection) { final TItemViewHolder itemViewHolder; try { @@ -412,24 +500,19 @@ public abstract class ObservableCollectionAdapter delegate : delegates) { - if (itemViewType == delegate.getItemViewType()) { - if (payloads == null) { - delegate.onBindViewHolder(itemViewHolder, item, positionInAdapter, positionInCollection); - } else { - delegate.onBindViewHolder(itemViewHolder, item, payloads, positionInAdapter, positionInCollection); - } - return; + if (itemAdapterDelegate != null) { + if (payloads == null) { + itemAdapterDelegate.onBindViewHolder(itemViewHolder, item, positionInAdapter, positionInCollection); + } else { + itemAdapterDelegate.onBindViewHolder(itemViewHolder, item, payloads, positionInAdapter, positionInCollection); } - } - if (payloads == null) { - onBindItemToViewHolder(itemViewHolder, positionInAdapter, item); } else { - onBindItemToViewHolder(itemViewHolder, positionInAdapter, item, payloads); + if (payloads == null) { + onBindItemToViewHolder(itemViewHolder, positionInAdapter, item); + } else { + onBindItemToViewHolder(itemViewHolder, positionInAdapter, item, payloads); + } } } @@ -467,7 +550,7 @@ public abstract class ObservableCollectionAdapter payloads) { - // do nothing by default + // do nothing by default - let delegates do it } @Nullable public TItem getItem(final int positionInAdapter) { - final int positionInCollection = positionInAdapter - getHeadersCount(); - return positionInCollection < 0 || positionInCollection >= innerCollection.size() ? null : innerCollection.get(positionInCollection); + final int positionInCollection = getItemPositionInCollection(positionInAdapter); + return positionInCollection >= 0 ? innerCollection.get(positionInCollection) : null; } /** @@ -578,4 +661,10 @@ public abstract class ObservableCollectionAdapter Type of {@link BindableViewHolder} of delegate. + */ +public abstract class PositionAdapterDelegate extends AdapterDelegate { + + public PositionAdapterDelegate(@NonNull final LifecycleBindable parentLifecycleBindable) { + super(parentLifecycleBindable); + } + + /** + * Returns if object is processable by this delegate. + * + * @param positionInAdapter Position of item in adapter; + * @return True if item is processable by this delegate. + */ + public abstract boolean isForViewType(final int positionInAdapter); + + /** + * Returns unique ID of item to support stable ID's logic of RecyclerView's adapter. + * + * @param positionInAdapter Position of item in adapter; + * @return Unique item ID. + */ + public long getItemId(final int positionInAdapter) { + return 0; + } + + /** + * Creates ViewHolder to bind position to it later. + * + * @param parent Container of ViewHolder's view. + * @return New ViewHolder. + */ + @NonNull + public abstract TViewHolder onCreateViewHolder(@NonNull final ViewGroup parent); + + /** + * Binds position to ViewHolder. + * + * @param holder ViewHolder to bind position to; + * @param positionInAdapter Position of item in adapter. + */ + public abstract void onBindViewHolder(@NonNull final TViewHolder holder, final int positionInAdapter); + + /** + * Binds position with payloads to ViewHolder. + * + * @param holder ViewHolder to bind position to; + * @param payloads Payloads; + * @param positionInAdapter Position of item in adapter. + */ + public void onBindViewHolder(@NonNull final TViewHolder holder, @NonNull final List payloads, final int positionInAdapter) { + //do nothing by default + } + +} diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java b/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java index c69fdeb..423fc98 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java @@ -44,6 +44,8 @@ public class SimpleActionBarDrawerToggle extends ActionBarDrawerToggle @NonNull private final View sidebar; + private boolean isInvalidateOptionsMenuSupported = true; + private boolean hamburgerShowed; private boolean sidebarDisabled; @@ -203,7 +205,9 @@ public class SimpleActionBarDrawerToggle extends ActionBarDrawerToggle @Override public void onDrawerClosed(@NonNull final View view) { - activity.supportInvalidateOptionsMenu(); + if (isInvalidateOptionsMenuSupported) { + activity.supportInvalidateOptionsMenu(); + } } /** @@ -218,7 +222,18 @@ public class SimpleActionBarDrawerToggle extends ActionBarDrawerToggle @Override public void onDrawerOpened(@NonNull final View drawerView) { activity.hideSoftInput(); - activity.supportInvalidateOptionsMenu(); + if (isInvalidateOptionsMenuSupported) { + activity.supportInvalidateOptionsMenu(); + } + } + + /** + * Set turn on/off invocation of supportInvalidateOptionsMenu + * + * @param isInvalidateOptionsMenuSupported flag for turning on/off invocation. + */ + public void setInvalidateOptionsMenuSupported(final boolean isInvalidateOptionsMenuSupported) { + this.isInvalidateOptionsMenuSupported = isInvalidateOptionsMenuSupported; } @Override