Merge branch 'feature/general_improvements' into master-rx-java-2

# Conflicts:
#	src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java
#	src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java
#	src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java
#	src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java
#	src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java
This commit is contained in:
Gavriil Sitnikov 2017-04-20 19:54:12 +03:00
commit a4a0e0d4b8
2 changed files with 490 additions and 102 deletions

View File

@ -0,0 +1,253 @@
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;
import ru.touchin.roboswag.components.utils.UiUtils;
import rx.Completable;
import rx.Observable;
import rx.Single;
import rx.Subscription;
import rx.functions.Action0;
import rx.functions.Action1;
/**
* 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 <TViewHolder> Type of {@link BindableViewHolder} of delegate;
* @param <TItem> Type of items to bind to {@link BindableViewHolder}s.
*/
@SuppressWarnings("PMD.TooManyMethods")
//TooManyMethods: it's ok as it is LifecycleBindable
public abstract class AdapterDelegate<TViewHolder extends BindableViewHolder, TItem> implements LifecycleBindable {
@NonNull
private final LifecycleBindable parentLifecycleBindable;
private final int defaultItemViewType;
public AdapterDelegate(@NonNull final LifecycleBindable parentLifecycleBindable) {
this.parentLifecycleBindable = parentLifecycleBindable;
this.defaultItemViewType = UiUtils.OfViews.generateViewId();
}
/**
* Returns parent {@link LifecycleBindable} that this delegate created from (e.g. Activity or ViewController).
*
* @return Parent {@link LifecycleBindable}.
*/
@NonNull
public LifecycleBindable getParentLifecycleBindable() {
return parentLifecycleBindable;
}
/**
* Unique ID of AdapterDelegate.
*
* @return Unique ID.
*/
public int getItemViewType() {
return defaultItemViewType;
}
/**
* 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 adapterPosition 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 adapterPosition, final int itemCollectionPosition);
/**
* Returns unique ID of item to support stable ID's logic of RecyclerView's adapter.
*
* @param item Item to check;
* @param adapterPosition Position of item in adapter;
* @param itemCollectionPosition Position of item in collection that contains item;
* @return Unique item ID.
*/
public long getItemId(@NonNull final TItem item, final int adapterPosition, final int itemCollectionPosition) {
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 adapterPosition Position of item in adapter;
* @param itemCollectionPosition Position of item in collection that contains item;
*/
public abstract void onBindViewHolder(@NonNull final TViewHolder holder, @NonNull final TItem item,
final int adapterPosition, final int itemCollectionPosition);
/**
* 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 adapterPosition Position of item in adapter;
* @param itemCollectionPosition Position of item in collection that contains item;
*/
public void onBindViewHolder(@NonNull final TViewHolder holder, @NonNull final TItem item, @NonNull final List<Object> 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
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable) {
return parentLifecycleBindable.untilStop(observable);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable, @NonNull final Action1<T> onNextAction) {
return parentLifecycleBindable.untilStop(observable, onNextAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction) {
return parentLifecycleBindable.untilStop(observable, onNextAction, onErrorAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction,
@NonNull final Action0 onCompletedAction) {
return parentLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single) {
return parentLifecycleBindable.untilStop(single);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single, @NonNull final Action1<T> onSuccessAction) {
return parentLifecycleBindable.untilStop(single, onSuccessAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single,
@NonNull final Action1<T> onSuccessAction,
@NonNull final Action1<Throwable> onErrorAction) {
return parentLifecycleBindable.untilStop(single, onSuccessAction, onErrorAction);
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable) {
return parentLifecycleBindable.untilStop(completable);
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) {
return parentLifecycleBindable.untilStop(completable, onCompletedAction);
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable,
@NonNull final Action0 onCompletedAction,
@NonNull final Action1<Throwable> onErrorAction) {
return parentLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable) {
return parentLifecycleBindable.untilDestroy(observable);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable, @NonNull final Action1<T> onNextAction) {
return parentLifecycleBindable.untilDestroy(observable, onNextAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction) {
return parentLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction,
@NonNull final Action0 onCompletedAction) {
return parentLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single) {
return parentLifecycleBindable.untilDestroy(single);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single, @NonNull final Action1<T> onSuccessAction) {
return parentLifecycleBindable.untilDestroy(single, onSuccessAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single,
@NonNull final Action1<T> onSuccessAction,
@NonNull final Action1<Throwable> onErrorAction) {
return parentLifecycleBindable.untilDestroy(single, onSuccessAction, onErrorAction);
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable) {
return parentLifecycleBindable.untilDestroy(completable);
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) {
return parentLifecycleBindable.untilDestroy(completable, onCompletedAction);
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable,
@NonNull final Action0 onCompletedAction,
@NonNull final Action1<Throwable> onErrorAction) {
return parentLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction);
}
}

View File

@ -22,19 +22,17 @@ package ru.touchin.roboswag.components.adapters;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.subjects.BehaviorSubject;
import ru.touchin.roboswag.components.utils.LifecycleBindable;
import ru.touchin.roboswag.components.utils.UiUtils;
@ -44,11 +42,13 @@ import ru.touchin.roboswag.core.observables.collections.ObservableCollection;
import ru.touchin.roboswag.core.observables.collections.ObservableList;
import ru.touchin.roboswag.core.observables.collections.loadable.LoadingMoreList;
import ru.touchin.roboswag.core.utils.Optional;
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
/**
* Created by Gavriil Sitnikov on 20/11/2015.
* Adapter based on {@link ObservableCollection} and providing some useful features like:
* - item-based binding by {@link #onBindItemToViewHolder(ViewHolder, int, Object)}} method;
* - item-based binding method;
* - delegates by {@link AdapterDelegate} over itemViewType logic;
* - item click listener setup by {@link #setOnItemClickListener(OnItemClickListener)};
* - allows to inform about footers/headers by overriding base create/bind methods and {@link #getHeadersCount()} plus {@link #getFootersCount()};
* - by default it is pre-loading items for collections like {@link ru.touchin.roboswag.core.observables.collections.loadable.LoadingMoreList}.
@ -56,27 +56,31 @@ import ru.touchin.roboswag.core.utils.Optional;
* @param <TItem> Type of items to bind to ViewHolders;
* @param <TItemViewHolder> Type of ViewHolders to show items.
*/
public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends ObservableCollectionAdapter.ViewHolder>
@SuppressWarnings("unchecked")
public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends BindableViewHolder>
extends RecyclerView.Adapter<BindableViewHolder> {
private static final int PRE_LOADING_COUNT = 10;
private static final int PRE_LOADING_COUNT = 20;
@NonNull
private final BehaviorSubject<Optional<ObservableCollection<TItem>>> observableCollectionSubject
= BehaviorSubject.createDefault(new Optional<>(null));
@NonNull
private final BehaviorSubject<Boolean> moreAutoLoadingRequested = BehaviorSubject.create();
@NonNull
private final LifecycleBindable lifecycleBindable;
@Nullable
private OnItemClickListener<TItem> onItemClickListener;
private Object onItemClickListener;
private int lastUpdatedChangeNumber = -1;
@NonNull
private final Observable historyPreLoadingObservable;
@NonNull
private final ObservableList<TItem> innerCollection = new ObservableList<>();
private boolean anyChangeApplied;
private long itemClickDelayMillis;
@NonNull
private final List<RecyclerView> attachedRecyclerViews = new LinkedList<>();
@NonNull
private final List<AdapterDelegate<TItemViewHolder, TItem>> delegates = new ArrayList<>();
public ObservableCollectionAdapter(@NonNull final LifecycleBindable lifecycleBindable) {
super();
@ -89,16 +93,33 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
innerCollection.clear();
return Observable.empty();
}
innerCollection.set(collection.getItems());
return collection.observeChanges().observeOn(AndroidSchedulers.mainThread());
}), this::onApplyChanges);
historyPreLoadingObservable = observableCollectionSubject
.switchMap(optional -> {
final ObservableCollection<TItem> collection = optional.get();
lifecycleBindable.untilDestroy(createMoreAutoLoadingObservable());
}
@NonNull
private Observable createMoreAutoLoadingObservable() {
return observableCollectionSubject
.switchMap(collectionOptional -> {
final ObservableCollection<TItem> collection = collectionOptional.get();
if (!(collection instanceof LoadingMoreList)) {
return Observable.empty();
}
final int size = collection.size();
return ((LoadingMoreList) collection).loadRange(size, size + PRE_LOADING_COUNT).toObservable();
return moreAutoLoadingRequested
.distinctUntilChanged()
.switchMap(requested -> {
if (!requested) {
return Observable.empty();
}
final int size = collection.size();
return ((LoadingMoreList<?, ?, ?>) collection)
.loadRange(size, size + PRE_LOADING_COUNT)
.onErrorReturnItem(new ArrayList<>())
.toObservable()
.doOnComplete(() -> moreAutoLoadingRequested.onNext(false));
});
});
}
@ -124,7 +145,7 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
/**
* Returns if any change of source collection applied to adapter.
* It's important to not show some footers or header before first change have applied.
* It's important to not show some footers or headers before first change have applied.
*
* @return True id any change applied.
*/
@ -275,6 +296,74 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
return 0;
}
/**
* Returns list of added delegates.
*
* @return List of {@link AdapterDelegate}.
*/
@NonNull
public List<AdapterDelegate<TItemViewHolder, TItem>> getDelegates() {
return Collections.unmodifiableList(delegates);
}
/**
* Adds {@link AdapterDelegate} to adapter.
*
* @param delegate Delegate to add.
*/
public void addDelegate(@NonNull final AdapterDelegate<? extends TItemViewHolder, ? extends TItem> delegate) {
for (final AdapterDelegate addedDelegate : delegates) {
if (addedDelegate.getItemViewType() == delegate.getItemViewType()) {
Lc.assertion("AdapterDelegate with viewType=" + delegate.getItemViewType() + " already added");
return;
}
}
delegates.add((AdapterDelegate<TItemViewHolder, TItem>) delegate);
notifyDataSetChanged();
}
/**
* Removes {@link AdapterDelegate} from adapter.
*
* @param delegate Delegate to remove.
*/
public void removeDelegate(@NonNull final AdapterDelegate<? extends TItemViewHolder, ? extends TItem> delegate) {
delegates.remove((AdapterDelegate<TItemViewHolder, TItem>) delegate);
notifyDataSetChanged();
}
@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<?, TItem> delegate : delegates) {
if (delegate.isForViewType(item, positionInAdapter, positionInCollection)) {
return delegate.getItemViewType();
}
}
return 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 int itemViewType = getItemViewType(positionInAdapter);
final TItem item = innerCollection.get(positionInCollection);
for (final AdapterDelegate<?, TItem> delegate : delegates) {
if (delegate.getItemViewType() == itemViewType) {
return delegate.getItemId(item, positionInAdapter, positionInCollection);
}
}
return super.getItemId(positionInAdapter);
}
@Override
public int getItemCount() {
return getHeadersCount() + innerCollection.size() + getFootersCount();
@ -282,18 +371,40 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
@NonNull
@Override
public abstract BindableViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType);
public BindableViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
for (final AdapterDelegate<?, TItem> delegate : delegates) {
if (delegate.getItemViewType() == viewType) {
return delegate.onCreateViewHolder(parent);
}
}
throw new ShouldNotHappenException("Add some AdapterDelegate or override this method");
}
@SuppressWarnings({"unchecked", "deprecation"})
@Override
public void onBindViewHolder(@NonNull final BindableViewHolder holder, final int position) {
public void onBindViewHolder(@NonNull final BindableViewHolder holder, final int positionInAdapter) {
lastUpdatedChangeNumber = innerCollection.getChangesCount();
final int itemPosition = position - getHeadersCount();
if (itemPosition < 0 || itemPosition >= innerCollection.size()) {
final int positionInCollection = positionInAdapter - getHeadersCount();
if (positionInCollection < 0 || positionInCollection >= innerCollection.size()) {
return;
}
updateMoreAutoLoadingRequest(positionInCollection);
bindItemViewHolder(holder, null, positionInAdapter, positionInCollection);
}
@Override
public void onBindViewHolder(@NonNull final BindableViewHolder holder, final int positionInAdapter, @NonNull final List<Object> payloads) {
super.onBindViewHolder(holder, positionInAdapter, payloads);
final int positionInCollection = positionInAdapter - getHeadersCount();
if (positionInCollection < 0 || positionInCollection >= innerCollection.size()) {
return;
}
bindItemViewHolder(holder, payloads, positionInAdapter, positionInCollection);
}
private void bindItemViewHolder(@NonNull final BindableViewHolder holder, @Nullable final List<Object> payloads,
final int positionInAdapter, final int positionInCollection) {
final TItemViewHolder itemViewHolder;
try {
itemViewHolder = (TItemViewHolder) holder;
@ -301,29 +412,79 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
Lc.assertion(exception);
return;
}
final TItem item = innerCollection.get(itemPosition);
itemViewHolder.setAdapter(this);
onBindItemToViewHolder(itemViewHolder, position, item);
itemViewHolder.bindPosition(position);
if (onItemClickListener != null && !isOnClickListenerDisabled(item)) {
UiUtils.setOnRippleClickListener(holder.itemView, () -> onItemClickListener.onItemClicked(item, position), getItemClickDelay());
final TItem item = innerCollection.get(positionInCollection);
final int itemViewType = getItemViewType(positionInAdapter);
updateClickListener(holder, item, positionInAdapter, positionInCollection);
for (final AdapterDelegate<TItemViewHolder, TItem> 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 (payloads == null) {
onBindItemToViewHolder(itemViewHolder, positionInAdapter, item);
} else {
onBindItemToViewHolder(itemViewHolder, positionInAdapter, item, payloads);
}
}
private void updateClickListener(@NonNull final BindableViewHolder holder, @NonNull final TItem item,
final int positionInAdapter, final int positionInCollection) {
if (onItemClickListener != null && !isOnClickListenerDisabled(item, positionInAdapter, positionInCollection)) {
UiUtils.setOnRippleClickListener(holder.itemView,
() -> {
if (onItemClickListener instanceof OnItemClickListener) {
((OnItemClickListener) onItemClickListener).onItemClicked(item);
} else if (onItemClickListener instanceof OnItemWithPositionClickListener) {
((OnItemWithPositionClickListener) onItemClickListener).onItemClicked(item, positionInAdapter, positionInCollection);
} else {
Lc.assertion("Unexpected onItemClickListener type " + onItemClickListener);
}
},
itemClickDelayMillis);
}
}
private void updateMoreAutoLoadingRequest(final int positionInCollection) {
if (positionInCollection > innerCollection.size() - PRE_LOADING_COUNT) {
return;
}
moreAutoLoadingRequested.onNext(true);
}
/**
* Method to bind item (from {@link #getObservableCollection()}) to item-specific ViewHolder.
* It is not calling for headers and footer which counts are returned by {@link #getHeadersCount()} and @link #getFootersCount()}.
*
* @param holder ViewHolder to bind item to;
* @param position Position of ViewHolder (NOT item!);
* @param item Item returned by position (WITH HEADER OFFSET!).
* @param holder ViewHolder to bind item to;
* @param positionInAdapter Position of ViewHolder (NOT item!);
* @param item Item returned by position (WITH HEADER OFFSET!).
*/
protected abstract void onBindItemToViewHolder(@NonNull TItemViewHolder holder, int position, @NonNull TItem item);
protected abstract void onBindItemToViewHolder(@NonNull TItemViewHolder holder, int positionInAdapter, @NonNull TItem item);
/**
* Method to bind item (from {@link #getObservableCollection()}) to item-specific ViewHolder with payloads.
* It is not calling for headers and footer which counts are returned by {@link #getHeadersCount()} and @link #getFootersCount()}.
*
* @param holder ViewHolder to bind item to;
* @param positionInAdapter Position of ViewHolder in adapter (NOT item!);
* @param item Item returned by position (WITH HEADER OFFSET!);
* @param payloads Payloads.
*/
protected void onBindItemToViewHolder(@NonNull final TItemViewHolder holder, final int positionInAdapter, @NonNull final TItem item,
@NonNull final List<Object> payloads) {
// do nothing by default
}
@Nullable
public TItem getItem(final int position) {
final int positionInList = position - getHeadersCount();
return positionInList < 0 || positionInList >= innerCollection.size() ? null : innerCollection.get(positionInList);
public TItem getItem(final int positionInAdapter) {
final int positionInCollection = positionInAdapter - getHeadersCount();
return positionInCollection < 0 || positionInCollection >= innerCollection.size() ? null : innerCollection.get(positionInCollection);
}
/**
@ -332,26 +493,51 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
* @param onItemClickListener Item click listener.
*/
public void setOnItemClickListener(@Nullable final OnItemClickListener<TItem> onItemClickListener) {
this.setOnItemClickListener(onItemClickListener, UiUtils.RIPPLE_EFFECT_DELAY);
}
/**
* Sets item click listener.
*
* @param onItemClickListener Item click listener;
* @param itemClickDelayMillis Delay of calling click listener.
*/
public void setOnItemClickListener(@Nullable final OnItemClickListener<TItem> onItemClickListener, final long itemClickDelayMillis) {
this.onItemClickListener = onItemClickListener;
this.itemClickDelayMillis = itemClickDelayMillis;
refreshUpdate();
}
/**
* Returns delay of item click. By default returns delay of ripple effect duration.
* Sets item click listener.
*
* @return Milliseconds delay of click.
* @param onItemClickListener Item click listener.
*/
protected long getItemClickDelay() {
return UiUtils.RIPPLE_EFFECT_DELAY;
public void setOnItemClickListener(@Nullable final OnItemWithPositionClickListener<TItem> onItemClickListener) {
this.setOnItemClickListener(onItemClickListener, UiUtils.RIPPLE_EFFECT_DELAY);
}
/**
* Sets item click listener.
*
* @param onItemClickListener Item click listener;
* @param itemClickDelayMillis Delay of calling click listener.
*/
public void setOnItemClickListener(@Nullable final OnItemWithPositionClickListener<TItem> onItemClickListener, final long itemClickDelayMillis) {
this.onItemClickListener = onItemClickListener;
this.itemClickDelayMillis = itemClickDelayMillis;
refreshUpdate();
}
/**
* Returns if click listening disabled or not for specific item.
*
* @param item Item to check click availability;
* @param item Item to check click availability;
* @param positionInAdapter Position of clicked item in adapter (with headers);
* @param positionInCollection Position of clicked item in inner collection;
* @return True if click listener enabled for such item.
*/
public boolean isOnClickListenerDisabled(@NonNull final TItem item) {
public boolean isOnClickListenerDisabled(@NonNull final TItem item, final int positionInAdapter, final int positionInCollection) {
return false;
}
@ -365,78 +551,27 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
/**
* Calls when item have clicked.
*
* @param item Clicked item;
* @param position Position of clicked item.
* @param item Clicked item.
*/
void onItemClicked(@NonNull TItem item, int position);
void onItemClicked(@NonNull TItem item);
}
/**
* Base item ViewHolder that have included pre-loading logic.
* Interface to simply add item click listener based on item position in adapter and collection.
*
* @param <TItem> Type of item
*/
public static class ViewHolder extends BindableViewHolder {
//it is needed to avoid massive requests on initial view holders attaching (like if we will add 10 items they all will try to load history)
private static final long DELAY_BEFORE_LOADING_HISTORY = TimeUnit.SECONDS.toMillis(1);
@Nullable
private Disposable historyPreLoadingSubscription;
@Nullable
private ObservableCollectionAdapter adapter;
public ViewHolder(@NonNull final LifecycleBindable baseBindable, @NonNull final View itemView) {
super(baseBindable, itemView);
}
public interface OnItemWithPositionClickListener<TItem> {
/**
* Bind position to enable pre-loading for connected {@link ObservableCollection}.
* Calls when item have clicked.
*
* @param position Position of ViewHolder.
* @param item Clicked item;
* @param positionInAdapter Position of clicked item in adapter (with headers);
* @param positionInCollection Position of clicked item in inner collection.
*/
@SuppressWarnings("unchecked")
//unchecked: it's ok, we just need to load something more
public void bindPosition(final int position) {
if (historyPreLoadingSubscription != null) {
historyPreLoadingSubscription.dispose();
historyPreLoadingSubscription = null;
}
if (adapter != null && position - adapter.getHeadersCount() > adapter.innerCollection.size() - PRE_LOADING_COUNT) {
historyPreLoadingSubscription = untilDestroy(adapter.historyPreLoadingObservable
.delaySubscription(DELAY_BEFORE_LOADING_HISTORY, TimeUnit.MILLISECONDS)
.onErrorResumeNext(Observable.empty()));
}
}
@SuppressWarnings("PMD.DefaultPackage")
//it is for internal use only
@Deprecated
void setAdapter(@Nullable final ObservableCollectionAdapter adapter) {
this.adapter = adapter;
}
/**
* Simply get String from resources.
*
* @param stringRes string resource id;
* @return Requested String that matches with provided string resource id.
*/
@NonNull
public String getString(@StringRes final int stringRes) {
return itemView.getContext().getString(stringRes);
}
/**
* Simply get String from resources with arguments.
*
* @param stringRes String resource id;
* @param formatArgs The format arguments that will be used for substitution;
* @return Requested String that matches with provided string resource id.
*/
@NonNull
public String getString(@StringRes final int stringRes, @Nullable final Object... formatArgs) {
return itemView.getContext().getString(stringRes, formatArgs);
}
void onItemClicked(@NonNull TItem item, final int positionInAdapter, final int positionInCollection);
}