applicationInjector() {
13 | return DaggerAppComponent.builder().create(this);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.widget.DefaultItemAnimator;
5 | import android.support.v7.widget.LinearLayoutManager;
6 | import android.support.v7.widget.RecyclerView;
7 | import com.tumblr.example.model.ColorNamePrimitive;
8 | import com.tumblr.example.model.Palette;
9 | import com.tumblr.example.model.Primitive;
10 | import dagger.android.support.DaggerAppCompatActivity;
11 |
12 | import javax.inject.Inject;
13 |
14 | public class MainActivity extends DaggerAppCompatActivity {
15 |
16 | @Inject
17 | PrimitiveAdapter mPrimitiveAdapter;
18 |
19 | @Override
20 | protected void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 | setContentView(R.layout.activity_main);
23 |
24 | final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
25 |
26 | if (recyclerView != null) {
27 | recyclerView.setLayoutManager(new LinearLayoutManager(this));
28 | recyclerView.setItemAnimator(new DefaultItemAnimator());
29 |
30 | // A header has nothing special
31 | mPrimitiveAdapter.add(new Primitive.Header());
32 |
33 | // Reds
34 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.red_base_variant_0, "dark red"));
35 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.red_base_variant_1, "red"));
36 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.red_base_variant_2, "bright red"));
37 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.red_base_variant_3, "shy red"));
38 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.red_base_variant_4, "embarrassed red"));
39 | mPrimitiveAdapter.add(new Palette("Red Palette", R.color.red_base_variant_0, R.color.red_base_variant_2, R.color.red_base_variant_4));
40 |
41 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.yellow_base_variant_0, "dark yellow"));
42 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.yellow_base_variant_1, "yellow"));
43 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.yellow_base_variant_2, "bright yellow"));
44 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.yellow_base_variant_3, "shy yellow"));
45 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.yellow_base_variant_4, "embarrassed yellow"));
46 | mPrimitiveAdapter.add(new Palette("Yellow Palette",
47 | R.color.yellow_base_variant_0, R.color.yellow_base_variant_2, R.color.yellow_base_variant_4));
48 |
49 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.green_base_variant_0, "dark green"));
50 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.green_base_variant_1, "green"));
51 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.green_base_variant_2, "bright green"));
52 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.green_base_variant_3, "shy green"));
53 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.green_base_variant_4, "embarrassed green"));
54 | mPrimitiveAdapter.add(new Palette("Green Palette",
55 | R.color.green_base_variant_0, R.color.green_base_variant_2, R.color.green_base_variant_4));
56 |
57 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.blue_base_variant_0, "dark blue"));
58 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.blue_base_variant_1, "blue"));
59 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.blue_base_variant_2, "bright blue"));
60 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.blue_base_variant_3, "shy blue"));
61 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.blue_base_variant_4, "embarrassed blue"));
62 | mPrimitiveAdapter.add(new Palette("Blue Palette",
63 | R.color.blue_base_variant_0, R.color.blue_base_variant_2, R.color.blue_base_variant_4));
64 |
65 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.purple_base_variant_0, "dark purple"));
66 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.purple_base_variant_1, "purple"));
67 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.purple_base_variant_2, "bright purple"));
68 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.purple_base_variant_3, "shy purple"));
69 | mPrimitiveAdapter.add(new ColorNamePrimitive(R.color.purple_base_variant_4, "embarrassed purple"));
70 | mPrimitiveAdapter.add(new Palette("Purple Palette",
71 | R.color.purple_base_variant_0, R.color.purple_base_variant_2, R.color.purple_base_variant_4));
72 |
73 | mPrimitiveAdapter.add(new Palette("Rainbow",
74 | R.color.red_base_variant_0, R.color.yellow_base_variant_0, R.color.green_base_variant_0,
75 | R.color.blue_base_variant_0, R.color.purple_base_variant_0));
76 |
77 | mPrimitiveAdapter.add(new Palette("Strange Rainbow",
78 | R.color.red_base_variant_0, R.color.yellow_base_variant_1, R.color.green_base_variant_2,
79 | R.color.blue_base_variant_3, R.color.purple_base_variant_4));
80 |
81 | recyclerView.setAdapter(mPrimitiveAdapter);
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/PrimitiveAdapter.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 | import com.tumblr.example.dagger.PerActivity;
6 | import com.tumblr.example.model.Primitive;
7 | import com.tumblr.example.viewholder.PrimitiveViewHolder;
8 | import com.tumblr.graywater.GraywaterAdapter;
9 |
10 | import javax.inject.Inject;
11 | import javax.inject.Provider;
12 | import java.util.Map;
13 |
14 | /**
15 | * Example adapter.
16 | *
17 | * Created by ericleong on 3/13/16.
18 | */
19 | @PerActivity
20 | public class PrimitiveAdapter extends GraywaterAdapter<
21 | Primitive,
22 | PrimitiveViewHolder,
23 | GraywaterAdapter.Binder extends Primitive, PrimitiveViewHolder, ? extends PrimitiveViewHolder>,
24 | Class extends Primitive>> {
25 |
26 | @NonNull
27 | private final Map,
28 | Provider>>> mItemBinderMap;
32 |
33 | @NonNull
34 | private final Map,
35 | Provider>> mActionListenerMap;
38 |
39 | @Inject
40 | public PrimitiveAdapter(final Map, ViewHolderCreator> viewHolderCreatorMapClass,
41 | @NonNull final Map,
42 | Provider>>>
46 | itemBinderMap,
47 | @NonNull final Map,
48 | Provider>> actionListenerMap) {
52 |
53 | for (Map.Entry, ViewHolderCreator> entry : viewHolderCreatorMapClass.entrySet()) {
54 | register(entry.getValue(), entry.getKey());
55 | }
56 |
57 | mItemBinderMap = itemBinderMap;
58 | mActionListenerMap = actionListenerMap;
59 | }
60 |
61 | @Nullable
62 | @Override
63 | protected ItemBinder extends Primitive,
64 | ? extends PrimitiveViewHolder,
65 | ? extends Binder extends Primitive, PrimitiveViewHolder, ? extends PrimitiveViewHolder>>
66 | getItemBinder(final Primitive model) {
67 | final Class extends Primitive> modelType = getModelType(model);
68 |
69 | return mItemBinderMap.get(modelType).get();
70 | }
71 |
72 | @Nullable
73 | @Override
74 | protected ActionListener extends Primitive, PrimitiveViewHolder, ? extends PrimitiveViewHolder>
75 | getActionListener(final Primitive model) {
76 | final Class extends Primitive> modelType = getModelType(model);
77 | final Provider>
78 | provider = mActionListenerMap.get(modelType);
79 |
80 | return provider != null ? provider.get() : null;
81 | }
82 |
83 | @NonNull
84 | @Override
85 | protected Class extends Primitive> getModelType(final Primitive model) {
86 | return model.getClass();
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/binder/ColorNameToastBinder.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.binder;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 | import com.tumblr.example.R;
6 | import com.tumblr.example.model.ColorNamePrimitive;
7 | import com.tumblr.example.viewholder.ColorPrimitiveViewHolder;
8 | import com.tumblr.example.viewholder.PrimitiveViewHolder;
9 | import com.tumblr.graywater.GraywaterAdapter;
10 |
11 | import javax.inject.Inject;
12 | import javax.inject.Provider;
13 | import java.util.List;
14 |
15 | /**
16 | * Created by ericleong on 3/24/16.
17 | */
18 | public class ColorNameToastBinder implements GraywaterAdapter.Binder {
19 |
20 | @Inject
21 | public ColorNameToastBinder() {
22 |
23 | }
24 |
25 | @Override
26 | public int getViewType(final ColorNamePrimitive model) {
27 | return R.layout.item_color;
28 | }
29 |
30 | @Override
31 | public void prepare(@NonNull final ColorNamePrimitive model,
32 | final List>> binderList,
34 | final int binderIndex) {
35 |
36 | }
37 |
38 | @Override
39 | public void bind(@NonNull final ColorNamePrimitive model,
40 | @NonNull final ColorPrimitiveViewHolder holder,
41 | @NonNull final List>> binderList,
43 | final int binderIndex,
44 | @Nullable final GraywaterAdapter.ActionListener<
45 | ColorNamePrimitive, PrimitiveViewHolder, ColorPrimitiveViewHolder> actionListener) {
46 | holder.getView().setBackgroundColor(holder.getView().getResources().getColor(model.getColor()));
47 | holder.getActionListenerDelegate().update(actionListener, model, holder, binderList, binderIndex, null);
48 | holder.getView().setOnClickListener(holder.getActionListenerDelegate());
49 | }
50 |
51 | @Override
52 | public void unbind(@NonNull final ColorPrimitiveViewHolder holder) {
53 | holder.getView().setOnClickListener(null);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/binder/HeaderBinder.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.binder;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 | import com.tumblr.example.R;
6 | import com.tumblr.example.model.Primitive;
7 | import com.tumblr.example.viewholder.HeaderViewHolder;
8 | import com.tumblr.example.viewholder.PrimitiveViewHolder;
9 | import com.tumblr.graywater.GraywaterAdapter;
10 |
11 | import javax.inject.Inject;
12 | import javax.inject.Provider;
13 | import java.util.List;
14 |
15 | /**
16 | * Created by ericleong on 3/13/16.
17 | */
18 | public class HeaderBinder implements GraywaterAdapter.Binder {
19 |
20 | @Inject
21 | public HeaderBinder() {
22 |
23 | }
24 |
25 | @Override
26 | public int getViewType(final Primitive.Header model) {
27 | return R.layout.item_header;
28 | }
29 |
30 | @Override
31 | public void prepare(@NonNull final Primitive.Header model,
32 | final List>> binderList,
34 | final int binderIndex) {
35 |
36 | }
37 |
38 | @Override
39 | public void bind(@NonNull final Primitive.Header model,
40 | @NonNull final HeaderViewHolder holder,
41 | @NonNull final List>> binderList,
43 | final int binderIndex,
44 | @Nullable final GraywaterAdapter.ActionListener<
45 | Primitive.Header, PrimitiveViewHolder, HeaderViewHolder> actionListener) {
46 |
47 | }
48 |
49 | @Override
50 | public void unbind(@NonNull final HeaderViewHolder holder) {
51 |
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/binder/PaletteColorBinder.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.binder;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 | import android.view.View;
6 | import android.widget.Toast;
7 | import com.tumblr.example.R;
8 | import com.tumblr.example.model.Palette;
9 | import com.tumblr.example.viewholder.ColorPrimitiveViewHolder;
10 | import com.tumblr.example.viewholder.PrimitiveViewHolder;
11 | import com.tumblr.graywater.GraywaterAdapter;
12 |
13 | import javax.inject.Inject;
14 | import javax.inject.Provider;
15 | import java.util.List;
16 |
17 | /**
18 | * Created by ericleong on 3/13/16.
19 | */
20 | public class PaletteColorBinder implements GraywaterAdapter.Binder {
21 |
22 | @Inject
23 | public PaletteColorBinder() {
24 |
25 | }
26 |
27 | @Override
28 | public int getViewType(final Palette model) {
29 | return R.layout.item_color;
30 | }
31 |
32 | @Override
33 | public void prepare(@NonNull final Palette model,
34 | final List>> binderList,
36 | final int binderIndex) {
37 |
38 | }
39 |
40 | @Override
41 | public void bind(@NonNull final Palette model,
42 | @NonNull final ColorPrimitiveViewHolder holder,
43 | @NonNull final List>> binderList,
45 | final int binderIndex,
46 | @Nullable final GraywaterAdapter.ActionListener<
47 | Palette, PrimitiveViewHolder, ColorPrimitiveViewHolder> actionListener) {
48 | holder.getView().setBackgroundColor(holder.getView().getResources().getColor(model.getColors().get
49 | (binderIndex - 1)));
50 |
51 | holder.getView().setOnClickListener(new View.OnClickListener() {
52 | @Override
53 | public void onClick(final View v) {
54 | PaletteColorBinder.this.onClick(v, model, holder);
55 | }
56 | });
57 | }
58 |
59 | @Override
60 | public void unbind(@NonNull final ColorPrimitiveViewHolder holder) {
61 | holder.getView().setOnClickListener(null);
62 | }
63 |
64 | public void onClick(final View v, final Palette model, final ColorPrimitiveViewHolder holder) {
65 | Toast.makeText(v.getContext(), model.getString(), Toast.LENGTH_SHORT).show();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/binder/TextPrimitiveBinder.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.binder;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 | import com.tumblr.example.R;
6 | import com.tumblr.example.model.Primitive;
7 | import com.tumblr.example.viewholder.PrimitiveViewHolder;
8 | import com.tumblr.example.viewholder.TextPrimitiveViewHolder;
9 | import com.tumblr.graywater.GraywaterAdapter;
10 |
11 | import javax.inject.Inject;
12 | import javax.inject.Provider;
13 | import java.util.List;
14 |
15 | /**
16 | * Created by ericleong on 3/13/16.
17 | */
18 | public class TextPrimitiveBinder
19 | implements GraywaterAdapter.Binder {
20 |
21 | @Inject
22 | public TextPrimitiveBinder() {
23 |
24 | }
25 |
26 | @Override
27 | public int getViewType(final T model) {
28 | return R.layout.item_text;
29 | }
30 |
31 | @Override
32 | public void prepare(@NonNull final T model,
33 | final List>> binderList,
35 | final int binderIndex) {
36 |
37 | }
38 |
39 | @Override
40 | public void bind(@NonNull final T model,
41 | @NonNull final TextPrimitiveViewHolder holder,
42 | @NonNull final List>> binderList,
44 | final int binderIndex,
45 | @Nullable final GraywaterAdapter.ActionListener actionListener) {
46 | holder.getTextView().setText(model.getString());
47 | }
48 |
49 | @Override
50 | public void unbind(@NonNull final TextPrimitiveViewHolder holder) {
51 |
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/binderlist/ColorNamePrimitiveItemBinder.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.binderlist;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 | import android.view.View;
6 | import android.widget.Toast;
7 | import com.tumblr.example.PrimitiveAdapter;
8 | import com.tumblr.example.binder.ColorNameToastBinder;
9 | import com.tumblr.example.binder.TextPrimitiveBinder;
10 | import com.tumblr.example.dagger.PerActivity;
11 | import com.tumblr.example.model.ColorNamePrimitive;
12 | import com.tumblr.example.viewholder.PrimitiveViewHolder;
13 | import com.tumblr.graywater.GraywaterAdapter;
14 |
15 | import javax.inject.Inject;
16 | import javax.inject.Provider;
17 | import java.util.ArrayList;
18 | import java.util.List;
19 |
20 | /**
21 | * Created by ericleong on 3/28/16.
22 | */
23 | @PerActivity
24 | public class ColorNamePrimitiveItemBinder
25 | implements GraywaterAdapter.ItemBinder>,
27 | GraywaterAdapter.ActionListener {
28 |
29 | private final Provider> mColorNameTextBinder;
30 | private final Provider mColorNameToastBinder;
31 |
32 | private final Provider mAdapter;
33 |
34 | @Inject
35 | public ColorNamePrimitiveItemBinder(final Provider adapter,
36 | final Provider> colorNameTextBinder,
37 | final Provider colorNameToastBinder) {
38 | mColorNameTextBinder = colorNameTextBinder;
39 | mColorNameToastBinder = colorNameToastBinder;
40 | mAdapter = adapter;
41 | }
42 |
43 | @NonNull
44 | @Override
45 | public List>>
46 | getBinderList(@NonNull final ColorNamePrimitive model, final int position) {
47 | return new ArrayList>>() {{
49 | add(mColorNameTextBinder);
50 | add(mColorNameToastBinder);
51 | }};
52 | }
53 |
54 | @Override
55 | public void act(@NonNull final ColorNamePrimitive model,
56 | @NonNull final PrimitiveViewHolder holder,
57 | @NonNull final View v,
58 | @NonNull final List>> binderList,
60 | final int binderIndex,
61 | @Nullable final Object obj) {
62 | Toast.makeText(v.getContext(), model.getString(), Toast.LENGTH_SHORT).show();
63 |
64 | final PrimitiveAdapter adapter = mAdapter.get();
65 |
66 | adapter.add(adapter.getItemPosition(holder.getAdapterPosition()) + 1,
67 | new ColorNamePrimitive(model.getColor(), model.getString() + "+"), true);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/binderlist/HeaderPrimitiveItemBinder.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.binderlist;
2 |
3 | import android.support.annotation.NonNull;
4 | import com.tumblr.example.binder.HeaderBinder;
5 | import com.tumblr.example.dagger.PerActivity;
6 | import com.tumblr.example.model.Primitive;
7 | import com.tumblr.example.viewholder.PrimitiveViewHolder;
8 | import com.tumblr.graywater.GraywaterAdapter;
9 |
10 | import javax.inject.Inject;
11 | import javax.inject.Provider;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | /**
16 | * Created by ericleong on 3/28/16.
17 | */
18 | @PerActivity
19 | public class HeaderPrimitiveItemBinder implements
20 | GraywaterAdapter.ItemBinder> {
22 | private final Provider mHeaderBinder;
23 |
24 | @Inject
25 | public HeaderPrimitiveItemBinder(final Provider headerBinder) {
26 | mHeaderBinder = headerBinder;
27 | }
28 |
29 | @NonNull
30 | @Override
31 | public List>>
33 | getBinderList(@NonNull final Primitive.Header model, final int position) {
34 | return new ArrayList>>() {{
36 | add(mHeaderBinder);
37 | }};
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/binderlist/PaletteItemBinder.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.binderlist;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 | import android.view.View;
6 | import com.tumblr.example.binder.PaletteColorBinder;
7 | import com.tumblr.example.binder.TextPrimitiveBinder;
8 | import com.tumblr.example.dagger.PerActivity;
9 | import com.tumblr.example.model.Palette;
10 | import com.tumblr.example.viewholder.PrimitiveViewHolder;
11 | import com.tumblr.graywater.GraywaterAdapter;
12 |
13 | import javax.inject.Inject;
14 | import javax.inject.Provider;
15 | import java.util.ArrayList;
16 | import java.util.List;
17 |
18 | /**
19 | * Created by ericleong on 3/28/16.
20 | */
21 | @PerActivity
22 | public class PaletteItemBinder implements GraywaterAdapter.ItemBinder>,
24 | GraywaterAdapter.ActionListener {
25 | private final Provider> mPaletteTextPrimitiveBinder;
26 | private final Provider mPaletteColorBinder;
27 |
28 | @Inject
29 | public PaletteItemBinder(final Provider> paletteTextPrimitiveBinder,
30 | final Provider paletteColorBinder) {
31 | mPaletteTextPrimitiveBinder = paletteTextPrimitiveBinder;
32 | mPaletteColorBinder = paletteColorBinder;
33 | }
34 |
35 | @NonNull
36 | @Override
37 | public List>>
38 | getBinderList(@NonNull final Palette model, final int position) {
39 | return new ArrayList>>() {{
40 | add(mPaletteTextPrimitiveBinder);
41 |
42 | for (int color : model.getColors()) {
43 | add(mPaletteColorBinder);
44 | }
45 | }};
46 | }
47 |
48 | @Override
49 | public void act(@NonNull final Palette model,
50 | @NonNull final PrimitiveViewHolder holder,
51 | @NonNull final View v,
52 | @NonNull final List>> binderList,
54 | final int binderIndex,
55 | @Nullable final Object obj) {
56 |
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/dagger/AppComponent.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.dagger;
2 |
3 | import com.tumblr.example.App;
4 | import com.tumblr.example.dagger.module.ActivityBindingModule;
5 | import dagger.Component;
6 | import dagger.android.AndroidInjector;
7 | import dagger.android.support.AndroidSupportInjectionModule;
8 |
9 | import javax.inject.Singleton;
10 |
11 | /**
12 | * Created by ericleong on 12/6/17.
13 | */
14 | @Singleton
15 | @Component(modules = {
16 | AndroidSupportInjectionModule.class,
17 | ActivityBindingModule.class
18 | })
19 | public interface AppComponent extends AndroidInjector {
20 | @Component.Builder
21 | abstract class Builder extends AndroidInjector.Builder {}
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/dagger/PerActivity.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.dagger;
2 |
3 | import javax.inject.Scope;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 |
7 | /**
8 | * Activity scope.
9 | *
10 | * Created by ericleong on 12/6/17.
11 | */
12 | @Scope
13 | @Retention(RetentionPolicy.RUNTIME)
14 | public @interface PerActivity {
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/dagger/key/PrimitiveCreatorKey.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.dagger.key;
2 |
3 | import com.tumblr.example.viewholder.PrimitiveViewHolder;
4 | import dagger.MapKey;
5 |
6 | import java.lang.annotation.Documented;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.Target;
9 |
10 | import static java.lang.annotation.ElementType.METHOD;
11 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
12 |
13 | /**
14 | * A {@link MapKey} annotation for maps with {@code Class extends PrimitiveViewHolder>} keys.
15 | *
16 | * Created by ericleong on 12/6/17.
17 | */
18 | @Documented
19 | @Target(METHOD)
20 | @Retention(RUNTIME)
21 | @MapKey
22 | public @interface PrimitiveCreatorKey {
23 | Class extends PrimitiveViewHolder> value();
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/dagger/key/PrimitiveItemBinderKey.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.dagger.key;
2 |
3 | import com.tumblr.example.model.Primitive;
4 | import dagger.MapKey;
5 |
6 | import java.lang.annotation.Documented;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.Target;
9 |
10 | import static java.lang.annotation.ElementType.METHOD;
11 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
12 |
13 | /**
14 | * A {@link MapKey} annotation for maps with {@code Class extends Primitive>} keys.
15 | *
16 | * Created by ericleong on 12/6/17.
17 | */
18 | @Documented
19 | @Target(METHOD)
20 | @Retention(RUNTIME)
21 | @MapKey
22 | public @interface PrimitiveItemBinderKey {
23 | Class extends Primitive> value();
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/dagger/module/ActionListenerModule.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.dagger.module;
2 |
3 | import com.tumblr.example.binderlist.ColorNamePrimitiveItemBinder;
4 | import com.tumblr.example.dagger.PerActivity;
5 | import com.tumblr.example.dagger.key.PrimitiveItemBinderKey;
6 | import com.tumblr.example.model.ColorNamePrimitive;
7 | import com.tumblr.example.model.Primitive;
8 | import com.tumblr.example.viewholder.PrimitiveViewHolder;
9 | import com.tumblr.graywater.GraywaterAdapter;
10 | import dagger.Binds;
11 | import dagger.Module;
12 | import dagger.multibindings.IntoMap;
13 |
14 | /**
15 | * Created by ericleong on 12/7/17.
16 | */
17 | @Module
18 | public abstract class ActionListenerModule {
19 | @PerActivity
20 | @Binds
21 | @IntoMap
22 | @PrimitiveItemBinderKey(ColorNamePrimitive.class)
23 | abstract GraywaterAdapter.ActionListener extends Primitive, PrimitiveViewHolder, ? extends PrimitiveViewHolder>
24 | bindsColorNamePrimitiveActionListener(ColorNamePrimitiveItemBinder colorNamePrimitiveItemBinder);
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/dagger/module/ActivityBindingModule.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.dagger.module;
2 |
3 | import com.tumblr.example.MainActivity;
4 | import com.tumblr.example.dagger.PerActivity;
5 | import dagger.Module;
6 | import dagger.android.ContributesAndroidInjector;
7 |
8 | /**
9 | * Created by ericleong on 12/6/17.
10 | */
11 | @Module
12 | public abstract class ActivityBindingModule {
13 | @PerActivity
14 | @ContributesAndroidInjector(
15 | modules = {
16 | ItemBinderModule.class,
17 | ViewHolderCreatorModule.class,
18 | ActionListenerModule.class
19 | }
20 | )
21 | abstract MainActivity contributeMainActivityInjector();
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/dagger/module/ItemBinderModule.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.dagger.module;
2 |
3 | import com.tumblr.example.binderlist.ColorNamePrimitiveItemBinder;
4 | import com.tumblr.example.binderlist.HeaderPrimitiveItemBinder;
5 | import com.tumblr.example.binderlist.PaletteItemBinder;
6 | import com.tumblr.example.dagger.PerActivity;
7 | import com.tumblr.example.dagger.key.PrimitiveItemBinderKey;
8 | import com.tumblr.example.model.ColorNamePrimitive;
9 | import com.tumblr.example.model.Palette;
10 | import com.tumblr.example.model.Primitive;
11 | import com.tumblr.example.viewholder.PrimitiveViewHolder;
12 | import com.tumblr.graywater.GraywaterAdapter;
13 | import dagger.Binds;
14 | import dagger.Module;
15 | import dagger.multibindings.IntoMap;
16 |
17 | /**
18 | * Created by ericleong on 12/6/17.
19 | */
20 | @Module
21 | public abstract class ItemBinderModule {
22 | @PerActivity
23 | @Binds
24 | @IntoMap
25 | @PrimitiveItemBinderKey(ColorNamePrimitive.class)
26 | abstract GraywaterAdapter.ItemBinder<
27 | ? extends Primitive,
28 | ? extends PrimitiveViewHolder,
29 | ? extends GraywaterAdapter.Binder extends Primitive, PrimitiveViewHolder, ? extends PrimitiveViewHolder>>
30 | bindsColorNamePrimitiveItemBinder(ColorNamePrimitiveItemBinder colorNamePrimitiveItemBinder);
31 |
32 | @PerActivity
33 | @Binds
34 | @IntoMap
35 | @PrimitiveItemBinderKey(Primitive.Header.class)
36 | abstract GraywaterAdapter.ItemBinder<
37 | ? extends Primitive,
38 | ? extends PrimitiveViewHolder,
39 | ? extends GraywaterAdapter.Binder extends Primitive, PrimitiveViewHolder, ? extends PrimitiveViewHolder>>
40 | bindsHeaderPrimitiveItemBinder(HeaderPrimitiveItemBinder headerPrimitiveItemBinder);
41 |
42 | @PerActivity
43 | @Binds
44 | @IntoMap
45 | @PrimitiveItemBinderKey(Palette.class)
46 | abstract GraywaterAdapter.ItemBinder<
47 | ? extends Primitive,
48 | ? extends PrimitiveViewHolder,
49 | ? extends GraywaterAdapter.Binder extends Primitive, PrimitiveViewHolder, ? extends PrimitiveViewHolder>>
50 | bindsPaletteItemBinder(PaletteItemBinder paletteItemBinder);
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/dagger/module/ViewHolderCreatorModule.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.dagger.module;
2 |
3 | import com.tumblr.example.dagger.key.PrimitiveCreatorKey;
4 | import com.tumblr.example.viewholder.ColorPrimitiveViewHolder;
5 | import com.tumblr.example.viewholder.HeaderViewHolder;
6 | import com.tumblr.example.viewholder.TextPrimitiveViewHolder;
7 | import com.tumblr.example.viewholdercreator.ColorPrimitiveViewHolderCreator;
8 | import com.tumblr.example.viewholdercreator.HeaderViewHolderCreator;
9 | import com.tumblr.example.viewholdercreator.TextPrimitiveViewHolderCreator;
10 | import com.tumblr.graywater.GraywaterAdapter;
11 | import dagger.Binds;
12 | import dagger.Module;
13 | import dagger.multibindings.IntoMap;
14 |
15 | /**
16 | * Created by ericleong on 12/6/17.
17 | */
18 | @Module
19 | public abstract class ViewHolderCreatorModule {
20 | @Binds
21 | @IntoMap
22 | @PrimitiveCreatorKey(TextPrimitiveViewHolder.class)
23 | abstract GraywaterAdapter.ViewHolderCreator bindsTextPrimitiveViewHolderCreator(
24 | TextPrimitiveViewHolderCreator textPrimitiveViewHolderCreator);
25 |
26 | @Binds
27 | @IntoMap
28 | @PrimitiveCreatorKey(HeaderViewHolder.class)
29 | abstract GraywaterAdapter.ViewHolderCreator bindsHeaderViewHolderCreator(HeaderViewHolderCreator headerViewHolderCreator);
30 |
31 | @Binds
32 | @IntoMap
33 | @PrimitiveCreatorKey(ColorPrimitiveViewHolder.class)
34 | abstract GraywaterAdapter.ViewHolderCreator bindsColorPrimitiveViewHolderCreator(
35 | ColorPrimitiveViewHolderCreator colorPrimitiveViewHolderCreator);
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/model/ColorNamePrimitive.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.model;
2 |
3 | import android.support.annotation.ColorRes;
4 |
5 | /**
6 | * Created by ericleong on 3/13/16.
7 | */
8 | public class ColorNamePrimitive implements Primitive.Color, Primitive.Text {
9 | @ColorRes
10 | private int color;
11 |
12 | private final String string;
13 |
14 | public ColorNamePrimitive(final int color, final String string) {
15 | this.color = color;
16 | this.string = string;
17 | }
18 |
19 | public void setColor(@ColorRes final int color) {
20 | this.color = color;
21 | }
22 |
23 | @ColorRes
24 | public int getColor() {
25 | return color;
26 | }
27 |
28 | public String getString() {
29 | return string;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/model/Palette.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.model;
2 |
3 | import android.support.annotation.ColorRes;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | /**
9 | * A palette has 3 colors.
10 | *
11 | * Created by ericleong on 3/13/16.
12 | */
13 | public class Palette implements Primitive, Primitive.Text {
14 |
15 | private String name;
16 |
17 | private List colors = new ArrayList<>();
18 |
19 | public Palette(String name, @ColorRes int... colors) {
20 | this.name = name;
21 |
22 | for (int color : colors) {
23 | this.colors.add(color);
24 | }
25 | }
26 |
27 | @Override
28 | public String getString() {
29 | return name;
30 | }
31 |
32 | public List getColors() {
33 | return colors;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/model/Primitive.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.model;
2 |
3 | import android.support.annotation.ColorRes;
4 |
5 | /**
6 | * Created by ericleong on 3/13/16.
7 | */
8 | public interface Primitive {
9 |
10 | interface Text extends Primitive {
11 | String getString();
12 | }
13 |
14 | interface Color extends Primitive {
15 | @ColorRes
16 | int getColor();
17 | }
18 |
19 | class Header implements Primitive {
20 | // Dummy marker class
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/model/TextPrimitive.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.model;
2 |
3 | /**
4 | * Created by ericleong on 3/13/16.
5 | */
6 | public class TextPrimitive implements Primitive.Text {
7 | private final String string;
8 |
9 | public TextPrimitive(final String string) {
10 | this.string = string;
11 | }
12 |
13 | @Override
14 | public String getString() {
15 | return string;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/viewholder/ColorPrimitiveViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.viewholder;
2 |
3 | import android.view.View;
4 | import com.tumblr.example.R;
5 | import com.tumblr.example.model.ColorNamePrimitive;
6 | import com.tumblr.graywater.GraywaterAdapter;
7 |
8 | /**
9 | * Created by ericleong on 3/13/16.
10 | */
11 | public class ColorPrimitiveViewHolder extends PrimitiveViewHolder {
12 |
13 | private GraywaterAdapter.ActionListenerDelegate
14 | mActionListenerDelegate = new GraywaterAdapter.ActionListenerDelegate<>();
15 |
16 | private final View view;
17 |
18 | public ColorPrimitiveViewHolder(final View itemView) {
19 | super(itemView);
20 | view = itemView.findViewById(R.id.color);
21 | }
22 |
23 | public View getView() {
24 | return view;
25 | }
26 |
27 | public GraywaterAdapter.ActionListenerDelegate
28 | getActionListenerDelegate() {
29 | return mActionListenerDelegate;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/viewholder/HeaderViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.viewholder;
2 |
3 | import android.view.View;
4 |
5 | /**
6 | * Seems a bit ridiculous but helps us with type safety.
7 | */
8 | public class HeaderViewHolder extends PrimitiveViewHolder {
9 | public HeaderViewHolder(final View itemView) {
10 | super(itemView);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/viewholder/PrimitiveViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.viewholder;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 | import android.view.View;
5 |
6 | /**
7 | * Created by ericleong on 3/13/16.
8 | */
9 | public abstract class PrimitiveViewHolder extends RecyclerView.ViewHolder {
10 | public PrimitiveViewHolder(final View itemView) {
11 | super(itemView);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/viewholder/TextPrimitiveViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.viewholder;
2 |
3 | import android.view.View;
4 | import android.widget.TextView;
5 | import com.tumblr.example.R;
6 |
7 | /**
8 | * Created by ericleong on 3/13/16.
9 | */
10 | public class TextPrimitiveViewHolder extends PrimitiveViewHolder {
11 | private final TextView textView;
12 |
13 | public TextPrimitiveViewHolder(final View itemView) {
14 | super(itemView);
15 | textView = (TextView) itemView.findViewById(R.id.text);
16 | }
17 |
18 | public TextView getTextView() {
19 | return textView;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/viewholdercreator/ColorPrimitiveViewHolderCreator.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.viewholdercreator;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.view.ViewGroup;
5 | import com.tumblr.example.R;
6 | import com.tumblr.example.viewholder.ColorPrimitiveViewHolder;
7 | import com.tumblr.graywater.GraywaterAdapter;
8 |
9 | import javax.inject.Inject;
10 |
11 | /**
12 | * Created by ericleong on 3/15/16.
13 | */
14 | public class ColorPrimitiveViewHolderCreator implements GraywaterAdapter.ViewHolderCreator {
15 |
16 | @Inject
17 | public ColorPrimitiveViewHolderCreator() {
18 |
19 | }
20 |
21 | @Override
22 | public ColorPrimitiveViewHolder create(final ViewGroup parent) {
23 | return new ColorPrimitiveViewHolder(GraywaterAdapter.inflate(parent, R.layout.item_color));
24 | }
25 |
26 | @Override
27 | public int getViewType() {
28 | return R.layout.item_color;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/viewholdercreator/HeaderViewHolderCreator.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.viewholdercreator;
2 |
3 | import android.view.ViewGroup;
4 | import com.tumblr.example.R;
5 | import com.tumblr.example.viewholder.HeaderViewHolder;
6 | import com.tumblr.graywater.GraywaterAdapter;
7 |
8 | import javax.inject.Inject;
9 |
10 | /**
11 | * Created by ericleong on 3/15/16.
12 | */
13 | public class HeaderViewHolderCreator implements GraywaterAdapter.ViewHolderCreator {
14 |
15 | @Inject
16 | public HeaderViewHolderCreator() {
17 |
18 | }
19 |
20 | @Override
21 | public HeaderViewHolder create(final ViewGroup parent) {
22 | return new HeaderViewHolder(GraywaterAdapter.inflate(parent, R.layout.item_header));
23 | }
24 |
25 | @Override
26 | public int getViewType() {
27 | return R.layout.item_header;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tumblr/example/viewholdercreator/TextPrimitiveViewHolderCreator.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.example.viewholdercreator;
2 |
3 | import android.view.ViewGroup;
4 | import com.tumblr.example.R;
5 | import com.tumblr.example.viewholder.TextPrimitiveViewHolder;
6 | import com.tumblr.graywater.GraywaterAdapter;
7 |
8 | import javax.inject.Inject;
9 |
10 | /**
11 | * Created by ericleong on 3/15/16.
12 | */
13 | public class TextPrimitiveViewHolderCreator implements GraywaterAdapter.ViewHolderCreator {
14 |
15 | @Inject
16 | public TextPrimitiveViewHolderCreator() {
17 |
18 | }
19 |
20 | @Override
21 | public TextPrimitiveViewHolder create(final ViewGroup parent) {
22 | return new TextPrimitiveViewHolder(GraywaterAdapter.inflate(parent, R.layout.item_text));
23 | }
24 |
25 | @Override
26 | public int getViewType() {
27 | return R.layout.item_text;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumblr/Graywater/662ba58886640c4af899e6a312d7723bed71369d/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumblr/Graywater/662ba58886640c4af899e6a312d7723bed71369d/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumblr/Graywater/662ba58886640c4af899e6a312d7723bed71369d/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumblr/Graywater/662ba58886640c4af899e6a312d7723bed71369d/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumblr/Graywater/662ba58886640c4af899e6a312d7723bed71369d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 | #d85f46
8 | #f94e27
9 | #fd4347
10 | #efc0b8
11 | #fe7576
12 |
13 | #f1993c
14 | #ffd731
15 | #fff148
16 | #decba6
17 | #e8ec75
18 |
19 | #5bbc8c
20 | #5bbe52
21 | #60cdb4
22 | #9dcaca
23 | #c1e4ab
24 |
25 | #56a0cb
26 | #8056a0cb
27 | #60cae0
28 | #558bd9
29 | #3b4fc8
30 | #6158c2
31 |
32 | #a780c1
33 | #b09dd4
34 | #d86dc3
35 | #e1b8d2
36 | #fc4081
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 0dp
4 | 0dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | graywater
3 | This is a Header
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | google()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.0.1'
10 |
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | google()
20 | }
21 | }
22 |
23 | task clean(type: Delete) {
24 | delete rootProject.buildDir
25 | }
26 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumblr/Graywater/662ba58886640c4af899e6a312d7723bed71369d/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Dec 05 14:40:30 EST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/graywater/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/graywater/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'checkstyle'
3 |
4 | android {
5 | compileSdkVersion 26
6 | buildToolsVersion '26.0.2'
7 |
8 | defaultConfig {
9 | minSdkVersion 16
10 | targetSdkVersion 26
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | testImplementation'junit:junit:4.12'
25 | testImplementation "org.robolectric:robolectric:3.5"
26 | api 'com.android.support:recyclerview-v7:26.1.0'
27 | api 'javax.inject:javax.inject:1'
28 | }
29 |
30 | checkstyle {
31 | toolVersion = "6.7"
32 | }
33 |
34 | task checkstyle(type: Checkstyle) {
35 | configFile = rootProject.file('graywater/checkstyle.xml')
36 | configProperties = ['proj.module.dir': projectDir.absolutePath]
37 | classpath = files()
38 |
39 | source 'src/main/java'
40 | include '**/*.java'
41 | exclude '**/gen/**'
42 | }
43 |
--------------------------------------------------------------------------------
/graywater/checkstyle-suppressions.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/graywater/checkstyle.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
22 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
32 |
33 |
34 |
36 |
38 |
39 |
40 |
42 |
43 |
44 |
46 |
47 |
48 |
49 |
50 |
52 |
54 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
139 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
--------------------------------------------------------------------------------
/graywater/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/ericleong/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/graywater/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/graywater/src/main/java/com/tumblr/graywater/GraywaterAdapter.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.graywater;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.support.annotation.AnyRes;
5 | import android.support.annotation.LayoutRes;
6 | import android.support.annotation.NonNull;
7 | import android.support.annotation.Nullable;
8 | import android.support.annotation.VisibleForTesting;
9 | import android.support.v4.util.ArrayMap;
10 | import android.support.v4.util.Pair;
11 | import android.support.v7.widget.RecyclerView;
12 | import android.view.LayoutInflater;
13 | import android.view.View;
14 | import android.view.ViewGroup;
15 |
16 | import javax.inject.Provider;
17 | import java.util.ArrayList;
18 | import java.util.HashSet;
19 | import java.util.List;
20 | import java.util.ListIterator;
21 | import java.util.Map;
22 | import java.util.Set;
23 |
24 | /**
25 | * Maps models to multiple view holders.
26 | *
27 | * Created by ericleong on 3/11/16.
28 | *
29 | * @param
30 | * the model type.
31 | * @param
32 | * the viewholder type.
33 | * @param
34 | * the binder type.
35 | * @param
36 | * the type of the model type ({@code Class} for example)
37 | */
38 | public abstract class GraywaterAdapter<
39 | T,
40 | VH extends RecyclerView.ViewHolder,
41 | B extends GraywaterAdapter.Binder extends T, VH, ? extends VH>,
42 | MT>
43 | extends RecyclerView.Adapter {
44 |
45 | private static final int NO_PREVIOUS_BOUND_VIEWHOLDER = -1;
46 |
47 | /**
48 | * The T list for adapter items.
49 | */
50 | @NonNull
51 | protected final List mItems = new ArrayList<>();
52 |
53 | /**
54 | * Map of viewtypes to ViewHolderCreators.
55 | */
56 | @NonNull
57 | private final Map mViewHolderCreatorMap = new ArrayMap<>();
58 |
59 | /**
60 | * Map of viewtypes to the class of the viewholders they correspond to. This is just informational.
61 | */
62 | @NonNull
63 | private final Map> mViewTypeToViewHolderClassMap = new ArrayMap<>();
64 |
65 | /**
66 | * Map of viewtypes to the class of the viewholders they correspond to. This is just informational.
67 | */
68 | @NonNull
69 | private final Map, Integer> mViewHolderClassToViewTypeMap = new ArrayMap<>();
70 |
71 | /**
72 | * Map from model to a list of binders. A model may have multiple binders.
73 | */
74 | @NonNull
75 | protected final Map> mItemBinderMap = new ArrayMap<>();
76 |
77 | /**
78 | * Map from model to an action listener. A model may have multiple action listeners if there are multiple events.
79 | */
80 | @NonNull
81 | private final Map> mActionListenerMap = new ArrayMap<>();
82 |
83 | /**
84 | * Index of the last model that was bound.
85 | */
86 | private int mPreviousBoundViewHolderPosition = NO_PREVIOUS_BOUND_VIEWHOLDER;
87 |
88 | private final List>>> mBinderListCache = new ArrayList<>();
89 | private final List mViewHolderToItemPositionCache = new ArrayList<>();
90 | private final List mItemPositionToFirstViewHolderPositionCache = new ArrayList<>();
91 | /**
92 | * The viewholders that have had {@link #prepare(int, Binder, Object, List, int)} called on them.
93 | *
94 | * {@link #add(Object)}, {@link #remove(int)}, {@link #onViewRecycled(RecyclerView.ViewHolder)}
95 | * will all cause this to be cleared.
96 | */
97 | private final Set mViewHolderPreparedCache = new HashSet<>();
98 |
99 | /**
100 | * @param viewHolderCreator
101 | * the view holder creator to register.
102 | * @param viewHolderClass
103 | * the class of the viewholder (useful when debugging).
104 | */
105 | protected void register(final ViewHolderCreator viewHolderCreator, final Class extends VH> viewHolderClass) {
106 | final int viewType = viewHolderCreator.getViewType();
107 | mViewHolderCreatorMap.put(viewType, viewHolderCreator);
108 | mViewTypeToViewHolderClassMap.put(viewType, viewHolderClass);
109 | mViewHolderClassToViewTypeMap.put(viewHolderClass, viewType);
110 | }
111 |
112 | /**
113 | * @param modelType
114 | * the model type.
115 | * @param parts
116 | * the binders to use to display this model.
117 | * @param listener
118 | * the listener to associate with the model.
119 | */
120 | protected void register(@NonNull final MT modelType,
121 | @NonNull final ItemBinder extends T, ? extends VH, ? extends B> parts,
122 | @Nullable final ActionListener extends T, VH, ? extends VH> listener) {
123 | mItemBinderMap.put(modelType, parts);
124 | mActionListenerMap.put(modelType, listener);
125 | }
126 |
127 | /**
128 | * @param model
129 | * the model to get the type of.
130 | * @return the appropriate type (for example, {@link Class}).
131 | */
132 | @NonNull
133 | protected abstract MT getModelType(T model);
134 |
135 | /**
136 | * @param model
137 | * the model to get the {@link ItemBinder} for.
138 | * @return the {@link ItemBinder} for the given model.
139 | */
140 | @Nullable
141 | protected ItemBinder extends T, ? extends VH, ? extends B> getItemBinder(final T model) {
142 | return mItemBinderMap.get(getModelType(model));
143 | }
144 |
145 | /**
146 | * @param model
147 | * the model to get the {@link ItemBinder} for.
148 | * @return the {@link ActionListener} for the given model.
149 | */
150 | @Nullable
151 | protected ActionListener extends T, VH, ? extends VH> getActionListener(final T model) {
152 | return mActionListenerMap.get(getModelType(model));
153 | }
154 |
155 | /**
156 | * @param model
157 | * the model to get parts for.
158 | * @param position
159 | * the position of the model.
160 | * @return the list of binders to use.
161 | */
162 | @Nullable
163 | protected List>> getParts(final T model, final int position) {
164 | final List>> list;
165 |
166 | final ItemBinder itemBinder = getItemBinder(model);
167 |
168 | if (itemBinder != null) {
169 | list = itemBinder.getBinderList(model, position);
170 | } else {
171 | list = null;
172 | }
173 |
174 | return list;
175 | }
176 |
177 | /**
178 | * Computes the position of the item and the position of the binder within the item's binder list.
179 | * Note that this is an O(n) operation.
180 | *
181 | * @param viewHolderPosition
182 | * the position of the view holder in the adapter.
183 | * @return the item position and the position of the binder in the item's binder list.
184 | */
185 | @VisibleForTesting
186 | BinderResult computeItemAndBinderIndex(final int viewHolderPosition) {
187 | // subtract off the length of each list until we get to the desired item
188 |
189 | final int itemIndex = mViewHolderToItemPositionCache.get(viewHolderPosition);
190 | final T item = mItems.get(itemIndex);
191 | final List>> binders = mBinderListCache.get(itemIndex);
192 | // index of the first item in the set of viewholders for the current item.
193 | final int firstVHPosForItem = mItemPositionToFirstViewHolderPositionCache.get(itemIndex);
194 |
195 | return new BinderResult(item, itemIndex, binders, viewHolderPosition - firstVHPosForItem);
196 | }
197 |
198 | @Override
199 | public int getItemViewType(final int position) {
200 | return internalGetItemViewType(position);
201 | }
202 |
203 | /**
204 | * Return the view type of the item at position
for the purposes
205 | * of view recycling.
206 | *
207 | * @param viewHolderPosition
208 | * position to query
209 | * @return integer value identifying the type of the view needed to represent the item at
210 | * position
. Type codes need not be contiguous.
211 | */
212 | protected int internalGetItemViewType(final int viewHolderPosition) {
213 | final BinderResult result = computeItemAndBinderIndex(viewHolderPosition);
214 |
215 | final Provider> binder = result.getBinder();
216 | final int viewType;
217 |
218 | if (binder != null) {
219 | viewType = getViewHolderCreatorMap().get(binder.get().getViewType(result.item)).getViewType();
220 | } else {
221 | viewType = -1;
222 | }
223 |
224 | return viewType;
225 | }
226 |
227 | /**
228 | * @param viewType
229 | * the internal viewtype.
230 | * @return the viewholder class.
231 | */
232 | protected Class extends VH> getViewHolderClass(final int viewType) {
233 | return mViewTypeToViewHolderClassMap.get(viewType);
234 | }
235 |
236 | @NonNull
237 | protected Map getViewHolderCreatorMap() {
238 | return mViewHolderCreatorMap;
239 | }
240 |
241 | @Override
242 | public VH onCreateViewHolder(final ViewGroup parent, final int viewType) {
243 | return (VH) getViewHolderCreatorMap().get(viewType).create(parent);
244 | }
245 |
246 | @Override
247 | @SuppressLint("RecyclerView")
248 | public void onBindViewHolder(final VH holder, final int viewHolderPosition) {
249 |
250 | final BinderResult result = computeItemAndBinderIndex(viewHolderPosition);
251 | final Binder binder = result.getBinder().get();
252 |
253 | if (binder != null && result.item != null) {
254 |
255 | if (mPreviousBoundViewHolderPosition == NO_PREVIOUS_BOUND_VIEWHOLDER) {
256 | prepare(viewHolderPosition, binder, result.item, result.binderList, result.binderIndex);
257 | }
258 |
259 | binder.bind(result.item, holder, result.binderList, result.binderIndex, getActionListener(result.item));
260 |
261 | prepareInternal(viewHolderPosition);
262 | mPreviousBoundViewHolderPosition = viewHolderPosition;
263 | }
264 | }
265 |
266 | private void prepareInternal(final int viewHolderPosition) {
267 | prepare(viewHolderPosition, Integer.signum(viewHolderPosition - mPreviousBoundViewHolderPosition));
268 | }
269 |
270 | /**
271 | * Calls {@link #prepare(int, Binder, Object, List, int)}.
272 | *
273 | * @param lastBoundViewHolderPosition
274 | * the position of the last viewholder that was bound.
275 | * @param direction
276 | * the direction the list is moving.
277 | */
278 | protected void prepare(final int lastBoundViewHolderPosition, final int direction) {
279 | for (int i = 1; i <= numViewHoldersToPrepare(); i++) {
280 | final int viewHolderPosition = lastBoundViewHolderPosition + direction * i;
281 | if (isViewHolderPositionWithinBounds(viewHolderPosition)) {
282 | final BinderResult result = computeItemAndBinderIndex(viewHolderPosition);
283 | final Binder binder = result.getBinder().get();
284 |
285 | if (binder != null && result.item != null) {
286 | prepare(viewHolderPosition, binder, result.item, result.binderList, result.binderIndex);
287 | }
288 | }
289 | }
290 | }
291 |
292 | /**
293 | * Calls {@link Binder#prepare(Object, List, int)}.
294 | *
295 | * @param viewHolderPosition
296 | * the position of the viewholder.
297 | * @param binder
298 | * the binder to call.
299 | * @param model
300 | * the model being prepared.
301 | * @param binderList
302 | * the list of binders
303 | * @param binderIndex
304 | * the index in the list of viewholders associated with this model
305 | */
306 | protected void prepare(final int viewHolderPosition,
307 | final Binder binder,
308 | final T model,
309 | final List>> binderList,
310 | final int binderIndex) {
311 | if (!mViewHolderPreparedCache.contains(viewHolderPosition)) {
312 | binder.prepare(model, binderList, binderIndex);
313 | mViewHolderPreparedCache.add(viewHolderPosition);
314 | }
315 | }
316 |
317 | /**
318 | * @return Number of viewholders to prepare ahead.
319 | */
320 | @SuppressWarnings("checkstyle:magicnumber")
321 | protected int numViewHoldersToPrepare() {
322 | return 3;
323 | }
324 |
325 | /**
326 | * Checks if the timeline position is within the bounds of the underlying List
327 | *
328 | * @param itemPosition
329 | * timeline item position.
330 | * @return true if within list bounds. False otherwise.
331 | */
332 | protected boolean isItemPositionWithinBounds(final int itemPosition) {
333 | return itemPosition >= 0 && itemPosition < mItems.size();
334 | }
335 |
336 | /**
337 | * Checks if the viewholder is within the bounds of underlying list.
338 | *
339 | * @param viewHolderPosition
340 | * viewholder position
341 | * @return true of within list bound. False otherwise.
342 | */
343 | protected boolean isViewHolderPositionWithinBounds(final int viewHolderPosition) {
344 | return viewHolderPosition >= 0 && viewHolderPosition < mViewHolderToItemPositionCache.size();
345 | }
346 |
347 | /**
348 | * Note that this is an O(n) operation, but it does not query for the list of binders.
349 | *
350 | * @param itemPosition
351 | * the position in the list of items.
352 | * @return the number of viewholders before the given item position.
353 | */
354 | @VisibleForTesting
355 | public int getViewHolderCount(final int itemPosition) {
356 | if (itemPosition >= 0 && !mItemPositionToFirstViewHolderPositionCache.isEmpty()) {
357 | if (itemPosition >= mItemPositionToFirstViewHolderPositionCache.size()) {
358 | return mViewHolderToItemPositionCache.size();
359 | } else {
360 | return mItemPositionToFirstViewHolderPositionCache.get(itemPosition);
361 | }
362 | } else {
363 | return 0;
364 | }
365 | }
366 |
367 | @Override
368 | public int getItemCount() {
369 | return mViewHolderToItemPositionCache.size();
370 | }
371 |
372 | /**
373 | * Note that this does not notify.
374 | *
375 | * @param item
376 | * the item to add to the adapter.
377 | */
378 | public void add(@NonNull final T item) {
379 | add(mItems.size(), item, true);
380 | }
381 |
382 | /**
383 | * @param item
384 | * the item to add to the adapter.
385 | * @param notify
386 | * whether or not to notify the adapter.
387 | */
388 | public void add(@NonNull final T item, final boolean notify) {
389 | add(mItems.size(), item, notify);
390 | }
391 |
392 | /**
393 | * This is an O(1) operation since it is cached.
394 | *
395 | * @param viewHolderPosition
396 | * the position in the view holder.
397 | * @return the position of the item in the list of items.
398 | */
399 | public int getItemPosition(final int viewHolderPosition) {
400 | if (isViewHolderPositionWithinBounds(viewHolderPosition)) {
401 | return mViewHolderToItemPositionCache.get(viewHolderPosition);
402 | } else {
403 | return -1;
404 | }
405 | }
406 |
407 | /**
408 | * @param itemIndex
409 | * the current view holders binder position.
410 | * @param viewHolderPosition
411 | * the view holder position.
412 | * @return the binder index associated with view holder.
413 | */
414 | public int getBinderPosition(final int itemIndex, final int viewHolderPosition) {
415 | return viewHolderPosition - mItemPositionToFirstViewHolderPositionCache.get(itemIndex);
416 | }
417 |
418 | /**
419 | * Note that this is an O(n) operation, since the cache needs to be updated.
420 | *
421 | * @param position
422 | * the position to insert into the list.
423 | * @param item
424 | * the item to add. Note that if it is null
, there is no way to determine which binder to use.
425 | * @param notify
426 | * whether or not to notify the adapter.
427 | */
428 | public void add(final int position, @NonNull final T item, final boolean notify) {
429 | final int numViewHolders = getViewHolderCount(position);
430 |
431 | final List>> binders = getParts(item, position);
432 |
433 | mItems.add(position, item);
434 | mBinderListCache.add(position, binders);
435 |
436 | if (binders != null) {
437 | if (notify) {
438 | notifyItemRangeInserted(numViewHolders, binders.size());
439 | }
440 |
441 | final List itemPositions = new ArrayList<>();
442 | for (int i = 0; i < binders.size(); i++) {
443 | itemPositions.add(position);
444 | }
445 |
446 | mViewHolderToItemPositionCache.addAll(numViewHolders, itemPositions);
447 | for (int viewHolderIndex = numViewHolders + binders.size(); viewHolderIndex < mViewHolderToItemPositionCache.size();
448 | viewHolderIndex++) {
449 | mViewHolderToItemPositionCache.set(viewHolderIndex, mViewHolderToItemPositionCache.get(viewHolderIndex) + 1);
450 | mViewHolderPreparedCache.remove(viewHolderIndex);
451 | }
452 |
453 | mItemPositionToFirstViewHolderPositionCache.add(position, numViewHolders);
454 | for (int itemIndex = position + 1; itemIndex < mItemPositionToFirstViewHolderPositionCache.size(); itemIndex++) {
455 | mItemPositionToFirstViewHolderPositionCache.set(itemIndex,
456 | mItemPositionToFirstViewHolderPositionCache.get(itemIndex) + binders.size());
457 | }
458 | }
459 | }
460 |
461 | /**
462 | * Note that this is an O(n) operation, since the cache needs to be updated.
463 | *
464 | * @param itemPosition
465 | * removes the item at the position from the adapter.
466 | * @return the removed item, or null
if the position was out of bounds.
467 | */
468 | @Nullable
469 | public T remove(final int itemPosition) {
470 | return remove(itemPosition, true);
471 | }
472 |
473 | /**
474 | * Note that this is an O(n) operation, since the cache needs to be updated.
475 | *
476 | * @param itemPosition
477 | * removes the item at the position from the adapter.
478 | * @param notify
479 | * whether or not to call {@link #notifyItemRangeRemoved(int, int)}
480 | * @return the removed item, or null
if the position was out of bounds.
481 | */
482 | @Nullable
483 | public T remove(final int itemPosition, final boolean notify) {
484 |
485 | final T item;
486 |
487 | if (isItemPositionWithinBounds(itemPosition)) {
488 | final int numViewHolders = getViewHolderCount(itemPosition);
489 |
490 | item = mItems.get(itemPosition);
491 |
492 | final List>> binders = mBinderListCache.get(itemPosition);
493 |
494 | mItems.remove(itemPosition);
495 |
496 | for (final ListIterator iter = mViewHolderToItemPositionCache.listIterator(); iter.hasNext(); ) {
497 | if (iter.next() == itemPosition) {
498 | iter.remove();
499 | }
500 | }
501 |
502 | for (int viewHolderIndex = numViewHolders; viewHolderIndex < mViewHolderToItemPositionCache.size();
503 | viewHolderIndex++) {
504 | mViewHolderToItemPositionCache.set(viewHolderIndex, mViewHolderToItemPositionCache.get(viewHolderIndex) - 1);
505 | mViewHolderPreparedCache.remove(viewHolderIndex);
506 | }
507 |
508 | mItemPositionToFirstViewHolderPositionCache.remove(itemPosition);
509 |
510 | if (binders != null) {
511 | for (int itemIndex = itemPosition; itemIndex < mItemPositionToFirstViewHolderPositionCache.size(); itemIndex++) {
512 | mItemPositionToFirstViewHolderPositionCache.set(itemIndex,
513 | mItemPositionToFirstViewHolderPositionCache.get(itemIndex) - binders.size());
514 | }
515 | }
516 |
517 | mBinderListCache.remove(itemPosition);
518 |
519 | if (binders != null && notify) {
520 | notifyItemRangeRemoved(numViewHolders, binders.size());
521 | }
522 | } else {
523 | item = null;
524 | }
525 |
526 | return item;
527 | }
528 |
529 | /**
530 | * Finds the adapter data position for the first instance of a particular view holder used in an item.
531 | *
532 | * @param itemPosition
533 | * the position for the item that uses the view holder.
534 | * @param viewHolderClass
535 | * the view holder type to look for.
536 | * @return the adapter data position for the view holder, or -1 if not found.
537 | */
538 | public int getFirstViewHolderPosition(final int itemPosition, @NonNull final Class extends VH> viewHolderClass) {
539 | if (isItemPositionWithinBounds(itemPosition) && mViewHolderClassToViewTypeMap.containsKey(viewHolderClass)) {
540 | final int itemStartPos = getViewHolderCount(itemPosition);
541 | int viewHolderIndex = 0;
542 | final List>> binders = mBinderListCache.get(itemPosition);
543 | final int viewType = mViewHolderClassToViewTypeMap.get(viewHolderClass);
544 | final T item = mItems.get(itemPosition);
545 | for (Provider> binder : binders) {
546 | if (binder.get().getViewType(item) == viewType) {
547 | return itemStartPos + viewHolderIndex;
548 | }
549 | viewHolderIndex++;
550 | }
551 | }
552 | return -1;
553 | }
554 |
555 | /**
556 | * Clears the adapter.
557 | */
558 | public void clear() {
559 | mItems.clear();
560 | mBinderListCache.clear();
561 | mViewHolderToItemPositionCache.clear();
562 | mItemPositionToFirstViewHolderPositionCache.clear();
563 | mViewHolderPreparedCache.clear();
564 | mPreviousBoundViewHolderPosition = NO_PREVIOUS_BOUND_VIEWHOLDER;
565 | }
566 |
567 | /**
568 | * This is very similar to {@link View#inflate(android.content.Context, int, ViewGroup)}
569 | * but does not attach the inflated view.
570 | *
571 | * @param parent
572 | * the parent viewgroup
573 | * @param layoutRes
574 | * the layout to inflate
575 | * @return the inflated and unattached view.
576 | */
577 | public static View inflate(final ViewGroup parent, @LayoutRes final int layoutRes) {
578 | return LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false);
579 | }
580 |
581 | @Override
582 | public void onViewRecycled(final VH holder) {
583 | super.onViewRecycled(holder);
584 |
585 | onViewRecycled(holder, holder.getAdapterPosition());
586 | }
587 |
588 | /**
589 | * @param holder
590 | * the viewholder to recycle.
591 | * @param viewHolderPosition
592 | * the position of the viewholder.
593 | */
594 | protected void onViewRecycled(final VH holder, final int viewHolderPosition) {
595 | if (isViewHolderPositionWithinBounds(viewHolderPosition)) {
596 | final BinderResult result = computeItemAndBinderIndex(viewHolderPosition);
597 | final Binder binder = result.getBinder().get();
598 |
599 | if (binder != null) {
600 | mViewHolderPreparedCache.remove(viewHolderPosition);
601 |
602 | if (holder.getItemViewType() == binder.getViewType(result.item)) {
603 | binder.unbind(holder);
604 | }
605 | }
606 | }
607 | }
608 |
609 | @NonNull
610 | public List getItems() {
611 | return mItems;
612 | }
613 |
614 | /**
615 | * @param itemPosition
616 | * the item position.
617 | * @return the binders that belong to the item at the given position.
618 | */
619 | @Nullable
620 | public List>> getBindersForPosition(final int itemPosition) {
621 | final List>> binders;
622 |
623 | if (isItemPositionWithinBounds(itemPosition)) {
624 | binders = mBinderListCache.get(itemPosition);
625 | } else {
626 | binders = null;
627 | }
628 |
629 | return binders;
630 | }
631 |
632 | /**
633 | * @param itemPosition
634 | * the item position.
635 | * @return the range of viewholders that represent the item. The first is the offset, the second is the count.
636 | */
637 | @Nullable
638 | public Pair getViewHolderRange(final int itemPosition) {
639 | final Pair range;
640 |
641 | if (isItemPositionWithinBounds(itemPosition)) {
642 | final int numViewHolders = getViewHolderCount(itemPosition);
643 |
644 | final List>> binders = mBinderListCache.get(itemPosition);
645 |
646 | range = new Pair<>(numViewHolders, binders.size());
647 | } else {
648 | range = null;
649 | }
650 |
651 | return range;
652 | }
653 |
654 | /**
655 | * Binds a model of type {@code U} to a viewholder of type {@code V}.
656 | *
657 | * @param
658 | * the model.
659 | * @param
660 | * the viewholder type of the adapter.
661 | * @param
662 | * the viewholder type to be bound.
663 | */
664 | public interface Binder {
665 |
666 | /**
667 | * @param model
668 | * the model that will be bound.
669 | * @return the type of the viewholder.
670 | */
671 | @AnyRes
672 | int getViewType(U model);
673 |
674 | /**
675 | * Called to notify this binder that it may be called soon in the future. It may be called multiple times
676 | * before the view is actually ready to be bound. It is useful for preloading images.
677 | *
678 | * @param model
679 | * the model that will be bound.
680 | * @param binderList
681 | * the list of binders.
682 | * @param binderIndex
683 | * the index of the binder in the list of binders.
684 | */
685 | void prepare(@NonNull U model, List>> binderList, int binderIndex);
686 |
687 | /**
688 | * Called when {@link android.support.v7.widget.RecyclerView.Adapter#onBindViewHolder(RecyclerView.ViewHolder,
689 | * int)}
690 | * is called.
691 | *
692 | * @param model
693 | * the model to bind to the viewholder
694 | * @param holder
695 | * the viewholder to update
696 | * @param binderList
697 | * the list of binders
698 | * @param binderIndex
699 | * the index in the list of viewholders associated with this model
700 | * @param actionListener
701 | * the action listener to use
702 | */
703 | void bind(@NonNull U model, @NonNull W holder, @NonNull List>> binderList,
704 | int binderIndex, @Nullable ActionListener actionListener);
705 |
706 | /**
707 | * Called when {@link android.support.v7.widget.RecyclerView.Adapter#onViewRecycled(RecyclerView.ViewHolder)}
708 | * is called.
709 | *
710 | * @param holder
711 | * the view holder that was recycled.
712 | */
713 | void unbind(@NonNull W holder);
714 | }
715 |
716 | /**
717 | * Creates a viewholder.
718 | */
719 | public interface ViewHolderCreator {
720 | /**
721 | * Called when {@link android.support.v7.widget.RecyclerView.Adapter#onCreateViewHolder(ViewGroup, int)}
722 | * is called.
723 | *
724 | * @param parent
725 | * the parent view.
726 | * @return the inflated view.
727 | */
728 | RecyclerView.ViewHolder create(ViewGroup parent);
729 |
730 | /**
731 | * In nearly all cases, this should simply return the layout id.
732 | *
733 | * @return the view type to associate with this {@link android.support.v7.widget.RecyclerView.ViewHolder}.
734 | */
735 | int getViewType();
736 | }
737 |
738 | /**
739 | * Gets the list of binders associated with an item.
740 | *
741 | * @param
742 | * the model type.
743 | * @param
744 | * the viewholder type.
745 | * @param
746 | * the binder type.
747 | */
748 | public interface ItemBinder> {
749 | /**
750 | * @param model
751 | * the model that will be bound.
752 | * @param position
753 | * the position of the model in the list.
754 | * @return the list of binders to use.
755 | */
756 | @NonNull
757 | List> getBinderList(@NonNull U model, int position);
758 | }
759 |
760 | /**
761 | * @param
762 | * the model type.
763 | * @param
764 | * the viewholder type from the adapter.
765 | * @param
766 | * the viewholder type.
767 | */
768 | public interface ActionListener {
769 | /**
770 | * @param model
771 | * the model associated with the view that was modified.
772 | * @param holder
773 | * the viewholder associated with the view that was touched.
774 | * @param v
775 | * the view that was touched.
776 | * @param binderList
777 | * the list of binders associated with the model.
778 | * @param binderIndex
779 | * the index of the binder that was modified.
780 | * @param obj
781 | * an extra object for message passing.
782 | */
783 | void act(@NonNull U model, @NonNull W holder, @NonNull View v,
784 | @NonNull List>> binderList,
785 | int binderIndex, @Nullable Object obj);
786 | }
787 |
788 | /**
789 | * A helper {@link android.view.View.OnClickListener} that can be used to hold references to objects that are
790 | * passed in during a {@link Binder#bind(Object, RecyclerView.ViewHolder, List, int, ActionListener)}.
791 | *
792 | * Note that it uses strong references.
793 | *
794 | * @param
795 | * the model type.
796 | * @param
797 | * the viewholder type of the adapter.
798 | * @param
799 | * the viewholder type to be bound.
800 | */
801 | public static class ActionListenerDelegate
802 | implements View.OnClickListener {
803 | /**
804 | * The model.
805 | */
806 | public U model;
807 | /**
808 | * The viewholder.
809 | */
810 | public W holder;
811 | /**
812 | * The list of binders.
813 | */
814 | public List>> binders;
815 | /**
816 | * The index into the list of binders.
817 | */
818 | public int binderIndex;
819 | /**
820 | * A spare object to pass around.
821 | */
822 | @Nullable
823 | public Object obj;
824 | /**
825 | * The listener to call on click.
826 | */
827 | public ActionListener actionListener;
828 |
829 | /**
830 | * @param actionListener
831 | * the listener to call on click.
832 | * @param model
833 | * the model that is being clicked.
834 | * @param holder
835 | * the view holder that is being clicked.
836 | * @param binders
837 | * the list of binders associated with the model.
838 | * @param binderIndex
839 | * the index into the list of binders of the view holder that is being clicked.
840 | * @param obj
841 | * an extra object that can be used to pass around extra data.
842 | */
843 | public void update(final ActionListener actionListener,
844 | @NonNull final U model, @NonNull final W holder,
845 | @NonNull final List>> binders, final int binderIndex,
846 | @Nullable final Object obj) {
847 | this.model = model;
848 | this.holder = holder;
849 | this.binders = binders;
850 | this.binderIndex = binderIndex;
851 | this.obj = obj;
852 | this.actionListener = actionListener;
853 | }
854 |
855 | @Override
856 | public void onClick(final View v) {
857 | actionListener.act(model, holder, v, binders, binderIndex, obj);
858 | }
859 | }
860 |
861 | /**
862 | * Internal class that holds the item and binder associated with a viewholder.
863 | */
864 | @VisibleForTesting
865 | final class BinderResult {
866 | /**
867 | * The item associated with the viewholder.
868 | */
869 | @Nullable
870 | public final T item;
871 | /**
872 | * The position of th item in the list of items.
873 | */
874 | @VisibleForTesting
875 | public final int itemPosition;
876 | /**
877 | * The list of binders associated with the item.
878 | */
879 | @Nullable
880 | public final List>> binderList;
881 | /**
882 | * The index of the binder to use in the list of binders.
883 | */
884 | public final int binderIndex;
885 |
886 | /**
887 | * @param item
888 | * the model.
889 | * @param itemPosition
890 | * the position of the model in the list of models.
891 | * @param binderList
892 | * the list of binders associated with the item.
893 | * @param binderIndex
894 | * the index of the specific binder to use in the {@code binderList}.
895 | */
896 | BinderResult(@Nullable final T item,
897 | final int itemPosition,
898 | @Nullable final List>> binderList,
899 | final int binderIndex) {
900 | this.item = item;
901 | this.itemPosition = itemPosition;
902 | this.binderList = binderList;
903 | this.binderIndex = binderIndex;
904 | }
905 |
906 | /**
907 | * @return the binder to use.
908 | */
909 | @Nullable
910 | public Provider> getBinder() {
911 | return binderList != null && binderIndex >= 0 && binderIndex < binderList.size()
912 | ? binderList.get(binderIndex) : null;
913 | }
914 | }
915 | }
916 |
--------------------------------------------------------------------------------
/graywater/src/test/java/com/tumblr/graywater/GraywaterAdapterTest.java:
--------------------------------------------------------------------------------
1 | package com.tumblr.graywater;
2 |
3 | import android.net.Uri;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.ImageView;
10 | import android.widget.TextView;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 | import org.robolectric.RobolectricTestRunner;
14 |
15 | import javax.inject.Provider;
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | import static org.junit.Assert.assertEquals;
20 |
21 | @RunWith(RobolectricTestRunner.class)
22 | public class GraywaterAdapterTest {
23 |
24 | private abstract static class TestBinder
25 | implements GraywaterAdapter.Binder {
26 |
27 | }
28 |
29 | private static class TestAdapter
30 | extends GraywaterAdapter<
31 | Object,
32 | RecyclerView.ViewHolder,
33 | TestBinder, RecyclerView.ViewHolder, ? extends RecyclerView.ViewHolder>,
34 | Class>> {
35 |
36 | @Override
37 | protected Class> getModelType(final Object model) {
38 |
39 | Class> modelType = model.getClass();
40 |
41 | // Types are messy.
42 | final ItemBinder itemBinder = mItemBinderMap.get(modelType);
43 |
44 | final Class> declaringClass = modelType.getDeclaringClass();
45 | if (itemBinder == null && declaringClass != null) {
46 | modelType = declaringClass;
47 | }
48 |
49 | return modelType;
50 | }
51 |
52 | private static class TextViewHolder extends RecyclerView.ViewHolder {
53 | TextViewHolder(final View itemView) {
54 | super(itemView);
55 | }
56 | }
57 |
58 | private static class ImageViewHolder extends RecyclerView.ViewHolder {
59 | ImageViewHolder(final View itemView) {
60 | super(itemView);
61 | }
62 | }
63 |
64 | private static class TextViewHolderCreator implements GraywaterAdapter.ViewHolderCreator {
65 |
66 | static final int VIEW_TYPE = 1;
67 |
68 | @Override
69 | public TextViewHolder create(final ViewGroup parent) {
70 | return new TextViewHolder(new TextView(parent.getContext()));
71 | }
72 |
73 | @Override
74 | public int getViewType() {
75 | return VIEW_TYPE;
76 | }
77 | }
78 |
79 | private static class ImageViewHolderCreator implements GraywaterAdapter.ViewHolderCreator {
80 |
81 | static final int VIEW_TYPE = 2;
82 |
83 | @Override
84 | public ImageViewHolder create(final ViewGroup parent) {
85 | return new ImageViewHolder(new ImageView(parent.getContext()));
86 | }
87 |
88 | @Override
89 | public int getViewType() {
90 | return VIEW_TYPE;
91 | }
92 | }
93 |
94 | private static class TextBinder extends TestBinder {
95 |
96 | @Override
97 | public int getViewType(@NonNull final String model) {
98 | return TextViewHolderCreator.VIEW_TYPE;
99 | }
100 |
101 | @Override
102 | public void prepare(@NonNull final String model,
103 | final List>> binderList,
105 | final int binderIndex) {
106 |
107 | }
108 |
109 | @Override
110 | public void bind(@NonNull final String model,
111 | @NonNull final TextViewHolder holder,
112 | @NonNull final List>> binderList,
114 | final int binderIndex,
115 | @Nullable final ActionListener actionListener) {
116 | ((TextView) holder.itemView).setText(model);
117 | }
118 |
119 | @Override
120 | public void unbind(@NonNull final TextViewHolder holder) {
121 | ((TextView) holder.itemView).setText("");
122 | }
123 | }
124 |
125 | private static class ImageBinder extends TestBinder {
126 | @Override
127 | public int getViewType(@NonNull final Uri model) {
128 | return ImageViewHolderCreator.VIEW_TYPE;
129 | }
130 |
131 | @Override
132 | public void prepare(@NonNull final Uri model,
133 | final List>> binderList,
135 | final int binderIndex) {
136 |
137 | }
138 |
139 | @Override
140 | public void bind(@NonNull final Uri model,
141 | @NonNull final ImageViewHolder holder,
142 | @NonNull final List>> binderList,
144 | final int binderIndex,
145 | @Nullable final ActionListener actionListener) {
146 | ((ImageView) holder.itemView).setImageURI(model); // not a good idea in production ;)
147 | }
148 |
149 | @Override
150 | public void unbind(@NonNull final ImageViewHolder holder) {
151 |
152 | }
153 | }
154 |
155 | public TestAdapter() {
156 | super();
157 |
158 | register(new TextViewHolderCreator(), TextViewHolder.class);
159 | register(new ImageViewHolderCreator(), ImageViewHolder.class);
160 |
161 | final Provider textBinder = new Provider() {
162 | @Override
163 | public TextBinder get() {
164 | return new TextBinder();
165 | }
166 | };
167 | final Provider imageBinder = new Provider() {
168 | @Override
169 | public ImageBinder get() {
170 | return new ImageBinder();
171 | }
172 | };
173 |
174 | register(
175 | String.class,
176 | new ItemBinder>() {
178 | @NonNull
179 | @Override
180 | public List>>
181 | getBinderList(@NonNull final String model, final int position) {
182 | return new ArrayList>>() {{
184 | add(textBinder);
185 | add(textBinder);
186 | }};
187 | }
188 | }, null);
189 |
190 | register(
191 | Uri.class,
192 | new ItemBinder>() {
194 | @NonNull
195 | @Override
196 | public List>> getBinderList(@NonNull final Uri model, final int position) {
198 | return new ArrayList<
199 | Provider extends TestBinder>>() {{
200 | add(imageBinder);
201 | add(imageBinder);
202 | add(imageBinder);
203 | }};
204 | }
205 | }, null);
206 | }
207 | }
208 |
209 | @Test
210 | public void testAdd() throws Exception {
211 | final TestAdapter adapter = new TestAdapter();
212 |
213 | adapter.add("one");
214 | assertEquals(2, adapter.getItemCount());
215 |
216 | adapter.add("two");
217 | assertEquals(4, adapter.getItemCount());
218 |
219 | adapter.add("three");
220 | assertEquals(6, adapter.getItemCount());
221 | }
222 |
223 | @Test
224 | public void testRemove() throws Exception {
225 | final TestAdapter adapter = new TestAdapter();
226 |
227 | adapter.add("zero");
228 | adapter.add("one");
229 | adapter.add("two");
230 | adapter.add("three");
231 | adapter.add("four");
232 | adapter.add("five");
233 |
234 | // remove
235 | adapter.remove(0);
236 | assertEquals(2 * 5, adapter.getItemCount());
237 | adapter.remove(4);
238 | assertEquals(2 * 4, adapter.getItemCount());
239 | adapter.remove(2);
240 | assertEquals(2 * 3, adapter.getItemCount());
241 |
242 | // state
243 | final List items = adapter.getItems();
244 | assertEquals(3, items.size());
245 | assertEquals("one", items.get(0));
246 | assertEquals("two", items.get(1));
247 | assertEquals("four", items.get(2));
248 | }
249 |
250 | @Test
251 | public void testViewHolderPosition() throws Exception {
252 | final TestAdapter adapter = new TestAdapter();
253 |
254 | adapter.add("zero");
255 | adapter.add("one");
256 | adapter.add("two");
257 | adapter.add("three");
258 | adapter.add("four");
259 | adapter.add("five");
260 |
261 | // ["zero", "zero", "one", "one, ... ]
262 | final GraywaterAdapter.BinderResult one = adapter.computeItemAndBinderIndex(2);
263 | assertEquals(1, one.itemPosition);
264 | assertEquals(0, one.binderIndex);
265 | assertEquals("one", one.item);
266 |
267 | final GraywaterAdapter.BinderResult oneDouble = adapter.computeItemAndBinderIndex(3);
268 | assertEquals(1, oneDouble.itemPosition);
269 | assertEquals(1, oneDouble.binderIndex);
270 | assertEquals("one", oneDouble.item);
271 |
272 | final GraywaterAdapter.BinderResult three = adapter.computeItemAndBinderIndex(6);
273 | assertEquals(3, three.itemPosition);
274 | assertEquals(0, three.binderIndex);
275 | assertEquals("three", three.item);
276 |
277 | final GraywaterAdapter.BinderResult threeDouble = adapter.computeItemAndBinderIndex(7);
278 | assertEquals(3, threeDouble.itemPosition);
279 | assertEquals(1, threeDouble.binderIndex);
280 | assertEquals("three", three.item);
281 |
282 | final GraywaterAdapter.BinderResult zero = adapter.computeItemAndBinderIndex(0);
283 | assertEquals(0, zero.itemPosition);
284 | assertEquals(0, zero.binderIndex);
285 | assertEquals("zero", zero.item);
286 |
287 | final GraywaterAdapter.BinderResult five = adapter.computeItemAndBinderIndex(11);
288 | assertEquals(5, five.itemPosition);
289 | assertEquals(1, five.binderIndex);
290 | assertEquals("five", five.item);
291 | }
292 |
293 | @Test
294 | public void testViewHolderPositionWithRemove() throws Exception {
295 | final TestAdapter adapter = new TestAdapter();
296 |
297 | adapter.add("zero");
298 | adapter.add("one");
299 | adapter.add("two");
300 | adapter.add("three");
301 | adapter.add("four");
302 | adapter.add("five");
303 |
304 | adapter.remove(0);
305 |
306 | // ["one", "one, "two", "two", ... ]
307 | final GraywaterAdapter.BinderResult two = adapter.computeItemAndBinderIndex(2);
308 | assertEquals(1, two.itemPosition);
309 | assertEquals(0, two.binderIndex);
310 |
311 | // four is in position five
312 | final GraywaterAdapter.BinderResult four = adapter.computeItemAndBinderIndex(9);
313 | assertEquals(4, four.itemPosition);
314 | assertEquals(1, four.binderIndex);
315 | }
316 |
317 | @Test
318 | public void testViewHolderPositionWithRemoveThenAdd() throws Exception {
319 | final TestAdapter adapter = new TestAdapter();
320 |
321 | adapter.add("zero");
322 | adapter.add("one");
323 | adapter.add("two");
324 | adapter.add("three");
325 | adapter.add("four");
326 | adapter.add("five");
327 |
328 | adapter.remove(0);
329 |
330 | adapter.add(0, "zero", false);
331 |
332 | // ["zero", "zero", "one", "one, ... ]
333 | final GraywaterAdapter.BinderResult one = adapter.computeItemAndBinderIndex(2);
334 | assertEquals(1, one.itemPosition);
335 | assertEquals(0, one.binderIndex);
336 | assertEquals("one", one.item);
337 |
338 | final GraywaterAdapter.BinderResult oneDouble = adapter.computeItemAndBinderIndex(3);
339 | assertEquals(1, oneDouble.itemPosition);
340 | assertEquals(1, oneDouble.binderIndex);
341 | assertEquals("one", oneDouble.item);
342 |
343 | final GraywaterAdapter.BinderResult three = adapter.computeItemAndBinderIndex(6);
344 | assertEquals(3, three.itemPosition);
345 | assertEquals(0, three.binderIndex);
346 | assertEquals("three", three.item);
347 |
348 | final GraywaterAdapter.BinderResult threeDouble = adapter.computeItemAndBinderIndex(7);
349 | assertEquals(3, threeDouble.itemPosition);
350 | assertEquals(1, threeDouble.binderIndex);
351 | assertEquals("three", three.item);
352 |
353 | final GraywaterAdapter.BinderResult zero = adapter.computeItemAndBinderIndex(0);
354 | assertEquals(0, zero.itemPosition);
355 | assertEquals(0, zero.binderIndex);
356 | assertEquals("zero", zero.item);
357 |
358 | final GraywaterAdapter.BinderResult five = adapter.computeItemAndBinderIndex(11);
359 | assertEquals(5, five.itemPosition);
360 | assertEquals(1, five.binderIndex);
361 | assertEquals("five", five.item);
362 | }
363 |
364 | @Test
365 | public void testViewHolderPositionWithRemoveThenAddMiddle() throws Exception {
366 | final TestAdapter adapter = new TestAdapter();
367 |
368 | adapter.add("zero");
369 | adapter.add("one");
370 | adapter.add("two");
371 | adapter.add("three");
372 | adapter.add("four");
373 | adapter.add("five");
374 |
375 | final Object obj = adapter.remove(2);
376 | assertEquals("two", obj);
377 |
378 | adapter.add(2, "two", false);
379 |
380 | // ["zero", "zero", "one", "one, ... ]
381 | final GraywaterAdapter.BinderResult one = adapter.computeItemAndBinderIndex(2);
382 | assertEquals(1, one.itemPosition);
383 | assertEquals(0, one.binderIndex);
384 | assertEquals("one", one.item);
385 |
386 | final GraywaterAdapter.BinderResult oneDouble = adapter.computeItemAndBinderIndex(3);
387 | assertEquals(1, oneDouble.itemPosition);
388 | assertEquals(1, oneDouble.binderIndex);
389 | assertEquals("one", oneDouble.item);
390 |
391 | final GraywaterAdapter.BinderResult three = adapter.computeItemAndBinderIndex(6);
392 | assertEquals(3, three.itemPosition);
393 | assertEquals(0, three.binderIndex);
394 | assertEquals("three", three.item);
395 |
396 | final GraywaterAdapter.BinderResult threeDouble = adapter.computeItemAndBinderIndex(7);
397 | assertEquals(3, threeDouble.itemPosition);
398 | assertEquals(1, threeDouble.binderIndex);
399 | assertEquals("three", three.item);
400 |
401 | final GraywaterAdapter.BinderResult zero = adapter.computeItemAndBinderIndex(0);
402 | assertEquals(0, zero.itemPosition);
403 | assertEquals(0, zero.binderIndex);
404 | assertEquals("zero", zero.item);
405 |
406 | final GraywaterAdapter.BinderResult five = adapter.computeItemAndBinderIndex(11);
407 | assertEquals(5, five.itemPosition);
408 | assertEquals(1, five.binderIndex);
409 | assertEquals("five", five.item);
410 | }
411 |
412 | @Test
413 | public void testGetItemViewType() throws Exception {
414 | final TestAdapter adapter = new TestAdapter();
415 |
416 | // ["https://www.tumblr.com", "https://www.tumblr.com", "https://www.tumblr.com", "one", "one",
417 | // "http://dreamynomad.com", "http://dreamynomad.com", "http://dreamynomad.com", ...]
418 | adapter.add(Uri.parse("https://www.tumblr.com"));
419 | assertEquals(3, adapter.getItemCount());
420 | adapter.add("one");
421 | assertEquals(5, adapter.getItemCount());
422 | adapter.add(Uri.parse("http://dreamynomad.com"));
423 | assertEquals(8, adapter.getItemCount());
424 | adapter.add("three");
425 | assertEquals(10, adapter.getItemCount());
426 | adapter.add(Uri.parse("https://google.com"));
427 | assertEquals(13, adapter.getItemCount());
428 | adapter.add("five");
429 | assertEquals(15, adapter.getItemCount());
430 |
431 | assertEquals(TestAdapter.ImageViewHolderCreator.VIEW_TYPE, adapter.getItemViewType(0));
432 | assertEquals(TestAdapter.ImageViewHolderCreator.VIEW_TYPE, adapter.getItemViewType(1));
433 | assertEquals(TestAdapter.ImageViewHolderCreator.VIEW_TYPE, adapter.getItemViewType(2));
434 | assertEquals(TestAdapter.TextViewHolderCreator.VIEW_TYPE, adapter.getItemViewType(3));
435 | assertEquals(TestAdapter.TextViewHolderCreator.VIEW_TYPE, adapter.getItemViewType(4));
436 | assertEquals(TestAdapter.ImageViewHolderCreator.VIEW_TYPE, adapter.getItemViewType(5));
437 | assertEquals(TestAdapter.ImageViewHolderCreator.VIEW_TYPE, adapter.getItemViewType(6));
438 | // ...
439 | assertEquals(TestAdapter.ImageViewHolderCreator.VIEW_TYPE, adapter.getItemViewType(10));
440 | assertEquals(TestAdapter.ImageViewHolderCreator.VIEW_TYPE, adapter.getItemViewType(11));
441 | assertEquals(TestAdapter.ImageViewHolderCreator.VIEW_TYPE, adapter.getItemViewType(12));
442 | assertEquals(TestAdapter.TextViewHolderCreator.VIEW_TYPE, adapter.getItemViewType(13));
443 | assertEquals(TestAdapter.TextViewHolderCreator.VIEW_TYPE, adapter.getItemViewType(14));
444 | }
445 |
446 | @Test
447 | public void testMultiViewHolderPosition() throws Exception {
448 | final TestAdapter adapter = new TestAdapter();
449 |
450 | // ["https://www.tumblr.com", "https://www.tumblr.com", "https://www.tumblr.com", "one", "one",
451 | // "http://dreamynomad.com", "http://dreamynomad.com", "http://dreamynomad.com", ...]
452 | final Uri tumblrUri = Uri.parse("https://www.tumblr.com");
453 | adapter.add(tumblrUri);
454 | assertEquals(3, adapter.getItemCount());
455 | adapter.add("one");
456 | assertEquals(5, adapter.getItemCount());
457 | adapter.add(Uri.parse("http://dreamynomad.com"));
458 | assertEquals(8, adapter.getItemCount());
459 | adapter.add("three");
460 | assertEquals(10, adapter.getItemCount());
461 | final Uri googleUri = Uri.parse("https://google.com");
462 | adapter.add(googleUri);
463 | assertEquals(13, adapter.getItemCount());
464 | adapter.add("five");
465 | assertEquals(15, adapter.getItemCount());
466 |
467 | final GraywaterAdapter.BinderResult tumblr = adapter.computeItemAndBinderIndex(1);
468 | assertEquals(0, tumblr.itemPosition);
469 | assertEquals(1, tumblr.binderIndex);
470 | assertEquals(tumblrUri, tumblr.item);
471 |
472 | final GraywaterAdapter.BinderResult one = adapter.computeItemAndBinderIndex(3);
473 | assertEquals(1, one.itemPosition);
474 | assertEquals(0, one.binderIndex);
475 | assertEquals("one", one.item);
476 |
477 | final GraywaterAdapter.BinderResult google = adapter.computeItemAndBinderIndex(12);
478 | assertEquals(4, google.itemPosition);
479 | assertEquals(2, google.binderIndex);
480 | assertEquals(googleUri, google.item);
481 |
482 | final GraywaterAdapter.BinderResult five = adapter.computeItemAndBinderIndex(14);
483 | assertEquals(5, five.itemPosition);
484 | assertEquals(1, five.binderIndex);
485 | assertEquals("five", five.item);
486 | }
487 |
488 | @Test
489 | public void testMultiViewHolderPositionWithRemoveThenAdd() throws Exception {
490 | final TestAdapter adapter = new TestAdapter();
491 |
492 | // ["https://www.tumblr.com", "https://www.tumblr.com", "https://www.tumblr.com", "one", "one",
493 | // "http://dreamynomad.com", "http://dreamynomad.com", "http://dreamynomad.com", ...]
494 | final Uri tumblrUri = Uri.parse("https://www.tumblr.com");
495 | adapter.add(tumblrUri);
496 | assertEquals(3, adapter.getItemCount());
497 | adapter.add("one");
498 | assertEquals(5, adapter.getItemCount());
499 | adapter.add(Uri.parse("http://dreamynomad.com"));
500 | assertEquals(8, adapter.getItemCount());
501 | adapter.add("three");
502 | assertEquals(10, adapter.getItemCount());
503 | final Uri googleUri = Uri.parse("https://google.com");
504 | adapter.add(googleUri);
505 | assertEquals(13, adapter.getItemCount());
506 | adapter.add("five");
507 | assertEquals(15, adapter.getItemCount());
508 |
509 | adapter.remove(3);
510 | adapter.add(3, "three", false);
511 |
512 | final GraywaterAdapter.BinderResult tumblr = adapter.computeItemAndBinderIndex(1);
513 | assertEquals(0, tumblr.itemPosition);
514 | assertEquals(1, tumblr.binderIndex);
515 | assertEquals(tumblrUri, tumblr.item);
516 |
517 | final GraywaterAdapter.BinderResult one = adapter.computeItemAndBinderIndex(3);
518 | assertEquals(1, one.itemPosition);
519 | assertEquals(0, one.binderIndex);
520 | assertEquals("one", one.item);
521 |
522 | final GraywaterAdapter.BinderResult google = adapter.computeItemAndBinderIndex(12);
523 | assertEquals(4, google.itemPosition);
524 | assertEquals(2, google.binderIndex);
525 | assertEquals(googleUri, google.item);
526 |
527 | final GraywaterAdapter.BinderResult five = adapter.computeItemAndBinderIndex(14);
528 | assertEquals(5, five.itemPosition);
529 | assertEquals(1, five.binderIndex);
530 | assertEquals("five", five.item);
531 | }
532 |
533 | @Test
534 | public void testClear() throws Exception {
535 | final TestAdapter adapter = new TestAdapter();
536 |
537 | // ["https://www.tumblr.com", "https://www.tumblr.com", "https://www.tumblr.com", "one", "one",
538 | // "http://dreamynomad.com", "http://dreamynomad.com", "http://dreamynomad.com", ...]
539 | final Uri tumblrUri = Uri.parse("https://www.tumblr.com");
540 | adapter.add(tumblrUri);
541 | assertEquals(3, adapter.getItemCount());
542 | adapter.add("one");
543 | assertEquals(5, adapter.getItemCount());
544 | adapter.add(Uri.parse("http://dreamynomad.com"));
545 | assertEquals(8, adapter.getItemCount());
546 | adapter.add("three");
547 | assertEquals(10, adapter.getItemCount());
548 | final Uri googleUri = Uri.parse("https://google.com");
549 | adapter.add(googleUri);
550 | assertEquals(13, adapter.getItemCount());
551 | adapter.add("five");
552 | assertEquals(15, adapter.getItemCount());
553 |
554 | adapter.clear();
555 |
556 | assertEquals(0, adapter.getItemCount());
557 | }
558 |
559 | @Test
560 | public void testClearThenAdd() throws Exception {
561 | final TestAdapter adapter = new TestAdapter();
562 |
563 | // ["https://www.tumblr.com", "https://www.tumblr.com", "https://www.tumblr.com", "one", "one",
564 | // "http://dreamynomad.com", "http://dreamynomad.com", "http://dreamynomad.com", ...]
565 | final Uri tumblrUri = Uri.parse("https://www.tumblr.com");
566 | adapter.add(tumblrUri);
567 | assertEquals(3, adapter.getItemCount());
568 | adapter.add("one");
569 | assertEquals(5, adapter.getItemCount());
570 | adapter.add(Uri.parse("http://dreamynomad.com"));
571 | assertEquals(8, adapter.getItemCount());
572 | adapter.add("three");
573 | assertEquals(10, adapter.getItemCount());
574 | final Uri googleUri = Uri.parse("https://google.com");
575 | adapter.add(googleUri);
576 | assertEquals(13, adapter.getItemCount());
577 | adapter.add("five");
578 | assertEquals(15, adapter.getItemCount());
579 |
580 | // Clear!
581 | adapter.clear();
582 |
583 | // ["https://www.tumblr.com", "https://www.tumblr.com", "https://www.tumblr.com", "one", "one",
584 | // "http://dreamynomad.com", "http://dreamynomad.com", "http://dreamynomad.com", ...]
585 | adapter.add(tumblrUri);
586 | assertEquals(3, adapter.getItemCount());
587 | adapter.add("one");
588 | assertEquals(5, adapter.getItemCount());
589 | adapter.add(Uri.parse("http://dreamynomad.com"));
590 | assertEquals(8, adapter.getItemCount());
591 | adapter.add("three");
592 | assertEquals(10, adapter.getItemCount());
593 | adapter.add(googleUri);
594 | assertEquals(13, adapter.getItemCount());
595 | adapter.add("five");
596 | assertEquals(15, adapter.getItemCount());
597 |
598 | final GraywaterAdapter.BinderResult tumblr = adapter.computeItemAndBinderIndex(1);
599 | assertEquals(0, tumblr.itemPosition);
600 | assertEquals(1, tumblr.binderIndex);
601 | assertEquals(tumblrUri, tumblr.item);
602 |
603 | final GraywaterAdapter.BinderResult one = adapter.computeItemAndBinderIndex(3);
604 | assertEquals(1, one.itemPosition);
605 | assertEquals(0, one.binderIndex);
606 | assertEquals("one", one.item);
607 |
608 | final GraywaterAdapter.BinderResult google = adapter.computeItemAndBinderIndex(12);
609 | assertEquals(4, google.itemPosition);
610 | assertEquals(2, google.binderIndex);
611 | assertEquals(googleUri, google.item);
612 |
613 | final GraywaterAdapter.BinderResult five = adapter.computeItemAndBinderIndex(14);
614 | assertEquals(5, five.itemPosition);
615 | assertEquals(1, five.binderIndex);
616 | assertEquals("five", five.item);
617 | }
618 |
619 | @Test
620 | public void GraywaterAdapter_FirstVHPosition_FoundVHPosition() throws Exception {
621 | final TestAdapter adapter = new TestAdapter();
622 | adapter.add("Testing");
623 | final Uri tumblrUri = Uri.parse("https://www.tumblr.com");
624 | adapter.add(tumblrUri);
625 | adapter.add("Testing");
626 | final int imageViewHolderPosition = adapter.getFirstViewHolderPosition(1, TestAdapter.ImageViewHolder.class);
627 | assertEquals(2, imageViewHolderPosition);
628 | }
629 |
630 | @Test
631 | public void GraywaterAdapter_FirstVHPosition_DidNotFindVHPosition() throws Exception {
632 | final TestAdapter adapter = new TestAdapter();
633 | adapter.add("Testing");
634 | final Uri tumblrUri = Uri.parse("https://www.tumblr.com");
635 | adapter.add(tumblrUri);
636 | adapter.add("Testing");
637 | final int imageViewHolderPosition = adapter.getFirstViewHolderPosition(0, TestAdapter.ImageViewHolder.class);
638 | assertEquals(-1, imageViewHolderPosition);
639 | }
640 |
641 | @Test
642 | public void GraywaterAdapter_FirstVHPosition_InvalidItemPosition() throws Exception {
643 | final TestAdapter adapter = new TestAdapter();
644 | adapter.add("Testing");
645 | final Uri tumblrUri = Uri.parse("https://www.tumblr.com");
646 | adapter.add(tumblrUri);
647 | adapter.add("Testing");
648 | final int imageViewHolderPosition = adapter.getFirstViewHolderPosition(0xDEADBEEF, TestAdapter.ImageViewHolder.class);
649 | assertEquals(-1, imageViewHolderPosition);
650 | }
651 | }
652 |
--------------------------------------------------------------------------------
/graywater/src/test/resources/robolectric.properties:
--------------------------------------------------------------------------------
1 | constants=com.tumblr.graywater.BuildConfig
2 | packageName=com.tumblr.graywater
3 | sdk=23
4 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':graywater'
2 |
--------------------------------------------------------------------------------