subscriber) {
33 | verifyMainThread();
34 |
35 | FeatureEventListener listener =
36 | event -> {
37 | if (!subscriber.isDisposed()) {
38 | subscriber.onNext(event);
39 | }
40 | };
41 |
42 | subscriber.setCancellable(() -> featureController.removeFeatureEventListener(listener));
43 |
44 | featureController.addFeatureEventListener(listener);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/feature-adapter-rx/src/main/java/com/groupon/featureadapter/events/RxFeatureEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter.events;
17 |
18 |
19 | import com.groupon.featureadapter.FeatureController;
20 | import io.reactivex.Observable;
21 | import java.util.ArrayList;
22 | import java.util.List;
23 |
24 | import static io.reactivex.Observable.merge;
25 |
26 | /**
27 | * A helper class to make it easier to use {@link FeatureEvent}s from {@link FeatureController}s
28 | * with Rx 1.
29 | */
30 | public class RxFeatureEvent {
31 |
32 | /**
33 | * Creates an observable of {@link FeatureEvent}s out of a {@link FeatureControllerGroup}. It is
34 | * possible to call this method multiple times on the controller.
35 | *
36 | * Warning: The created observable keeps a strong reference to {@code
37 | * featureControllers}. Unsubscribe to free this reference.
38 | *
39 | * @param featureControllers a list of feature controllers.
40 | * @return an observable of the {@link FeatureEvent} that this group emits.
41 | */
42 | public static Observable featureEvents(
43 | List> featureControllers) {
44 | List> observables = new ArrayList<>();
45 | for (FeatureController controller : featureControllers) {
46 | observables.add(featureEvents(controller));
47 | }
48 | return merge(observables);
49 | }
50 |
51 | /**
52 | * Creates an observable of {@link FeatureEvent}s out of a {@link FeatureController}. It is
53 | * possible to call this method multiple times on the controller.
54 | *
55 | * Warning: The created observable keeps a strong reference to {@code controller}.
56 | * Unsubscribe to free this reference.
57 | *
58 | * @param controller a {@link FeatureController}.
59 | * @return an observable of the {@link FeatureEvent} that this controller emits.
60 | */
61 | public static Observable featureEvents(
62 | FeatureController controller) {
63 | return Observable.create(new FeatureControllerOnSubscribe(controller));
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | buildToolsVersion "28.0.3"
6 | defaultConfig {
7 | applicationId "com.groupon.android.featureadapter.sample.rx"
8 | minSdkVersion 19
9 | targetSdkVersion 26
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | compileOptions {
21 | sourceCompatibility JavaVersion.VERSION_1_8
22 | targetCompatibility JavaVersion.VERSION_1_8
23 | }
24 |
25 | lintOptions {
26 | abortOnError true
27 | quiet false
28 | htmlReport true
29 | xmlReport true
30 | lintConfig file("lint.xml")
31 | }
32 | }
33 |
34 | dependencies {
35 | implementation project(':feature-adapter-rx')
36 | implementation project(':feature-adapter-group')
37 |
38 | implementation 'androidx.appcompat:appcompat:1.1.0-alpha02'
39 | implementation 'com.google.android.material:material:1.1.0-alpha03'
40 | implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha02'
41 |
42 | implementation 'io.reactivex.rxjava2:rxjava:2.2.7'
43 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
44 | implementation 'com.jakewharton.rxbinding3:rxbinding:3.0.0-alpha2'
45 |
46 | implementation 'com.google.android:flexbox:1.1.0'
47 |
48 | implementation 'com.google.auto.value:auto-value-annotations:1.6.3'
49 | annotationProcessor 'com.google.auto.value:auto-value:1.6.3'
50 | implementation 'javax.annotation:javax.annotation-api:1.2'
51 |
52 | implementation 'com.jakewharton:butterknife:10.1.0'
53 | annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
54 |
55 | testImplementation 'junit:junit:4.12'
56 | testImplementation "org.easymock:easymock:3.4"
57 | compileOnly 'com.google.code.findbugs:jsr305:3.0.2'
58 |
59 | implementation 'com.groupon.grox:grox-core:1.1.2'
60 | implementation 'com.groupon.grox:grox-core-rx2:1.1.2'
61 | implementation 'com.groupon.grox:grox-commands-rx2:1.1.2'
62 |
63 | implementation 'com.github.stephanenicolas.toothpick:toothpick-runtime:2.1.0'
64 | implementation 'com.github.stephanenicolas.toothpick:smoothie-androidx:2.1.0'
65 | annotationProcessor 'com.github.stephanenicolas.toothpick:toothpick-compiler:2.1.0'
66 |
67 | testImplementation 'junit:junit:4.12'
68 | testImplementation 'org.easymock:easymock:3.4'
69 | }
70 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/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/ksmyth/Workspace/android/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 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/DealDetailsActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample;
17 |
18 | import static com.groupon.android.featureadapter.sample.state.SampleModel.STATE_ERROR;
19 | import static com.groupon.android.featureadapter.sample.state.SampleModel.STATE_LOADING;
20 | import static com.groupon.android.featureadapter.sample.state.SampleModel.STATE_READY;
21 | import static com.groupon.featureadapter.events.RxFeatureEvent.featureEvents;
22 | import static com.groupon.grox.rxjava2.RxStores.states;
23 | import static com.jakewharton.rxbinding3.view.RxView.clicks;
24 | import static io.reactivex.android.schedulers.AndroidSchedulers.mainThread;
25 | import static io.reactivex.schedulers.Schedulers.computation;
26 | import static io.reactivex.schedulers.Schedulers.io;
27 | import static toothpick.Toothpick.closeScope;
28 | import static toothpick.Toothpick.inject;
29 | import static toothpick.Toothpick.openScopes;
30 |
31 | import android.os.Bundle;
32 | import androidx.annotation.Nullable;
33 | import android.util.Log;
34 | import android.view.View;
35 | import android.widget.Button;
36 | import android.widget.ProgressBar;
37 | import androidx.appcompat.app.AppCompatActivity;
38 | import androidx.recyclerview.widget.RecyclerView;
39 | import butterknife.BindView;
40 | import butterknife.ButterKnife;
41 | import com.google.android.flexbox.FlexboxLayoutManager;
42 | import com.google.android.material.snackbar.Snackbar;
43 | import com.groupon.android.featureadapter.sample.events.RefreshDealCommand;
44 | import com.groupon.android.featureadapter.sample.features.FeatureControllerListCreator;
45 | import com.groupon.android.featureadapter.sample.rx.R;
46 | import com.groupon.android.featureadapter.sample.state.DealDetailsScopeSingleton;
47 | import com.groupon.android.featureadapter.sample.state.SampleModel;
48 | import com.groupon.android.featureadapter.sample.state.SampleStore;
49 | import com.groupon.featureadapter.FeatureAdapterDefaultAnimator;
50 | import com.groupon.featureadapter.FeatureAdapterItemDecoration;
51 | import com.groupon.featureadapter.FeatureAnimatorController;
52 | import com.groupon.featureadapter.FeatureController;
53 | import com.groupon.featureadapter.FeatureUpdate;
54 | import com.groupon.featureadapter.RxFeaturesAdapter;
55 | import com.groupon.grox.commands.rxjava2.Command;
56 | import io.reactivex.BackpressureStrategy;
57 | import io.reactivex.disposables.CompositeDisposable;
58 | import java.util.List;
59 | import javax.inject.Inject;
60 | import toothpick.Scope;
61 | import toothpick.smoothie.module.SmoothieActivityModule;
62 | import toothpick.smoothie.module.SmoothieAndroidXActivityModule;
63 |
64 | public class DealDetailsActivity extends AppCompatActivity {
65 |
66 | @BindView(R.id.recycler_view)
67 | RecyclerView recyclerView;
68 |
69 | @BindView(R.id.button_refresh)
70 | Button refreshButton;
71 |
72 | @BindView(R.id.progress)
73 | ProgressBar progressBar;
74 |
75 | @Inject SampleStore store;
76 | @Inject FeatureAnimatorController featureAnimatorController;
77 | @Inject FeatureAdapterItemDecoration featureAdapterItemDecoration;
78 | @Inject FeatureControllerListCreator featureControllerListCreator;
79 |
80 | private Scope scope;
81 |
82 | private final CompositeDisposable compositeDisposable = new CompositeDisposable();
83 |
84 | @Override
85 | protected void onCreate(@Nullable Bundle savedInstanceState) {
86 | scope = openScopes(getApplication(), DealDetailsScopeSingleton.class, this);
87 | scope.installModules(
88 | new SmoothieActivityModule(this),
89 | new SmoothieAndroidXActivityModule(this),
90 | new FeatureAnimatorModule(),
91 | new FeatureItemDecorationModule());
92 | inject(this, scope);
93 | super.onCreate(savedInstanceState);
94 | setContentView(R.layout.activity_with_recycler);
95 | ButterKnife.bind(this);
96 |
97 | List> features =
98 | featureControllerListCreator.getFeatureControllerList();
99 | RxFeaturesAdapter adapter = new RxFeaturesAdapter<>(features);
100 |
101 | recyclerView.setHasFixedSize(true);
102 | recyclerView.setLayoutManager(new FlexboxLayoutManager(this));
103 | recyclerView.setAdapter(adapter);
104 | recyclerView.setItemAnimator(new FeatureAdapterDefaultAnimator(featureAnimatorController));
105 | recyclerView.addItemDecoration(featureAdapterItemDecoration);
106 |
107 | compositeDisposable.add(clicks(refreshButton).subscribe(v -> refreshDeal(), this::logError));
108 |
109 | refreshButton.setOnClickListener(ignored -> refreshDeal());
110 |
111 | // listen for feature events
112 | compositeDisposable.add(
113 | featureEvents(features)
114 | .observeOn(computation())
115 | // Grox and Feature Adapter are different libraries
116 | // to combine the 2, we need a mechanism that, given a feature event,
117 | // we trigger a command. In our sample, and we recommend it as a good practice,
118 | // our Grox commands implement the FeatureEvent interface.
119 | // This is why, the cast below is required
120 | .cast(Command.class)
121 | .flatMap(Command::actions)
122 | .subscribe(store::dispatch, this::logError));
123 |
124 | // propagate states to features
125 | compositeDisposable.add(
126 | states(store)
127 | .subscribeOn(computation())
128 | .to(adapter::updateFeatureItems)
129 | .subscribe(this::logFeatureUpdate, this::logError));
130 |
131 | // listen for new states
132 | compositeDisposable.add(
133 | states(store).observeOn(mainThread()).subscribe(this::reactToNewState, this::logError));
134 |
135 | if (store.getState().deal() == null) {
136 | refreshDeal();
137 | }
138 | }
139 |
140 | @Override
141 | protected void onDestroy() {
142 | compositeDisposable.dispose();
143 | super.onDestroy();
144 | if (isFinishing()) {
145 | closeScope(DealDetailsScopeSingleton.class);
146 | }
147 | closeScope(this);
148 | }
149 |
150 | private void refreshDeal() {
151 | compositeDisposable.add(
152 | new RefreshDealCommand(scope)
153 | .actions()
154 | .subscribeOn(io())
155 | .subscribe(store::dispatch, this::logError));
156 | }
157 |
158 | private void reactToNewState(SampleModel sampleModel) {
159 | // update activity state
160 | switch (sampleModel.state()) {
161 | case STATE_READY:
162 | progressBar.setVisibility(View.GONE);
163 | break;
164 | case STATE_LOADING:
165 | progressBar.setVisibility(View.VISIBLE);
166 | break;
167 | case STATE_ERROR:
168 | progressBar.setVisibility(View.GONE);
169 | Snackbar.make(recyclerView, sampleModel.exceptionText(), Snackbar.LENGTH_LONG).show();
170 | break;
171 | }
172 | }
173 |
174 | private void logFeatureUpdate(List featureUpdate) {
175 | Log.d(getClass().getSimpleName(), featureUpdate.toString());
176 | }
177 |
178 | private void logError(Throwable t) {
179 | Log.e(getClass().getSimpleName(), t.getLocalizedMessage(), t);
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/FeatureAnimatorModule.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample;
17 |
18 | import com.groupon.featureadapter.FeatureAnimatorController;
19 |
20 | import toothpick.config.Module;
21 |
22 | /**
23 | * Provides a shared instance of {@link FeatureAnimatorController} to pass into the RecyclerView
24 | * animator, and to inject into the Features themselves.
25 | */
26 | class FeatureAnimatorModule extends Module {
27 | FeatureAnimatorModule() {
28 | bind(FeatureAnimatorController.class).toInstance(new FeatureAnimatorController());
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/FeatureItemDecorationModule.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample;
17 |
18 | import com.groupon.featureadapter.FeatureAdapterItemDecoration;
19 |
20 | import toothpick.config.Module;
21 |
22 | /**
23 | * Provides a shared instance of {@link FeatureAdapterItemDecoration} to pass to the RecyclerView
24 | * and to inject into the Features themselves.
25 | */
26 | class FeatureItemDecorationModule extends Module {
27 | FeatureItemDecorationModule() {
28 | bind(FeatureAdapterItemDecoration.class).toInstance(new FeatureAdapterItemDecoration());
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/events/RefreshDealCommand.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.events;
17 |
18 | import com.groupon.android.featureadapter.sample.model.Deal;
19 | import com.groupon.android.featureadapter.sample.model.DealApiClient;
20 | import com.groupon.android.featureadapter.sample.model.Option;
21 | import com.groupon.android.featureadapter.sample.state.SampleModel;
22 | import com.groupon.featureadapter.events.FeatureEvent;
23 | import com.groupon.grox.Action;
24 |
25 | import com.groupon.grox.commands.rxjava2.Command;
26 | import io.reactivex.Observable;
27 | import javax.inject.Inject;
28 |
29 | import toothpick.Scope;
30 |
31 | import static com.groupon.android.featureadapter.sample.state.SampleModel.STATE_ERROR;
32 | import static com.groupon.android.featureadapter.sample.state.SampleModel.STATE_LOADING;
33 | import static com.groupon.android.featureadapter.sample.state.SampleModel.STATE_READY;
34 | import static toothpick.Toothpick.inject;
35 |
36 | /**
37 | * Encapsulate the state update logic to refresh a deal from the Api.
38 | * This class implements {@link FeatureEvent} so that it can be launched from features.
39 | * It also implements {@link Command} so that it can be processed in a Grox chain.
40 | */
41 | public class RefreshDealCommand implements Command, FeatureEvent {
42 |
43 | @Inject DealApiClient dealApiClient;
44 |
45 | public RefreshDealCommand(Scope scope) {
46 | inject(this, scope);
47 | }
48 |
49 | @Override
50 | public Observable extends Action> actions() {
51 | return dealApiClient.getDeal()
52 | .map(SuccessAction::new)
53 | .map(action -> (Action) action)
54 | .onErrorReturn(FailedAction::new)
55 | .startWith(new StateLoadingAction());
56 | }
57 |
58 | /**
59 | * The deal is fetched successfully.
60 | */
61 | private static class SuccessAction implements Action {
62 |
63 | private final Deal deal;
64 |
65 | SuccessAction(Deal deal) {
66 | this.deal = deal;
67 | }
68 |
69 | @Override
70 | public SampleModel newState(SampleModel oldState) {
71 | return oldState.toBuilder()
72 | .setDeal(deal)
73 | .setSelectedOption(updateOption(deal, oldState.selectedOption()))
74 | .setState(STATE_READY)
75 | .setExceptionText(null)
76 | .build();
77 | }
78 |
79 | private static Option updateOption(Deal deal, Option previousOption) {
80 | if (previousOption == null) return null;
81 | for (Option option : deal.options) {
82 | if (previousOption.uuid.equals(option.uuid)) return option;
83 | }
84 | throw new IllegalArgumentException("Option does not exist");
85 | }
86 | }
87 |
88 | /**
89 | * There is an exception while fetching the deal.
90 | */
91 | private static class FailedAction implements Action {
92 |
93 | private final Throwable throwable;
94 |
95 | FailedAction(Throwable throwable) {
96 | this.throwable = throwable;
97 | }
98 |
99 | @Override
100 | public SampleModel newState(SampleModel oldState) {
101 | return oldState.toBuilder()
102 | .setState(STATE_ERROR)
103 | .setExceptionText(throwable.getLocalizedMessage())
104 | .build();
105 | }
106 | }
107 |
108 | /**
109 | * Update the progress state when fetching the deal.
110 | */
111 | private static class StateLoadingAction implements Action {
112 |
113 | @Override
114 | public SampleModel newState(SampleModel oldState) {
115 | return oldState.toBuilder()
116 | .setState(STATE_LOADING)
117 | .build();
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/FeatureControllerListCreator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features;
17 |
18 | import com.groupon.android.featureadapter.sample.features.badges.BadgeController;
19 | import com.groupon.android.featureadapter.sample.features.collapsible.CollapsibleController;
20 | import com.groupon.android.featureadapter.sample.features.header.HeaderController;
21 | import com.groupon.android.featureadapter.sample.features.options.OptionsController;
22 | import com.groupon.android.featureadapter.sample.state.SampleModel;
23 | import com.groupon.featureadapter.FeatureController;
24 |
25 | import java.util.List;
26 |
27 | import javax.inject.Inject;
28 |
29 | import static java.util.Arrays.asList;
30 |
31 | public class FeatureControllerListCreator {
32 |
33 | private final List> featureControllers;
34 |
35 | @Inject
36 | public FeatureControllerListCreator(OptionsController optionsController,
37 | CollapsibleController collapsibleController) {
38 | featureControllers = asList(
39 | new HeaderController(),
40 | optionsController,
41 | collapsibleController,
42 | new BadgeController()
43 | );
44 | }
45 |
46 | public List> getFeatureControllerList() {
47 | return featureControllers;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/badges/BadgeAdapterViewTypeDelegate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.badges;
17 |
18 | import androidx.recyclerview.widget.RecyclerView;
19 | import android.view.LayoutInflater;
20 | import android.view.View;
21 | import android.view.ViewGroup;
22 | import android.widget.TextView;
23 |
24 | import com.groupon.android.featureadapter.sample.rx.R;
25 | import com.groupon.featureadapter.AdapterViewTypeDelegate;
26 | import com.groupon.featureadapter.DiffUtilComparator;
27 |
28 | import java.util.List;
29 |
30 | import butterknife.BindView;
31 | import butterknife.ButterKnife;
32 |
33 | class BadgeAdapterViewTypeDelegate extends AdapterViewTypeDelegate {
34 |
35 | private static final int LAYOUT = R.layout.sample_badge;
36 |
37 | @Override
38 | public ViewHolder createViewHolder(ViewGroup parent) {
39 | return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(LAYOUT, parent, false));
40 | }
41 |
42 | @Override
43 | public void bindViewHolder(ViewHolder holder, BadgeModel model) {
44 | holder.badgeText.setText(model.badgeText);
45 | holder.badgeText.setAllCaps(model.isHighlighted);
46 | holder.badgeText.setOnClickListener(v -> fireEvent(new OnBadgeTap(model.badgeText)));
47 | }
48 |
49 | @Override
50 | public void bindViewHolder(ViewHolder holder, BadgeModel model, List payloads) {
51 | if (payloads == null || payloads.isEmpty()) {
52 | bindViewHolder(holder, model);
53 | return;
54 | }
55 | // only highlighted has been modified
56 | holder.badgeText.setAllCaps(model.isHighlighted);
57 | }
58 |
59 | @Override
60 | public void unbindViewHolder(ViewHolder holder) {
61 | // no op
62 | }
63 |
64 | @Override
65 | public DiffUtilComparator createDiffUtilComparator() {
66 | return new DiffUtilComparator() {
67 |
68 | @Override
69 | public boolean areItemsTheSame(BadgeModel oldModel, BadgeModel newModel) {
70 | return oldModel.badgeText.equals(newModel.badgeText);
71 | }
72 |
73 | @Override
74 | public boolean areContentsTheSame(BadgeModel oldModel, BadgeModel newModel) {
75 | return oldModel.isHighlighted == newModel.isHighlighted;
76 | }
77 |
78 | @Override
79 | public Object getChangePayload(BadgeModel oldModel, BadgeModel newModel) {
80 | return "isHighlighted";
81 | }
82 | };
83 | }
84 |
85 | static class ViewHolder extends RecyclerView.ViewHolder {
86 |
87 | @BindView(R.id.badge_text) TextView badgeText;
88 |
89 | ViewHolder(View itemView) {
90 | super(itemView);
91 | ButterKnife.bind(this, itemView);
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/badges/BadgeController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.badges;
17 |
18 | import com.groupon.android.featureadapter.sample.model.Deal;
19 | import com.groupon.android.featureadapter.sample.state.SampleModel;
20 | import com.groupon.featureadapter.AdapterViewTypeDelegate;
21 | import com.groupon.featureadapter.FeatureController;
22 | import com.groupon.featureadapter.ViewItem;
23 |
24 | import java.util.ArrayList;
25 | import java.util.Collection;
26 | import java.util.List;
27 |
28 | import static java.util.Arrays.asList;
29 | import static java.util.Collections.emptyList;
30 | import static java.util.Collections.singletonList;
31 |
32 | public class BadgeController extends FeatureController {
33 |
34 | private final BadgeAdapterViewTypeDelegate badgeDelegate = new BadgeAdapterViewTypeDelegate();
35 | private final BadgeAdapterViewTypeDelegate childBadgeDelegate = new BadgeAdapterViewTypeDelegate();
36 | private final GroupBadgeAdapterViewTypeDelegate groupBadgeDelegate = new GroupBadgeAdapterViewTypeDelegate(singletonList(childBadgeDelegate));
37 |
38 | @Override
39 | public Collection getAdapterViewTypeDelegates() {
40 | return asList(badgeDelegate, groupBadgeDelegate);
41 | }
42 |
43 | @Override
44 | public List buildItems(SampleModel sampleModel) {
45 | Deal deal = sampleModel.deal();
46 | if (deal == null) {
47 | return emptyList();
48 | }
49 | List items = new ArrayList<>();
50 |
51 | /*
52 | The first group of badges utilizes the {@link com.google.android.flexbox.FlexboxLayoutManager}
53 | to layout a wrapping list of badges in line with the rest of the Activity.
54 | */
55 | for (String badge : deal.badges) {
56 | items.add(new ViewItem<>(new BadgeModel(badge, badge.equals(sampleModel.highlightedBadge())), badgeDelegate));
57 | }
58 |
59 | /*
60 | The second group of badges utilizes the {@link GroupAdapterViewTypeDelegate} to nest a group
61 | of badges inside a custom layout. Note that it uses the same {@link BadgeAdapterViewTypeDelegate}
62 | class/ Note the same DiffUtilComparator and fireEvent still work for the child view items.
63 | */
64 | List childItems = new ArrayList<>();
65 | for (String badge : deal.badges) {
66 | childItems.add(new ViewItem<>(new BadgeModel(badge, badge.equals(sampleModel.highlightedBadge())), childBadgeDelegate));
67 | }
68 | items.add(new ViewItem<>(childItems, groupBadgeDelegate));
69 |
70 | return items;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/badges/BadgeModel.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.badges;
17 |
18 | class BadgeModel {
19 | final String badgeText;
20 | final boolean isHighlighted;
21 |
22 | BadgeModel(String badgeText, boolean isHighlighted) {
23 | this.badgeText = badgeText;
24 | this.isHighlighted = isHighlighted;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/badges/GroupBadgeAdapterViewTypeDelegate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.badges;
17 |
18 | import androidx.recyclerview.widget.RecyclerView;
19 | import android.view.LayoutInflater;
20 | import android.view.View;
21 | import android.view.ViewGroup;
22 | import android.widget.LinearLayout;
23 |
24 | import com.groupon.android.featureadapter.sample.rx.R;
25 | import com.groupon.featureadapter.AdapterViewTypeDelegate;
26 | import com.groupon.featureadapter.GroupAdapterViewTypeDelegate;
27 |
28 | import java.util.List;
29 |
30 | import butterknife.BindView;
31 | import butterknife.ButterKnife;
32 |
33 | class GroupBadgeAdapterViewTypeDelegate extends GroupAdapterViewTypeDelegate {
34 |
35 | private static final int LAYOUT = R.layout.sample_badge_group;
36 |
37 | GroupBadgeAdapterViewTypeDelegate(List delegates) {
38 | super(delegates);
39 | }
40 |
41 | @Override
42 | public GroupBadgeViewHolder createViewHolder(ViewGroup parent) {
43 | return new GroupBadgeViewHolder(LayoutInflater.from(parent.getContext()).inflate(LAYOUT, parent, false));
44 | }
45 |
46 | @Override
47 | protected ViewGroup getRootViewGroup(GroupBadgeViewHolder holder) {
48 | return holder.linearLayout;
49 | }
50 |
51 | static class GroupBadgeViewHolder extends RecyclerView.ViewHolder {
52 |
53 | @BindView(R.id.content_layout) LinearLayout linearLayout;
54 |
55 | GroupBadgeViewHolder(View itemView) {
56 | super(itemView);
57 | ButterKnife.bind(this, itemView);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/badges/OnBadgeTap.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.badges;
17 |
18 | import com.groupon.android.featureadapter.sample.state.SampleModel;
19 | import com.groupon.featureadapter.events.FeatureEvent;
20 | import com.groupon.grox.commands.rxjava2.SingleActionCommand;
21 |
22 | class OnBadgeTap extends SingleActionCommand implements FeatureEvent {
23 |
24 | private final String badge;
25 |
26 | OnBadgeTap(String badge) {
27 | this.badge = badge;
28 | }
29 |
30 | @Override
31 | public SampleModel newState(SampleModel oldState) {
32 | String newHighlightedBadge = badge.equals(oldState.highlightedBadge()) ? null : badge;
33 | return oldState
34 | .toBuilder()
35 | .setHighlightedBadge(newHighlightedBadge)
36 | .build();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/collapsible/CollapsibleController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.collapsible;
17 |
18 | import com.groupon.android.featureadapter.sample.state.SampleModel;
19 | import com.groupon.featureadapter.AdapterViewTypeDelegate;
20 | import com.groupon.featureadapter.FeatureAnimatorController;
21 | import com.groupon.featureadapter.FeatureController;
22 | import com.groupon.featureadapter.ViewItem;
23 |
24 | import java.util.Collection;
25 | import java.util.Collections;
26 | import java.util.List;
27 |
28 | import javax.inject.Inject;
29 |
30 | public class CollapsibleController extends FeatureController {
31 |
32 | private static final String TITLE = "Animating Collapsible Feature";
33 |
34 | @Inject FeatureAnimatorController featureAnimatorController;
35 |
36 | private CollapsibleParentAnimatorListener parentAnimatorListener;
37 |
38 | private final CollapsibleParentAdapterViewTypeDelegate parentDelegate = new CollapsibleParentAdapterViewTypeDelegate();
39 |
40 | @Inject
41 | public CollapsibleController() {
42 | }
43 |
44 | @Override
45 | public Collection getAdapterViewTypeDelegates() {
46 | return Collections.singletonList(parentDelegate);
47 | }
48 |
49 | @Override
50 | public List buildItems(SampleModel sampleModel) {
51 | if (sampleModel.deal() == null) {
52 | return Collections.emptyList();
53 | }
54 |
55 | if (parentAnimatorListener == null) {
56 | // register animator
57 | parentAnimatorListener = new CollapsibleParentAnimatorListener();
58 | featureAnimatorController.registerFeatureAnimatorListener(parentAnimatorListener, parentDelegate);
59 | }
60 |
61 | final CollapsibleParentModel parentModel = new CollapsibleParentModel(TITLE, sampleModel.collapsibleFeatureState().isCollapsed);
62 | return Collections.singletonList(new ViewItem<>(parentModel, parentDelegate));
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/collapsible/CollapsibleFeatureState.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.collapsible;
17 |
18 | public class CollapsibleFeatureState {
19 |
20 | public static final CollapsibleFeatureState DEFAULT = new CollapsibleFeatureState(false);
21 |
22 | public final boolean isCollapsed;
23 |
24 | public CollapsibleFeatureState(boolean isCollapsed) {
25 | this.isCollapsed = isCollapsed;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/collapsible/CollapsibleParentAdapterViewTypeDelegate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.collapsible;
17 |
18 | import android.view.LayoutInflater;
19 | import android.view.ViewGroup;
20 |
21 | import com.groupon.android.featureadapter.sample.rx.R;
22 | import com.groupon.featureadapter.AdapterViewTypeDelegate;
23 | import com.groupon.featureadapter.DiffUtilComparator;
24 |
25 | import java.util.List;
26 |
27 | class CollapsibleParentAdapterViewTypeDelegate extends AdapterViewTypeDelegate {
28 |
29 | static final float CARET_ROTATION_EXPANDED = 0f;
30 | static final float CARET_ROTATION_COLLAPSED = 180f;
31 |
32 | private static final int LAYOUT = R.layout.sample_collapsible_parent;
33 |
34 | @Override
35 | public CollapsibleParentViewHolder createViewHolder(ViewGroup parent) {
36 | return new CollapsibleParentViewHolder(LayoutInflater.from(parent.getContext()).inflate(LAYOUT, parent, false));
37 | }
38 |
39 | @Override
40 | public void bindViewHolder(CollapsibleParentViewHolder holder, CollapsibleParentModel model) {
41 | holder.titleText.setText(model.title);
42 | holder.caretImage.setRotation(model.isCollapsed ? CARET_ROTATION_COLLAPSED : CARET_ROTATION_EXPANDED);
43 | holder.itemView.setOnClickListener(v -> fireEvent(new OnCollapsibleParentTap()));
44 | holder.model = model;
45 | }
46 |
47 | @Override
48 | public void bindViewHolder(CollapsibleParentViewHolder holder, CollapsibleParentModel model, List payloads) {
49 | if (payloads == null || payloads.isEmpty()) {
50 | bindViewHolder(holder, model);
51 | }
52 | // This point will be reached if the DiffUtilComparator returns a payload for the collapsed
53 | // value updating. Caret rotation will be handled by the animator
54 | holder.model = model;
55 | }
56 |
57 | @Override
58 | public void unbindViewHolder(CollapsibleParentViewHolder holder) {
59 | // no op
60 | }
61 |
62 | @Override
63 | public DiffUtilComparator createDiffUtilComparator() {
64 | return new CollapsibleParentDiffUtilComparator();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/collapsible/CollapsibleParentAnimatorListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.collapsible;
17 |
18 | import android.animation.Animator;
19 | import android.animation.AnimatorListenerAdapter;
20 | import android.animation.ObjectAnimator;
21 | import androidx.recyclerview.widget.RecyclerView;
22 | import android.widget.ImageView;
23 |
24 | import com.groupon.featureadapter.FeatureAnimatorListener;
25 |
26 | import static android.view.View.ROTATION;
27 | import static com.groupon.android.featureadapter.sample.features.collapsible.CollapsibleParentAdapterViewTypeDelegate.CARET_ROTATION_COLLAPSED;
28 | import static com.groupon.android.featureadapter.sample.features.collapsible.CollapsibleParentAdapterViewTypeDelegate.CARET_ROTATION_EXPANDED;
29 |
30 | class CollapsibleParentAnimatorListener implements FeatureAnimatorListener {
31 |
32 | @Override
33 | public CollapsibleParentItemInfo getPreLayoutInformation(CollapsibleParentViewHolder viewHolder) {
34 | return new CollapsibleParentItemInfo(viewHolder);
35 | }
36 |
37 | @Override
38 | public CollapsibleParentItemInfo getPostLayoutInformation(CollapsibleParentViewHolder viewHolder) {
39 | return new CollapsibleParentItemInfo(viewHolder);
40 | }
41 |
42 | @Override
43 | public Animator setupChangeAnimation(RecyclerView.ItemAnimator itemAnimator, CollapsibleParentViewHolder oldHolder, CollapsibleParentViewHolder newHolder, CollapsibleParentItemInfo preInfo, CollapsibleParentItemInfo postInfo) {
44 | if (preInfo.isCollapsed == postInfo.isCollapsed) {
45 | return null;
46 | }
47 | final float rotationFrom = preInfo.isCollapsed ? CARET_ROTATION_COLLAPSED : CARET_ROTATION_EXPANDED;
48 | final float rotationTo = !preInfo.isCollapsed ? CARET_ROTATION_COLLAPSED : CARET_ROTATION_EXPANDED;
49 | final ObjectAnimator animation = ObjectAnimator.ofFloat(newHolder.caretImage, ROTATION.getName(), rotationFrom, rotationTo);
50 | animation.addListener(new OnAnimationFinishListener(newHolder.caretImage, rotationTo));
51 | return animation;
52 | }
53 |
54 | private static class OnAnimationFinishListener extends AnimatorListenerAdapter {
55 |
56 | private boolean isCancelled;
57 |
58 | private final ImageView caretImage;
59 | private final float rotationTo;
60 |
61 | OnAnimationFinishListener(ImageView caretImage, float rotationTo) {
62 | this.caretImage = caretImage;
63 | this.rotationTo = rotationTo;
64 | }
65 |
66 | @Override
67 | public void onAnimationCancel(Animator animation) {
68 | isCancelled = true;
69 | }
70 |
71 | @Override
72 | public void onAnimationEnd(Animator animation) {
73 | if (!isCancelled) {
74 | caretImage.setRotation(rotationTo);
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/collapsible/CollapsibleParentDiffUtilComparator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.collapsible;
17 |
18 | import com.groupon.featureadapter.DiffUtilComparator;
19 |
20 | class CollapsibleParentDiffUtilComparator implements DiffUtilComparator {
21 |
22 | @Override
23 | public boolean areItemsTheSame(CollapsibleParentModel oldModel, CollapsibleParentModel newModel) {
24 | return true;
25 | }
26 |
27 | @Override
28 | public boolean areContentsTheSame(CollapsibleParentModel oldModel, CollapsibleParentModel newModel) {
29 | return oldModel.isCollapsed == newModel.isCollapsed;
30 | }
31 |
32 | @Override
33 | public Object getChangePayload(CollapsibleParentModel oldModel, CollapsibleParentModel newModel) {
34 | // on reaching this point we know that isCollapsed has changed, so return a payload to
35 | // avoid doing a full bind
36 | return newModel.isCollapsed;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/collapsible/CollapsibleParentItemInfo.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.collapsible;
17 |
18 | import androidx.recyclerview.widget.RecyclerView;
19 |
20 | class CollapsibleParentItemInfo extends RecyclerView.ItemAnimator.ItemHolderInfo {
21 |
22 | final boolean isCollapsed;
23 |
24 | CollapsibleParentItemInfo(CollapsibleParentViewHolder holder) {
25 | this.isCollapsed = holder.model.isCollapsed;
26 | setFrom(holder);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/collapsible/CollapsibleParentModel.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.collapsible;
17 |
18 | class CollapsibleParentModel {
19 | final String title;
20 | final boolean isCollapsed;
21 |
22 | CollapsibleParentModel(String title, boolean isCollapsed) {
23 | this.title = title;
24 | this.isCollapsed = isCollapsed;
25 | }
26 |
27 | CollapsibleParentModel withCollapsed(boolean isCollapsed) {
28 | return new CollapsibleParentModel(title, isCollapsed);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/collapsible/CollapsibleParentViewHolder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.collapsible;
17 |
18 | import androidx.recyclerview.widget.RecyclerView;
19 | import android.view.View;
20 | import android.widget.ImageView;
21 | import android.widget.TextView;
22 |
23 | import com.groupon.android.featureadapter.sample.rx.R;
24 |
25 | import butterknife.BindView;
26 | import butterknife.ButterKnife;
27 |
28 | class CollapsibleParentViewHolder extends RecyclerView.ViewHolder {
29 |
30 | @BindView(R.id.collapsible_title_text) TextView titleText;
31 | @BindView(R.id.collapsible_caret_image) ImageView caretImage;
32 |
33 | CollapsibleParentModel model;
34 |
35 | CollapsibleParentViewHolder(View itemView) {
36 | super(itemView);
37 | ButterKnife.bind(this, itemView);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/collapsible/OnCollapsibleParentTap.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.collapsible;
17 |
18 | import com.groupon.android.featureadapter.sample.state.SampleModel;
19 | import com.groupon.featureadapter.events.FeatureEvent;
20 | import com.groupon.grox.commands.rxjava2.SingleActionCommand;
21 |
22 | class OnCollapsibleParentTap extends SingleActionCommand implements FeatureEvent {
23 |
24 | @Override
25 | public SampleModel newState(SampleModel oldState) {
26 | final boolean newIsCollapsed = !oldState.collapsibleFeatureState().isCollapsed;
27 | final CollapsibleFeatureState newCollapsibleFeatureState = new CollapsibleFeatureState(newIsCollapsed);
28 | return oldState
29 | .toBuilder()
30 | .setCollapsibleFeatureState(newCollapsibleFeatureState)
31 | .build();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/header/HeaderController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.header;
17 |
18 | import com.groupon.android.featureadapter.sample.model.Deal;
19 | import com.groupon.android.featureadapter.sample.model.Option;
20 | import com.groupon.android.featureadapter.sample.state.SampleModel;
21 | import com.groupon.featureadapter.AdapterViewTypeDelegate;
22 | import com.groupon.featureadapter.FeatureController;
23 | import com.groupon.featureadapter.ViewItem;
24 |
25 | import java.util.Collection;
26 | import java.util.List;
27 |
28 | import static java.util.Arrays.asList;
29 | import static java.util.Collections.emptyList;
30 |
31 | public class HeaderController extends FeatureController {
32 |
33 | private final ImageAdapterViewTypeDelegate imageDelegate = new ImageAdapterViewTypeDelegate();
34 | private final TitleAdapterViewTypeDelegate titleDelegate = new TitleAdapterViewTypeDelegate();
35 |
36 | @Override
37 | public Collection getAdapterViewTypeDelegates() {
38 | return asList(imageDelegate, titleDelegate);
39 | }
40 |
41 | @Override
42 | public List buildItems(SampleModel sampleModel) {
43 | Deal deal = sampleModel.deal();
44 | if (deal == null) {
45 | return emptyList();
46 | }
47 | return asList(
48 | new ViewItem<>(resolveImageUrl(sampleModel.selectedOption(), deal), imageDelegate),
49 | new ViewItem<>(resolveTitle(sampleModel.selectedOption(), deal), titleDelegate)
50 | );
51 | }
52 |
53 | private Integer resolveImageUrl(Option selectedOption, Deal deal) {
54 | return selectedOption != null ? selectedOption.imageId : deal.imageId;
55 | }
56 |
57 | private String resolveTitle(Option selectedOption, Deal deal) {
58 | return selectedOption != null ? selectedOption.title : deal.title;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/header/ImageAdapterViewTypeDelegate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.header;
17 |
18 | import androidx.recyclerview.widget.RecyclerView;
19 | import android.view.LayoutInflater;
20 | import android.view.View;
21 | import android.view.ViewGroup;
22 | import android.widget.ImageView;
23 |
24 | import com.groupon.android.featureadapter.sample.rx.R;
25 | import com.groupon.featureadapter.AdapterViewTypeDelegate;
26 |
27 | import butterknife.BindView;
28 | import butterknife.ButterKnife;
29 |
30 | class ImageAdapterViewTypeDelegate extends AdapterViewTypeDelegate {
31 |
32 | private static final int LAYOUT = R.layout.sample_header_image;
33 |
34 | @Override
35 | public ImageViewHolder createViewHolder(ViewGroup viewGroup) {
36 | return new ImageViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(LAYOUT, viewGroup, false));
37 | }
38 |
39 | @Override
40 | public void bindViewHolder(ImageViewHolder holder, Integer imageId) {
41 | holder.headerImage.setImageResource(imageId);
42 | }
43 |
44 | @Override
45 | public void unbindViewHolder(ImageViewHolder holder) {
46 | // do nothing
47 | }
48 |
49 | static class ImageViewHolder extends RecyclerView.ViewHolder {
50 |
51 | @BindView(R.id.header_image) ImageView headerImage;
52 |
53 | ImageViewHolder(View itemView) {
54 | super(itemView);
55 | ButterKnife.bind(this, itemView);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/header/TitleAdapterViewTypeDelegate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.header;
17 |
18 | import androidx.recyclerview.widget.RecyclerView;
19 | import android.view.LayoutInflater;
20 | import android.view.View;
21 | import android.view.ViewGroup;
22 | import android.widget.TextView;
23 |
24 | import com.groupon.android.featureadapter.sample.rx.R;
25 | import com.groupon.featureadapter.AdapterViewTypeDelegate;
26 |
27 | import butterknife.BindView;
28 | import butterknife.ButterKnife;
29 |
30 | class TitleAdapterViewTypeDelegate extends AdapterViewTypeDelegate {
31 |
32 | private static final int LAYOUT = R.layout.sample_header_title;
33 |
34 | @Override
35 | public TitleViewHolder createViewHolder(ViewGroup viewGroup) {
36 | return new TitleViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(LAYOUT, viewGroup, false));
37 | }
38 |
39 | @Override
40 | public void bindViewHolder(TitleViewHolder holder, String s) {
41 | holder.titleText.setText(s);
42 | }
43 |
44 | @Override
45 | public void unbindViewHolder(TitleViewHolder holder) {
46 | // no op
47 | }
48 |
49 | static class TitleViewHolder extends RecyclerView.ViewHolder {
50 |
51 | @BindView(R.id.header_title_text) TextView titleText;
52 |
53 | TitleViewHolder(View itemView) {
54 | super(itemView);
55 | ButterKnife.bind(this, itemView);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/options/OnOptionClickEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.options;
17 |
18 | import com.groupon.android.featureadapter.sample.model.Deal;
19 | import com.groupon.android.featureadapter.sample.model.Option;
20 | import com.groupon.android.featureadapter.sample.state.SampleModel;
21 | import com.groupon.featureadapter.events.FeatureEvent;
22 | import com.groupon.grox.commands.rxjava2.Command;
23 | import com.groupon.grox.commands.rxjava2.SingleActionCommand;
24 |
25 | class OnOptionClickEvent extends SingleActionCommand implements FeatureEvent {
26 |
27 | private final String uuid;
28 |
29 | OnOptionClickEvent(String uuid) {
30 | this.uuid = uuid;
31 | }
32 |
33 | @Override
34 | public SampleModel newState(SampleModel model) {
35 | Option newOption = findOption(model.deal(), uuid);
36 | return model.toBuilder()
37 | .setSelectedOption(newOption != model.selectedOption() ? newOption : null)
38 | .build();
39 | }
40 |
41 | private static Option findOption(Deal deal, String uuid) {
42 | for (Option option : deal.options) {
43 | if (uuid.equals(option.uuid)) return option;
44 | }
45 | throw new IllegalArgumentException("Option does not exist");
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/options/OptionsAdapterViewTypeDelegate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.options;
17 |
18 | import androidx.recyclerview.widget.RecyclerView;
19 | import android.view.LayoutInflater;
20 | import android.view.View;
21 | import android.view.ViewGroup;
22 | import android.widget.TextView;
23 |
24 | import com.groupon.android.featureadapter.sample.rx.R;
25 | import com.groupon.featureadapter.AdapterViewTypeDelegate;
26 |
27 | import butterknife.BindView;
28 | import butterknife.ButterKnife;
29 |
30 | class OptionsAdapterViewTypeDelegate extends AdapterViewTypeDelegate {
31 |
32 | private static final int LAYOUT = R.layout.sample_option;
33 |
34 | @Override
35 | public OptionsViewHolder createViewHolder(ViewGroup viewGroup) {
36 | return new OptionsViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(LAYOUT, viewGroup, false));
37 | }
38 |
39 | @Override
40 | public void bindViewHolder(OptionsViewHolder holder, OptionsModel optionsModel) {
41 | holder.titleText.setText(optionsModel.title());
42 | holder.titleText.setAllCaps(optionsModel.selected());
43 | holder.priceText.setText(optionsModel.price());
44 | holder.itemView.setOnClickListener(ignored -> fireEvent(new OnOptionClickEvent(optionsModel.uuid())));
45 | }
46 |
47 | @Override
48 | public void unbindViewHolder(OptionsViewHolder holder) {
49 | // no op
50 | }
51 |
52 | static class OptionsViewHolder extends RecyclerView.ViewHolder {
53 |
54 | @BindView(R.id.option_title_text) TextView titleText;
55 | @BindView(R.id.option_price_text) TextView priceText;
56 |
57 | OptionsViewHolder(View itemView) {
58 | super(itemView);
59 | ButterKnife.bind(this, itemView);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/options/OptionsController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.options;
17 |
18 | import android.app.Activity;
19 |
20 | import com.groupon.android.featureadapter.sample.model.Deal;
21 | import com.groupon.android.featureadapter.sample.model.Option;
22 | import com.groupon.android.featureadapter.sample.state.SampleModel;
23 | import com.groupon.featureadapter.AdapterViewTypeDelegate;
24 | import com.groupon.featureadapter.FeatureAdapterItemDecoration;
25 | import com.groupon.featureadapter.FeatureController;
26 | import com.groupon.featureadapter.ViewItem;
27 |
28 | import java.util.ArrayList;
29 | import java.util.Collection;
30 | import java.util.List;
31 |
32 | import javax.inject.Inject;
33 |
34 | import static java.util.Collections.emptyList;
35 | import static java.util.Collections.singletonList;
36 |
37 | public class OptionsController extends FeatureController {
38 |
39 | private final OptionsAdapterViewTypeDelegate optionsDelegate = new OptionsAdapterViewTypeDelegate();
40 |
41 | @Inject Activity activity;
42 | @Inject FeatureAdapterItemDecoration featureAdapterItemDecoration;
43 |
44 | private OptionsItemDecoration decoration;
45 |
46 | @Override
47 | public Collection getAdapterViewTypeDelegates() {
48 | return singletonList(optionsDelegate);
49 | }
50 |
51 | @Override
52 | public List buildItems(SampleModel sampleModel) {
53 | Deal deal = sampleModel.deal();
54 | if (deal == null) {
55 | return emptyList();
56 | }
57 |
58 | if (decoration == null) {
59 | decoration = new OptionsItemDecoration(activity);
60 | featureAdapterItemDecoration.registerFeatureDecoration(decoration, optionsDelegate);
61 | }
62 |
63 | List items = new ArrayList<>(deal.options.size());
64 | for (Option option : deal.options) {
65 | items.add(new ViewItem<>(fromOption(option, sampleModel.selectedOption()), optionsDelegate));
66 | }
67 | return items;
68 | }
69 |
70 | private static OptionsModel fromOption(Option option, Option selectedOption) {
71 | return OptionsModel.builder()
72 | .setUuid(option.uuid)
73 | .setTitle(option.title)
74 | .setPrice(option.price)
75 | .setSelected(option == selectedOption)
76 | .build();
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/options/OptionsItemDecoration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.options;
17 |
18 | import android.content.Context;
19 | import android.graphics.Canvas;
20 | import android.graphics.Rect;
21 | import android.graphics.drawable.Drawable;
22 | import androidx.core.content.ContextCompat;
23 | import androidx.recyclerview.widget.RecyclerView;
24 | import android.view.View;
25 |
26 | import com.groupon.android.featureadapter.sample.rx.R;
27 | import com.groupon.featureadapter.FeatureItemDecoration;
28 |
29 | class OptionsItemDecoration implements FeatureItemDecoration {
30 |
31 | private final Drawable divider;
32 | private final Rect tempBounds = new Rect();
33 |
34 | OptionsItemDecoration(Context context) {
35 | this.divider = ContextCompat.getDrawable(context, R.drawable.divider);
36 | }
37 |
38 | @Override
39 | public void getItemOffsetsImpl(Rect outRect, View view, RecyclerView.ViewHolder holder, RecyclerView parent, RecyclerView.State state) {
40 | outRect.bottom = divider.getIntrinsicHeight();
41 | }
42 |
43 | @Override
44 | public void onDrawViewImpl(Canvas canvas, View view, RecyclerView.ViewHolder holder, RecyclerView parent, RecyclerView.State state) {
45 | canvas.save();
46 | parent.getDecoratedBoundsWithMargins(view, tempBounds);
47 | divider.setBounds(tempBounds.left, tempBounds.bottom - divider.getIntrinsicHeight(), tempBounds.right, tempBounds.bottom);
48 | divider.draw(canvas);
49 | canvas.restore();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/features/options/OptionsModel.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.features.options;
17 |
18 | import com.google.auto.value.AutoValue;
19 |
20 | /**
21 | * It is not mandatory to use AutoValue to create the feature (small) models, we recommend it
22 | * as it generates valid equals and hashcode methods, and enforces immutable models.
23 | * (which are required)
24 | */
25 | @AutoValue
26 | abstract class OptionsModel {
27 | abstract String uuid();
28 | abstract String title();
29 | abstract String price();
30 | abstract boolean selected();
31 |
32 | abstract Builder toBuilder();
33 |
34 | static Builder builder() {
35 | return new AutoValue_OptionsModel.Builder();
36 | }
37 |
38 | @AutoValue.Builder
39 | static abstract class Builder {
40 | abstract Builder setUuid(String uuid);
41 | abstract Builder setTitle(String title);
42 | abstract Builder setPrice(String price);
43 | abstract Builder setSelected(boolean selected);
44 | abstract OptionsModel build();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/model/Deal.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.model;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 | public class Deal {
22 | public String title;
23 | public List options = new ArrayList<>();
24 | public int imageId;
25 | public List badges = new ArrayList<>();
26 | }
27 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/model/DealApiClient.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.model;
17 |
18 | import com.groupon.android.featureadapter.sample.rx.R;
19 | import io.reactivex.Observable;
20 | import java.util.Arrays;
21 | import java.util.concurrent.TimeUnit;
22 | import javax.inject.Inject;
23 |
24 | import static io.reactivex.Observable.fromCallable;
25 |
26 | /**
27 | * Mimics an Api request to fetch a {@link Deal} from the network.
28 | */
29 | public class DealApiClient {
30 |
31 | @Inject
32 | public DealApiClient() {}
33 |
34 | public Observable getDeal() {
35 | return fromCallable(this::createDeal)
36 | .delay(1, TimeUnit.SECONDS);
37 | }
38 |
39 | private Deal createDeal() {
40 | Deal deal = new Deal();
41 | deal.title = "Arrow";
42 | deal.imageId = R.drawable.ic_keyboard_arrow_down_black_24px;
43 |
44 | Option left = new Option();
45 | left.uuid = "left";
46 | left.title = "Arrow left";
47 | left.imageId = R.drawable.ic_keyboard_arrow_left_black_24px;
48 | left.price = "$111.11";
49 | deal.options.add(left);
50 |
51 | Option up = new Option();
52 | up.uuid = "up";
53 | up.title = "Arrow up";
54 | up.imageId = R.drawable.ic_keyboard_arrow_up_black_24px;
55 | up.price = "$333.33";
56 | deal.options.add(up);
57 |
58 | Option right = new Option();
59 | right.uuid = "right";
60 | right.title = "Arrow right";
61 | right.imageId = R.drawable.ic_keyboard_arrow_right_black_24px;
62 | right.price = "$222.22";
63 | deal.options.add(right);
64 |
65 |
66 |
67 | deal.badges.addAll(Arrays.asList("Zero", "One", "Two", "Three", "Four", "Five", "Six"));
68 |
69 | return deal;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/model/Option.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.model;
17 |
18 | public class Option {
19 | public String uuid;
20 | public String title;
21 | public int imageId;
22 | public String price;
23 | }
24 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/state/DealDetailsScopeSingleton.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.state;
17 |
18 | import java.lang.annotation.Retention;
19 |
20 | import javax.inject.Scope;
21 |
22 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
23 |
24 | @Scope
25 | @Retention(RUNTIME)
26 | public @interface DealDetailsScopeSingleton {
27 | }
28 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/state/SampleModel.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.state;
17 |
18 | import androidx.annotation.Nullable;
19 | import androidx.annotation.StringDef;
20 |
21 | import com.google.auto.value.AutoValue;
22 | import com.groupon.android.featureadapter.sample.features.collapsible.CollapsibleFeatureState;
23 | import com.groupon.android.featureadapter.sample.model.Deal;
24 | import com.groupon.android.featureadapter.sample.model.Option;
25 |
26 | import java.lang.annotation.Retention;
27 |
28 | import static java.lang.annotation.RetentionPolicy.SOURCE;
29 |
30 | @AutoValue
31 | public abstract class SampleModel {
32 |
33 | public static final String STATE_READY = "STATE_READY";
34 | public static final String STATE_LOADING = "STATE_LOADING";
35 | public static final String STATE_ERROR = "STATE_ERROR";
36 |
37 | @Retention(SOURCE)
38 | @StringDef({
39 | STATE_LOADING,
40 | STATE_READY,
41 | STATE_ERROR
42 | })
43 | public @interface State {}
44 |
45 | @Nullable
46 | public abstract Deal deal();
47 |
48 | @Nullable
49 | public abstract Option selectedOption();
50 |
51 | @State
52 | public abstract String state();
53 |
54 | @Nullable
55 | public abstract String exceptionText();
56 |
57 | public abstract CollapsibleFeatureState collapsibleFeatureState();
58 |
59 | @Nullable
60 | public abstract String highlightedBadge();
61 |
62 | public abstract Builder toBuilder();
63 |
64 | public static Builder builder() {
65 | return new AutoValue_SampleModel.Builder()
66 | .setState(STATE_READY)
67 | .setCollapsibleFeatureState(CollapsibleFeatureState.DEFAULT);
68 | }
69 |
70 | @AutoValue.Builder
71 | public static abstract class Builder {
72 | public abstract Builder setDeal(Deal deal);
73 |
74 | public abstract Builder setSelectedOption(Option option);
75 |
76 | public abstract Builder setState(@State String state);
77 |
78 | public abstract Builder setExceptionText(String exceptionText);
79 |
80 | public abstract Builder setCollapsibleFeatureState(CollapsibleFeatureState collapsibleFeatureState);
81 |
82 | public abstract Builder setHighlightedBadge(@Nullable String highlightedBadge);
83 |
84 | public abstract SampleModel build();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/java/com/groupon/android/featureadapter/sample/state/SampleStore.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.android.featureadapter.sample.state;
17 |
18 | import com.groupon.grox.Store;
19 |
20 | import javax.inject.Inject;
21 |
22 | @DealDetailsScopeSingleton
23 | public class SampleStore extends Store {
24 |
25 | @Inject
26 | public SampleStore() {
27 | super(SampleModel.builder().build());
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/drawable/divider.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/drawable/ic_caret_up_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/drawable/ic_keyboard_arrow_down_black_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/drawable/ic_keyboard_arrow_left_black_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/drawable/ic_keyboard_arrow_right_black_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/drawable/ic_keyboard_arrow_up_black_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/drawable/ic_keyboard_capslock_black_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/layout/activity_with_recycler.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
13 |
18 |
19 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/layout/sample_badge.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/layout/sample_badge_group.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/layout/sample_collapsible_parent.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/layout/sample_header_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/layout/sample_header_title.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/layout/sample_option.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/groupon/FeatureAdapter/c08586e7d2669f3cca409a6813ab1e2260db9c9a/feature-adapter-sample-rx/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/groupon/FeatureAdapter/c08586e7d2669f3cca409a6813ab1e2260db9c9a/feature-adapter-sample-rx/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/groupon/FeatureAdapter/c08586e7d2669f3cca409a6813ab1e2260db9c9a/feature-adapter-sample-rx/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/groupon/FeatureAdapter/c08586e7d2669f3cca409a6813ab1e2260db9c9a/feature-adapter-sample-rx/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/groupon/FeatureAdapter/c08586e7d2669f3cca409a6813ab1e2260db9c9a/feature-adapter-sample-rx/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/groupon/FeatureAdapter/c08586e7d2669f3cca409a6813ab1e2260db9c9a/feature-adapter-sample-rx/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/groupon/FeatureAdapter/c08586e7d2669f3cca409a6813ab1e2260db9c9a/feature-adapter-sample-rx/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/groupon/FeatureAdapter/c08586e7d2669f3cca409a6813ab1e2260db9c9a/feature-adapter-sample-rx/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/groupon/FeatureAdapter/c08586e7d2669f3cca409a6813ab1e2260db9c9a/feature-adapter-sample-rx/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/groupon/FeatureAdapter/c08586e7d2669f3cca409a6813ab1e2260db9c9a/feature-adapter-sample-rx/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #53A318
4 | #4b9216
5 | #a0db46
6 |
7 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | FeatureAdapter Sample Rx
3 | Error
4 |
5 |
--------------------------------------------------------------------------------
/feature-adapter-sample-rx/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/feature-adapter/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.dicedmelon.gradle:jacoco-android:0.1.4'
9 | }
10 | }
11 |
12 | apply plugin: 'com.android.library'
13 | apply from: rootProject.file("${quality_gradle_android_file}")
14 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
15 | apply plugin: 'jacoco-android'
16 |
17 | android {
18 | compileSdkVersion versions.compileSdk
19 | buildToolsVersion versions.buildTools
20 | defaultConfig {
21 | minSdkVersion versions.minSdk
22 | }
23 |
24 | compileOptions {
25 | sourceCompatibility JavaVersion.VERSION_1_8
26 | targetCompatibility JavaVersion.VERSION_1_8
27 | }
28 |
29 | testOptions {
30 | unitTests.returnDefaultValues = true
31 | }
32 | }
33 |
34 | dependencies {
35 | implementation deps.findbugs
36 | compileOnly deps.javaxannotation
37 |
38 | api deps.support.compat
39 | api deps.support.design
40 | api deps.support.recyclerview
41 |
42 | testImplementation deps.junit
43 | testImplementation deps.easymock
44 | }
45 |
--------------------------------------------------------------------------------
/feature-adapter/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=feature-adapter
2 | POM_NAME=Feature adapter
3 | POM_DESCRIPTION='non Rx main artifact for FeatureAdapter'
4 | POM_PACKAGING='jar'
--------------------------------------------------------------------------------
/feature-adapter/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
--------------------------------------------------------------------------------
/feature-adapter/src/main/java/com/groupon/featureadapter/AdapterViewTypeDelegate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | import static androidx.recyclerview.widget.RecyclerView.Adapter;
19 |
20 | import androidx.recyclerview.widget.DiffUtil;
21 | import androidx.recyclerview.widget.RecyclerView;
22 | import android.view.ViewGroup;
23 | import com.groupon.featureadapter.events.FeatureEvent;
24 | import com.groupon.featureadapter.events.FeatureEventListener;
25 | import com.groupon.featureadapter.events.FeatureEventSource;
26 | import java.util.List;
27 |
28 | /**
29 | * A delegate of the @link {@link FeaturesAdapter} for a given view type. This entity is responsible
30 | * for:
31 | *
32 | *
33 | * Creating the views of a given view type.
34 | * Recycling the views and re-bind them to a new model.
35 | * Providing a few hooks related to the life cycle of the views it is in charge of.
36 | *
37 | *
38 | * @param
39 | * @param the view model of this {@link AdapterViewTypeDelegate}. It is the same class as
40 | * the {@link ViewItem}'s model class. It is an output model class of a {@link
41 | * FeatureController}.
42 | */
43 | public abstract class AdapterViewTypeDelegate {
44 |
45 | private int viewType = RecyclerView.INVALID_TYPE;
46 | private final FeatureEventSource featureEventSource = new FeatureEventSource();
47 |
48 | void setViewType(int viewType) {
49 | this.viewType = viewType;
50 | }
51 |
52 | int getViewType() {
53 | return viewType;
54 | }
55 |
56 | /**
57 | * @return a {@link DiffUtilComparator} that will be used internally to animate the updates to the
58 | * list of items associated to this feature controller. By default, it returns a {@link
59 | * DefaultDiffUtilComparator}.
60 | * @see DiffUtil
61 | */
62 | public DiffUtilComparator createDiffUtilComparator() {
63 | return new DefaultDiffUtilComparator<>();
64 | }
65 |
66 | /**
67 | * Called by the Adapter from {@link Adapter#onCreateViewHolder(ViewGroup, int)}
68 | *
69 | * @param parent The ViewGroup into which the new View will be added after it is bound to an
70 | * adapter position.
71 | * @return A new ViewHolder that holds a View of the given view type.
72 | */
73 | public abstract HOLDER createViewHolder(ViewGroup parent);
74 |
75 | /**
76 | * Called by the Adapter from {@link Adapter#onBindViewHolder(RecyclerView.ViewHolder, int)} Maps
77 | * the data back to the view.
78 | *
79 | * @param holder The ViewHolder which should be updated to represent the contents of the item at
80 | * the given position in the data set.
81 | * @param model The Model containing all the data for the ViewHolder to bind to
82 | */
83 | public abstract void bindViewHolder(HOLDER holder, MODEL model);
84 |
85 | /**
86 | * Called by the Adapter from {@link Adapter#onBindViewHolder(RecyclerView.ViewHolder, int,
87 | * List)}. Maps the data back to the view, using a non empty PayLoad.
88 | *
89 | * The default implementation discards the {@code payloads} information and just calls {@link
90 | * #bindViewHolder(RecyclerView.ViewHolder, MODEL)}. Override, if you want to implement more
91 | * fine-grained, payload based updates.
92 | *
93 | * @param holder The ViewHolder which should be updated to represent the contents of the item at
94 | * the given position in the data set.
95 | * @param model The Model containing all the data for the ViewHolder to bind to.
96 | * @param payloads the payload of changes. An empty list means the view holder needs full re-bind.
97 | */
98 | public void bindViewHolder(HOLDER holder, MODEL model, List payloads) {
99 | bindViewHolder(holder, model);
100 | }
101 |
102 | /**
103 | * Called by the Adapter from {@link Adapter#onViewRecycled(RecyclerView.ViewHolder)}
104 | *
105 | * @param holder The ViewHolder for the view being recycled
106 | */
107 | public abstract void unbindViewHolder(HOLDER holder);
108 |
109 | /**
110 | * Called by the Adapter from {@link Adapter#onViewAttachedToWindow(RecyclerView.ViewHolder)} A
111 | * good place to register NST impressions
112 | *
113 | * @param holder Holder of the view being attached
114 | */
115 | public void onAttachToWindow(HOLDER holder) {}
116 |
117 | /**
118 | * Called by the Adapter from {@link Adapter#onViewDetachedFromWindow(RecyclerView.ViewHolder)}
119 | *
120 | * @param holder Holder of the view being detached
121 | */
122 | public void onDetachToWindow(HOLDER holder) {}
123 |
124 | /**
125 | * Adds a listener to the list of listeners.
126 | *
127 | * @param featureEventListener the listener to be added.
128 | */
129 | protected void addFeatureEventListener(FeatureEventListener featureEventListener) {
130 | featureEventSource.addFeatureEventListener(featureEventListener);
131 | }
132 |
133 | /**
134 | * Removes a listener to the list of listeners.
135 | *
136 | * @param featureEventListener the listener to be removed.
137 | */
138 | protected void removeFeatureEventListener(FeatureEventListener featureEventListener) {
139 | featureEventSource.removeFeatureEventListener(featureEventListener);
140 | }
141 |
142 | /**
143 | * Fires an {@link FeatureEvent} to all listeners.
144 | *
145 | * @param featureEvent the event to be passed to all listeners.
146 | */
147 | protected void fireEvent(FeatureEvent featureEvent) {
148 | featureEventSource.fireEvent(featureEvent);
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/feature-adapter/src/main/java/com/groupon/featureadapter/DefaultDiffUtilComparator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | /**
19 | * Default implementation of {@link DiffUtilComparator}. It considers that items at the same
20 | * position are the same (basically items are place holders), but can have changed between the new
21 | * list and the old list.
22 | *
23 | * It assumes that the {@code MODEL} class instances are not gonna be reused. They can be
24 | * immutable or not, this is left to the developers. But the instances should change every time a
25 | * feature's items are rebuilt.
26 | *
27 | *
A note on the API:
28 | *
29 | * @param
30 | */
31 | public final class DefaultDiffUtilComparator implements DiffUtilComparator {
32 |
33 | /**
34 | * @param oldModel the item in the old list.
35 | * @param newModel the item in the new list.
36 | * @return true. Basically items are place holders.
37 | */
38 | @Override
39 | public boolean areItemsTheSame(MODEL oldModel, MODEL newModel) {
40 | return true;
41 | }
42 |
43 | /**
44 | * @param oldModel the item in the old list.
45 | * @param newModel the item in the new list.
46 | * @return true if {@code oldModel == newModel} and false otherwise.
47 | */
48 | @Override
49 | public boolean areContentsTheSame(MODEL oldModel, MODEL newModel) {
50 | return oldModel == newModel;
51 | }
52 |
53 | /**
54 | * @param oldModel the item in the old list.
55 | * @param newModel the item in the new list.
56 | * @return default to null
57 | */
58 | @Override
59 | public Object getChangePayload(MODEL oldModel, MODEL newModel) {
60 | return null;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/feature-adapter/src/main/java/com/groupon/featureadapter/DiffUtilCallbackImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | import androidx.annotation.Nullable;
19 | import androidx.recyclerview.widget.DiffUtil;
20 |
21 | import java.util.List;
22 | import java.util.Map;
23 |
24 | /**
25 | * Callback of DiffUtil to compare items. It uses the {@link FeatureController}s' {@link
26 | * DiffUtilComparator} to do so.
27 | */
28 | class DiffUtilCallbackImpl extends DiffUtil.Callback {
29 |
30 | private final Map mapViewTypeToItemComparator;
31 | private final List extends ViewItem> oldList;
32 | private final List extends ViewItem> newList;
33 |
34 | DiffUtilCallbackImpl(
35 | Map mapViewTypeToItemComparator,
36 | List extends ViewItem> oldList,
37 | List extends ViewItem> newList) {
38 | this.mapViewTypeToItemComparator = mapViewTypeToItemComparator;
39 | this.oldList = oldList;
40 | this.newList = newList;
41 | }
42 |
43 | @Override
44 | public int getOldListSize() {
45 | return oldList.size();
46 | }
47 |
48 | @Override
49 | public int getNewListSize() {
50 | return newList.size();
51 | }
52 |
53 | @Override
54 | public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
55 | final ViewItem oldItem = oldList.get(oldItemPosition);
56 | final ViewItem newItem = newList.get(newItemPosition);
57 | // noinspection unchecked
58 | return oldItem.viewType == newItem.viewType
59 | && mapViewTypeToItemComparator
60 | .get(oldItem.viewType)
61 | .areItemsTheSame(oldItem.model, newItem.model);
62 | }
63 |
64 | @Override
65 | public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
66 | final ViewItem oldItem = oldList.get(oldItemPosition);
67 | final ViewItem newItem = newList.get(newItemPosition);
68 | // noinspection unchecked
69 | return mapViewTypeToItemComparator
70 | .get(oldItem.viewType)
71 | .areContentsTheSame(oldItem.model, newItem.model);
72 | }
73 |
74 | @Nullable
75 | @Override
76 | public Object getChangePayload(int oldItemPosition, int newItemPosition) {
77 | final ViewItem oldItem = oldList.get(oldItemPosition);
78 | final ViewItem newItem = newList.get(newItemPosition);
79 | // noinspection unchecked
80 | return mapViewTypeToItemComparator
81 | .get(oldItem.viewType)
82 | .getChangePayload(oldItem.model, newItem.model);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/feature-adapter/src/main/java/com/groupon/featureadapter/DiffUtilComparator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | /**
19 | * Compares item of a given model class. Classes implementing this interface will be used internally
20 | * with {@link androidx.recyclerview.widget.DiffUtil.Callback} to compare items in the recycler view.
21 | * When an adapter updates the list of items for a given feature controller, items of the old list
22 | * and of the new list will be compared. Depending on the result of this comparison, items will be
23 | * animated in the underlying recycler view in different ways (additions, deletions, modifications).
24 | *
25 | * Feature developers will have to extend this class as soon as their feature requires a non
26 | * trivial comparison for diff util. This trivial comparison is provided by {@link
27 | * DefaultDiffUtilComparator}. A comparator is in charge of all aspects related to comparing the
28 | * items of a feature.
29 | *
30 | *
In some cases, some features which are very computation intensive might want to avoid building
31 | * their feature items list as a whole, which will save a lot of computation (the creation of the
32 | * list of feature items and their comparison). In this case, please check {@link FeatureController}
33 | * to see how to achieve this optimisation.
34 | *
35 | * @param the class of the items to compare.
36 | * @see androidx.recyclerview.widget.DiffUtil.Callback
37 | * @see FeaturesAdapter#dispatchFeatureUpdate(FeatureUpdate)
38 | * @see FeatureController
39 | */
40 | public interface DiffUtilComparator {
41 |
42 | /** @see androidx.recyclerview.widget.DiffUtil.Callback#areItemsTheSame(int, int) */
43 | boolean areItemsTheSame(MODEL oldModel, MODEL newModel);
44 |
45 | /** @see androidx.recyclerview.widget.DiffUtil.Callback#areContentsTheSame(int, int) */
46 | boolean areContentsTheSame(MODEL oldModel, MODEL newModel);
47 |
48 | /** @see androidx.recyclerview.widget.DiffUtil.Callback#getChangePayload(int, int) */
49 | Object getChangePayload(MODEL oldModel, MODEL newModel);
50 | }
51 |
--------------------------------------------------------------------------------
/feature-adapter/src/main/java/com/groupon/featureadapter/FeatureAdapterDefaultAnimator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | import androidx.annotation.NonNull;
19 | import androidx.recyclerview.widget.DefaultItemAnimator;
20 | import androidx.recyclerview.widget.RecyclerView;
21 |
22 | import java.util.List;
23 |
24 | /**
25 | * Default implementation that takes and utilizes a {@link FeatureAnimatorController} to allow
26 | * Features to register and run their own Animations with a similar pattern to
27 | * {@link RecyclerView.ItemAnimator}.
28 | */
29 | public final class FeatureAdapterDefaultAnimator extends DefaultItemAnimator {
30 |
31 | private final FeatureAnimatorController featureAnimatorController;
32 |
33 | public FeatureAdapterDefaultAnimator(FeatureAnimatorController featureAnimatorController) {
34 | this.featureAnimatorController = featureAnimatorController;
35 | }
36 |
37 | @Override
38 | public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
39 | return true;
40 | }
41 |
42 | @NonNull
43 | @Override
44 | public ItemHolderInfo recordPreLayoutInformation(@NonNull RecyclerView.State state, @NonNull RecyclerView.ViewHolder viewHolder, int changeFlags, @NonNull List payloads) {
45 | final ItemHolderInfo info = featureAnimatorController.recordPreLayoutInformation(viewHolder);
46 | return info != null
47 | ? info
48 | : super.recordPreLayoutInformation(state, viewHolder, changeFlags, payloads);
49 | }
50 |
51 | @NonNull
52 | @Override
53 | public ItemHolderInfo recordPostLayoutInformation(@NonNull RecyclerView.State state, @NonNull RecyclerView.ViewHolder viewHolder) {
54 | final ItemHolderInfo info = featureAnimatorController.recordPostLayoutInformation(viewHolder);
55 | return info != null
56 | ? info
57 | : super.recordPostLayoutInformation(state, viewHolder);
58 | }
59 |
60 | @Override
61 | public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
62 | return featureAnimatorController.animateChange(this, oldHolder, newHolder, preInfo, postInfo) ||
63 | super.animateChange(oldHolder, newHolder, preInfo, postInfo);
64 | }
65 |
66 | @Override
67 | public void runPendingAnimations() {
68 | super.runPendingAnimations();
69 | featureAnimatorController.runPendingAnimations();
70 | }
71 |
72 | @Override
73 | public boolean isRunning() {
74 | return super.isRunning() || featureAnimatorController.isRunning();
75 | }
76 |
77 | @Override
78 | public void endAnimation(RecyclerView.ViewHolder item) {
79 | super.endAnimation(item);
80 | featureAnimatorController.endAnimation(item);
81 | }
82 |
83 | @Override
84 | public void endAnimations() {
85 | super.endAnimations();
86 | featureAnimatorController.endAnimations();
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/feature-adapter/src/main/java/com/groupon/featureadapter/FeatureAdapterItemDecoration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | import android.graphics.Canvas;
19 | import android.graphics.Rect;
20 | import androidx.recyclerview.widget.RecyclerView;
21 | import android.util.SparseArray;
22 | import android.view.View;
23 |
24 | /**
25 | * Simple ItemDecoration that allows Features to register implementations that only run on their
26 | * own view types.
27 | */
28 | public final class FeatureAdapterItemDecoration extends RecyclerView.ItemDecoration {
29 |
30 | private final SparseArray viewTypeDecorationMap = new SparseArray<>();
31 |
32 | public void registerFeatureDecoration(FeatureItemDecoration decoration, AdapterViewTypeDelegate viewTypeDelegate) {
33 | viewTypeDecorationMap.put(viewTypeDelegate.getViewType(), decoration);
34 | }
35 |
36 | @Override
37 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
38 | final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
39 | final int viewType = holder.getItemViewType();
40 | final FeatureItemDecoration decoration = viewTypeDecorationMap.get(viewType);
41 | if (decoration != null) {
42 | decoration.getItemOffsetsImpl(outRect, view, holder, parent, state);
43 | }
44 | }
45 |
46 | @Override
47 | public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
48 | if (parent.getLayoutManager() == null) {
49 | return;
50 | }
51 | for (int i = 0; i < parent.getChildCount(); i++) {
52 | final View view = parent.getChildAt(i);
53 | final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
54 | final int viewType = holder.getItemViewType();
55 | final FeatureItemDecoration decoration = viewTypeDecorationMap.get(viewType);
56 | if (decoration != null) {
57 | decoration.onDrawViewImpl(canvas, view, holder, parent, state);
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/feature-adapter/src/main/java/com/groupon/featureadapter/FeatureAnimatorController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | import android.animation.Animator;
19 | import android.animation.AnimatorListenerAdapter;
20 | import androidx.annotation.NonNull;
21 | import androidx.annotation.Nullable;
22 | import androidx.collection.ArrayMap;
23 | import androidx.recyclerview.widget.RecyclerView;
24 | import androidx.recyclerview.widget.RecyclerView.ItemAnimator;
25 | import androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
26 | import androidx.recyclerview.widget.RecyclerView.ViewHolder;
27 | import java.util.Hashtable;
28 |
29 | import java.util.Map;
30 |
31 | /**
32 | * Composable class to be attached to a {@link RecyclerView.ItemAnimator} that allows Features
33 | * to register change animation implementations against the {@link AdapterViewTypeDelegate} types.
34 | *
35 | * Refer to {@link FeatureAdapterDefaultAnimator} for an example on how to use this class with an
36 | * existing {@link RecyclerView.ItemAnimator}
37 | */
38 | public class FeatureAnimatorController {
39 |
40 | private final Hashtable viewTypeFeatureAnimatorMap = new Hashtable<>();
41 | private final Map viewHolderAnimatorMap = new ArrayMap<>();
42 |
43 | public void registerFeatureAnimatorListener(FeatureAnimatorListener featureAnimator, AdapterViewTypeDelegate viewTypeDelegate) {
44 | viewTypeFeatureAnimatorMap.put(viewTypeDelegate.getViewType(), featureAnimator);
45 | }
46 |
47 | @Nullable
48 | public ItemHolderInfo recordPreLayoutInformation(@NonNull ViewHolder viewHolder) {
49 | final FeatureAnimatorListener listener = getFeatureAnimatorListener(viewHolder);
50 | return listener != null ? listener.getPreLayoutInformation(viewHolder) : null;
51 | }
52 |
53 | @Nullable
54 | public ItemHolderInfo recordPostLayoutInformation(@NonNull ViewHolder viewHolder) {
55 | final FeatureAnimatorListener listener = getFeatureAnimatorListener(viewHolder);
56 | return listener != null ? listener.getPostLayoutInformation(viewHolder) : null;
57 | }
58 |
59 | public FeatureAnimatorListener getFeatureAnimatorListener(@NonNull ViewHolder viewHolder) {
60 | return viewTypeFeatureAnimatorMap.get(viewHolder.getItemViewType());
61 | }
62 |
63 | public boolean animateChange(@NonNull ItemAnimator itemAnimator, @NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
64 | final FeatureAnimatorListener listener = viewTypeFeatureAnimatorMap.get(oldHolder.getItemViewType());
65 | if (listener != null) {
66 | final Animator animator = listener.setupChangeAnimation(itemAnimator, oldHolder, newHolder, preInfo, postInfo);
67 | if (animator != null) {
68 | animator.addListener(new CustomAnimationEndListener(itemAnimator, newHolder));
69 | viewHolderAnimatorMap.put(newHolder, animator);
70 | return true;
71 | }
72 | }
73 | return false;
74 | }
75 |
76 | public void runPendingAnimations() {
77 | for (Animator animator : viewHolderAnimatorMap.values()) {
78 | animator.start();
79 | }
80 | }
81 |
82 | public boolean isRunning() {
83 | return !viewHolderAnimatorMap.isEmpty();
84 | }
85 |
86 | public void endAnimation(ViewHolder item) {
87 | final Animator animator = viewHolderAnimatorMap.get(item);
88 | if (animator != null) {
89 | animator.cancel();
90 | }
91 | }
92 |
93 | public void endAnimations() {
94 | for (Animator animator : viewHolderAnimatorMap.values()) {
95 | if (animator != null) {
96 | animator.cancel();
97 | }
98 | }
99 | }
100 |
101 | private class CustomAnimationEndListener extends AnimatorListenerAdapter {
102 |
103 | private final ViewHolder holder;
104 | private final ItemAnimator itemAnimator;
105 |
106 | CustomAnimationEndListener(ItemAnimator itemAnimator, ViewHolder holder) {
107 | this.holder = holder;
108 | this.itemAnimator = itemAnimator;
109 | }
110 |
111 | @Override
112 | public void onAnimationEnd(Animator animation) {
113 | viewHolderAnimatorMap.remove(holder);
114 | itemAnimator.dispatchAnimationFinished(holder);
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/feature-adapter/src/main/java/com/groupon/featureadapter/FeatureAnimatorListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | import android.animation.Animator;
19 | import androidx.recyclerview.widget.RecyclerView;
20 |
21 | /**
22 | * A Feature's implementation of a custom animator that only runs for its own view type.
23 | *
24 | * @param The {@link AdapterViewTypeDelegate} ViewHolder.
25 | * @param A {@link RecyclerView.ItemAnimator.ItemHolderInfo} model to hold animation metadata.
26 | */
27 | public interface FeatureAnimatorListener {
28 | M getPreLayoutInformation(VH viewHolder);
29 |
30 | M getPostLayoutInformation(VH viewHolder);
31 |
32 | Animator setupChangeAnimation(RecyclerView.ItemAnimator itemAnimator, VH oldHolder, VH newHolder, M preInfo, M postInfo);
33 | }
34 |
--------------------------------------------------------------------------------
/feature-adapter/src/main/java/com/groupon/featureadapter/FeatureController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | import com.groupon.featureadapter.events.FeatureEvent;
19 | import com.groupon.featureadapter.events.FeatureEventListener;
20 | import com.groupon.featureadapter.events.FeatureEventSource;
21 | import java.util.Collection;
22 | import java.util.List;
23 |
24 | /**
25 | * The main class to represent a feature in the feature-control library. A feature receives an input
26 | * model (typically from a data source such as a network call), and converts it into a list of items
27 | * that will be displayed inside the recycler view. Items can be of different types and rendered
28 | * using different views. Each item of the list will use its own output model, a partial
29 | * representation of the input model that is of interest to its associated view.
30 | *
31 | * Example: Let's consider the input model class
32 | *
33 | *
34 | * class Foo {
35 | * String title;
36 | * String currency;
37 | * String price;
38 | * }
39 | *
40 | *
41 | * To represent this input model class in feature-adapter, we could create as many different feature
42 | * controllers as we want. Each of them could be in charge of representing one or more field, and/or
43 | * one or more computed values from this input model (like the price in various different
44 | * currencies, or the amount of taxes, etc..). We could have for instance a feature controller that
45 | * represents the title, with items of type String. And a feature controller that represents the
46 | * price and the currency together, using a new output model, a simple pojo with 2 strings or it
47 | * could represent the price and the currency as separate items and use different views to represent
48 | * them.
49 | *
50 | * Note on optimizations: In some cases, some features which are very computation intensive might
51 | * want to avoid building their feature items list as a whole, which will save a lot of computation
52 | * (the creation of the list of feature items and their comparison). In this case, it is recommended
53 | * that a {@code FeatureController} stores the information about the last computed state and
54 | * compares it with the new state inside {@code {@link #buildItems(Object)}}. This comparison is
55 | * really optional and we introduce it here as an optimization mechanism.
56 | *
57 | * @param the class of the input model that this controller will represent partially.
58 | */
59 | public abstract class FeatureController {
60 |
61 | private final FeatureEventSource featureEventSource = new FeatureEventSource();
62 |
63 | /**
64 | * @return the list of the {@link AdapterViewTypeDelegate} that will be used to represent each
65 | * item on screen.
66 | */
67 | public abstract Collection getAdapterViewTypeDelegates();
68 |
69 | /**
70 | * Builds the list of items to represent the {@code model}. When building the {@link ViewItem}s,
71 | * we must provide them with a view type. The view type must be one of the view type of the {@link
72 | * AdapterViewTypeDelegate}s provided by the method {@link #getAdapterViewTypeDelegates()}.
73 | *
74 | * @param model the input model instance.
75 | * @return a list of {@link ViewItem} that represent an aspect of {@code model}.
76 | * @see #getAdapterViewTypeDelegates()
77 | * @see AdapterViewTypeDelegate#getViewType()
78 | * @see ViewItem#viewType
79 | */
80 | public abstract List buildItems(MODEL model);
81 |
82 | /**
83 | * Adds a {@link FeatureEventListener} to all the {@link AdapterViewTypeDelegate} returned by
84 | * {@link #getAdapterViewTypeDelegates()}.
85 | *
86 | * @param featureEventListener the listener to be added.
87 | */
88 | public void addFeatureEventListener(FeatureEventListener featureEventListener) {
89 | featureEventSource.addFeatureEventListener(featureEventListener);
90 | for (AdapterViewTypeDelegate adapterViewTypeDelegate : getAdapterViewTypeDelegates()) {
91 | adapterViewTypeDelegate.addFeatureEventListener(featureEventListener);
92 | }
93 | }
94 |
95 | /**
96 | * Removes a {@link FeatureEventListener} from all the {@link AdapterViewTypeDelegate} returned by
97 | * {@link #getAdapterViewTypeDelegates()}.
98 | *
99 | * @param featureEventListener the listener to be removed.
100 | */
101 | public void removeFeatureEventListener(FeatureEventListener featureEventListener) {
102 | featureEventSource.removeFeatureEventListener(featureEventListener);
103 | for (AdapterViewTypeDelegate adapterViewTypeDelegate : getAdapterViewTypeDelegates()) {
104 | adapterViewTypeDelegate.removeFeatureEventListener(featureEventListener);
105 | }
106 | }
107 |
108 | /**
109 | * Fires an {@link FeatureEvent} to all listeners.
110 | *
111 | * @param featureEvent the event to be passed to all listeners.
112 | */
113 | protected void fireEvent(FeatureEvent featureEvent) {
114 | featureEventSource.fireEvent(featureEvent);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/feature-adapter/src/main/java/com/groupon/featureadapter/FeatureItemDecoration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | import android.graphics.Canvas;
19 | import android.graphics.Rect;
20 | import androidx.recyclerview.widget.RecyclerView;
21 | import android.view.View;
22 |
23 | /**
24 | * Simple implementation of a {@link RecyclerView.ItemDecoration} for
25 | * features to implement and register with {@link FeatureAdapterItemDecoration}
26 | */
27 | public interface FeatureItemDecoration {
28 | void getItemOffsetsImpl(Rect outRect, View view, RecyclerView.ViewHolder holder, RecyclerView parent, RecyclerView.State state);
29 |
30 | void onDrawViewImpl(Canvas canvas, View view, RecyclerView.ViewHolder holder, RecyclerView parent, RecyclerView.State state);
31 | }
32 |
--------------------------------------------------------------------------------
/feature-adapter/src/main/java/com/groupon/featureadapter/FeatureItems.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | import static java.util.Collections.emptyList;
19 | import static java.util.Collections.unmodifiableList;
20 |
21 | import java.util.ArrayList;
22 | import java.util.IdentityHashMap;
23 | import java.util.Iterator;
24 | import java.util.List;
25 | import java.util.Map;
26 |
27 | /**
28 | * Maintains adapter feature order, item position / ownership WARNING: this class is not thread
29 | * safe. RxFeatureAdapter guarantees the current state with its behavior.
30 | *
31 | * @param the input model of the {@link FeatureController}s and {@link FeaturesAdapter}
32 | */
33 | class FeatureItems {
34 |
35 | private final List> featureControllers = new ArrayList<>();
36 | private final Map, List> mapFeatureControllerToItems =
37 | new IdentityHashMap<>();
38 | private final List items = new ArrayList<>();
39 |
40 | FeatureItems(List> featureControllers) {
41 | this.featureControllers.addAll(featureControllers);
42 | for (FeatureController featureController : this.featureControllers) {
43 | mapFeatureControllerToItems.put(featureController, emptyList());
44 | }
45 | }
46 |
47 | List> getFeatureControllers() {
48 | return featureControllers;
49 | }
50 |
51 | ViewItem get(int position) {
52 | return items.get(position);
53 | }
54 |
55 | List getItems(FeatureController featureController) {
56 | return mapFeatureControllerToItems.get(featureController);
57 | }
58 |
59 | int size() {
60 | return items.size();
61 | }
62 |
63 | Iterator iterator() {
64 | return items.iterator();
65 | }
66 |
67 | int setItemsAndGetOffset(List newItems, FeatureController featureController) {
68 | mapFeatureControllerToItems.put(featureController, unmodifiableList(newItems));
69 | items.clear();
70 | int offset = 0;
71 | for (FeatureController controller : featureControllers) {
72 | if (controller == featureController) {
73 | offset = items.size();
74 | }
75 | items.addAll(mapFeatureControllerToItems.get(controller));
76 | }
77 | return offset;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/feature-adapter/src/main/java/com/groupon/featureadapter/FeatureUpdate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | import androidx.annotation.NonNull;
19 | import androidx.annotation.Nullable;
20 | import androidx.recyclerview.widget.DiffUtil.DiffResult;
21 | import java.util.List;
22 |
23 | public class FeatureUpdate {
24 | @NonNull public final FeatureController featureController;
25 | @Nullable public final List newItems;
26 | @Nullable public final DiffResult diffResult;
27 |
28 | public FeatureUpdate(
29 | @NonNull FeatureController featureController,
30 | @Nullable List newItems,
31 | @Nullable DiffResult diffResult) {
32 | this.featureController = featureController;
33 | this.newItems = newItems;
34 | this.diffResult = diffResult;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/feature-adapter/src/main/java/com/groupon/featureadapter/FeaturesAdapterErrorHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | /**
19 | * Implement this interface and set it to the {@link FeaturesAdapter} to catch and handle any error that happens during the binding process
20 | * see {@link FeaturesAdapter#setFeaturesAdapterErrorHandler(FeaturesAdapterErrorHandler)}
21 | */
22 | @FunctionalInterface
23 | public interface FeaturesAdapterErrorHandler {
24 |
25 | /**
26 | * Implement this method to catch and handle any error that happens during the binding process
27 | *
28 | * @param throwable the error
29 | * @param position the position of the feature that caused the error
30 | */
31 | void onBindViewHolderError(Throwable throwable, int position);
32 | }
33 |
--------------------------------------------------------------------------------
/feature-adapter/src/main/java/com/groupon/featureadapter/ViewItem.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | /**
19 | * Wraps the model & view type into a single object.
20 | *
21 | * @param the view model of this item. It is an ouput class of a {@link FeatureController}.
22 | */
23 | public final class ViewItem {
24 |
25 | /** The model of this item. */
26 | public final MODEL model;
27 | /** Must match a {@link AdapterViewTypeDelegate#getViewType()}. */
28 | public final int viewType;
29 |
30 | /**
31 | * Creates a new item.
32 | *
33 | * @param model the instance of the {@code MODEL} class.
34 | * @param adapterViewTypeDelegate the {@link AdapterViewTypeDelegate} associated with this item.
35 | */
36 | public ViewItem(MODEL model, AdapterViewTypeDelegate adapterViewTypeDelegate) {
37 | this.model = model;
38 | this.viewType = adapterViewTypeDelegate.getViewType();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/feature-adapter/src/main/java/com/groupon/featureadapter/events/FeatureEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter.events;
17 |
18 | /**
19 | * Marker interface for all events that can be fired from a feature.
20 | *
21 | * Typically, when used in conjunction with grox-events, a class that implements this interface
22 | * will also implement the {@link com.groupon.events.Event} interface so that it can be turned into
23 | * {@link com.groupon.Action}s.
24 | *
25 | *
Note to maintainers: this class seems a bit strange as it looks both empty and redundant with
26 | * the {@link com.groupon.events.Event} class from grox-events. But this is done on purpose as we
27 | * don't want the feature control library to be coupled with grox directly, while letting developers
28 | * elegantly merge their own events with grox if they want to.
29 | */
30 | public interface FeatureEvent {}
31 |
--------------------------------------------------------------------------------
/feature-adapter/src/main/java/com/groupon/featureadapter/events/FeatureEventListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter.events;
17 |
18 | /** A listener of {@link FeatureEvent}s. */
19 | public interface FeatureEventListener {
20 | /**
21 | * Reacts to a {@link FeatureEvent} being emitted.
22 | *
23 | * @param featureEvent the event that was emitted.
24 | */
25 | void onFeatureEvent(FeatureEvent featureEvent);
26 | }
27 |
--------------------------------------------------------------------------------
/feature-adapter/src/main/java/com/groupon/featureadapter/events/FeatureEventSource.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter.events;
17 |
18 | import java.util.List;
19 | import java.util.concurrent.CopyOnWriteArrayList;
20 |
21 | /**
22 | * Internal helper to addFeatureEventListener/removeFeatureEventListener {@link
23 | * FeatureEventListener} to a list of listeners and fire events to all of them.
24 | */
25 | public class FeatureEventSource {
26 | /** Concurrency robust list of listener. */
27 | private List featureEventListeners = new CopyOnWriteArrayList<>();
28 |
29 | /**
30 | * Adds a listener to the list of listeners.
31 | *
32 | * @param featureEventListener the listener to be added.
33 | */
34 | public void addFeatureEventListener(FeatureEventListener featureEventListener) {
35 | featureEventListeners.add(featureEventListener);
36 | }
37 |
38 | /**
39 | * Removes a listener to the list of listeners.
40 | *
41 | * @param featureEventListener the listener to be removed.
42 | */
43 | public void removeFeatureEventListener(FeatureEventListener featureEventListener) {
44 | featureEventListeners.remove(featureEventListener);
45 | }
46 |
47 | /**
48 | * Emits an {@link FeatureEvent} to all listeners that were previously added.
49 | *
50 | * @param featureEvent the event to emit to all listeners.
51 | */
52 | public void fireEvent(FeatureEvent featureEvent) {
53 | for (FeatureEventListener featureEventListener : featureEventListeners) {
54 | featureEventListener.onFeatureEvent(featureEvent);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/feature-adapter/src/test/java/com/groupon/featureadapter/AdapterViewTypeDelegateTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | import static org.hamcrest.CoreMatchers.not;
19 | import static org.hamcrest.CoreMatchers.nullValue;
20 | import static org.junit.Assert.*;
21 |
22 | import org.junit.Test;
23 |
24 | public class AdapterViewTypeDelegateTest {
25 |
26 | @Test
27 | public void createItemComparatorForType_should_returnNonNullComparator() {
28 | //GIVEN
29 |
30 | AdapterViewTypeDelegate, String> stubAdapterViewTypeDelegate =
31 | new StubAdapterViewTypeDelegate();
32 |
33 | //WHEN
34 | final DiffUtilComparator diffUtilComparator =
35 | stubAdapterViewTypeDelegate.createDiffUtilComparator();
36 |
37 | //THEN
38 | assertThat(diffUtilComparator, not(nullValue()));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/feature-adapter/src/test/java/com/groupon/featureadapter/DefaultDiffUtilComparatorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | import static org.hamcrest.CoreMatchers.is;
19 | import static org.junit.Assert.assertThat;
20 |
21 | import org.junit.Test;
22 |
23 | public class DefaultDiffUtilComparatorTest {
24 |
25 | @Test
26 | public void areItemsTheSame_should_alwaysReturnTrue() throws Exception {
27 | //GIVEN
28 | DefaultDiffUtilComparator itemComparatorUnderTest = new DefaultDiffUtilComparator<>();
29 |
30 | //WHEN
31 | final boolean areItemsTheSame = itemComparatorUnderTest.areItemsTheSame("a", "b");
32 |
33 | //THEN
34 | assertThat(areItemsTheSame, is(true));
35 | }
36 |
37 | @Test
38 | public void areContentsTheSame_should_returnFalse_when_Not_equals() throws Exception {
39 | //funny but we can't actually mock equals with any mock lib !
40 | //SOF: https://stackoverflow.com/q/3007532/693752
41 | //GIVEN
42 | DefaultDiffUtilComparator itemComparatorUnderTest = new DefaultDiffUtilComparator<>();
43 |
44 | //WHEN
45 | final boolean areContentsTheSame = itemComparatorUnderTest.areContentsTheSame("a", "b");
46 |
47 | //THEN
48 | assertThat(areContentsTheSame, is(false));
49 | }
50 |
51 | @Test
52 | public void areContentsTheSame_should_returnTrue_when_equals() throws Exception {
53 | //funny but we can't actually mock equals with any mock lib !
54 | //SOF: https://stackoverflow.com/q/3007532/693752
55 | //GIVEN
56 | DefaultDiffUtilComparator itemComparatorUnderTest = new DefaultDiffUtilComparator<>();
57 |
58 | //WHEN
59 | final boolean areContentsTheSame = itemComparatorUnderTest.areContentsTheSame("a", "a");
60 |
61 | //THEN
62 | assertThat(areContentsTheSame, is(true));
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/feature-adapter/src/test/java/com/groupon/featureadapter/FeatureControllerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | import static java.util.Arrays.asList;
19 | import static org.easymock.EasyMock.createMock;
20 | import static org.easymock.EasyMock.replay;
21 | import static org.easymock.EasyMock.verify;
22 |
23 | import com.groupon.featureadapter.events.FeatureEvent;
24 | import com.groupon.featureadapter.events.FeatureEventListener;
25 | import org.junit.Test;
26 |
27 | public class FeatureControllerTest {
28 |
29 | @Test
30 | public void addFeatureEventListener_should_relayTheEventsFromTheAdapterViewTypeDelegates() {
31 | //GIVEN
32 | AdapterViewTypeDelegate stubAdapterViewTypeDelegate = new StubAdapterViewTypeDelegate();
33 | FeatureController featureController =
34 | new StubFeatureController<>(asList(stubAdapterViewTypeDelegate));
35 | FeatureEventListener mockListener = createMock(FeatureEventListener.class);
36 | final FeatureEvent featureEvent = new FeatureEvent() {};
37 | mockListener.onFeatureEvent(featureEvent);
38 | replay(mockListener);
39 |
40 | //WHEN
41 | featureController.addFeatureEventListener(mockListener);
42 | stubAdapterViewTypeDelegate.fireEvent(featureEvent);
43 |
44 | //THEN
45 | verify(mockListener);
46 | }
47 |
48 | @Test
49 | public void
50 | removeFeatureEventListener_should_stopRelayingTheEventsFromTheAdapterViewTypeDelegates() {
51 | //GIVEN
52 | AdapterViewTypeDelegate stubAdapterViewTypeDelegate = new StubAdapterViewTypeDelegate();
53 | FeatureController featureController =
54 | new StubFeatureController<>(asList(stubAdapterViewTypeDelegate));
55 | FeatureEventListener mockListener = createMock(FeatureEventListener.class);
56 | final FeatureEvent featureEvent = new FeatureEvent() {};
57 | replay(mockListener);
58 |
59 | //WHEN
60 | featureController.addFeatureEventListener(mockListener);
61 | featureController.removeFeatureEventListener(mockListener);
62 | stubAdapterViewTypeDelegate.fireEvent(featureEvent);
63 |
64 | //THEN
65 | verify(mockListener);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/feature-adapter/src/test/java/com/groupon/featureadapter/StubAdapterViewTypeDelegate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | import androidx.recyclerview.widget.RecyclerView.ViewHolder;
19 | import android.view.ViewGroup;
20 |
21 | class StubAdapterViewTypeDelegate extends AdapterViewTypeDelegate {
22 | @Override
23 | public ViewHolder createViewHolder(ViewGroup parent) {
24 | return null;
25 | }
26 |
27 | @Override
28 | public void bindViewHolder(ViewHolder holder, Object o) {}
29 |
30 | @Override
31 | public void unbindViewHolder(ViewHolder holder) {}
32 | }
33 |
--------------------------------------------------------------------------------
/feature-adapter/src/test/java/com/groupon/featureadapter/StubFeatureController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | import java.util.List;
19 |
20 | class StubFeatureController extends FeatureController {
21 |
22 | private List binders;
23 | private List items;
24 |
25 | public StubFeatureController(List binders) {
26 | this.binders = binders;
27 | }
28 |
29 | public StubFeatureController(List binders, List items) {
30 | this(binders);
31 | this.items = items;
32 | }
33 |
34 | @Override
35 | public List getAdapterViewTypeDelegates() {
36 | return binders;
37 | }
38 |
39 | @Override
40 | public List buildItems(T s) {
41 | return items;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/feature-adapter/src/test/java/com/groupon/featureadapter/TestUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, Groupon, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.groupon.featureadapter;
17 |
18 | import static org.easymock.EasyMock.createMock;
19 |
20 | import android.database.Observable;
21 | import androidx.recyclerview.widget.RecyclerView;
22 | import java.lang.reflect.Field;
23 | import java.util.ArrayList;
24 |
25 | //from https://github.com/bignerdranch/expandable-recycler-view/blob/master/expandablerecyclerview/src/test/java/com/bignerdranch/expandablerecyclerview/TestUtils.java
26 | //under MIT licence
27 | public final class TestUtils {
28 |
29 | private TestUtils() {}
30 |
31 | /**
32 | * Fixes internal dependencies to android.database.Observable so that a RecyclerView.Adapter can
33 | * be tested using regular unit tests while verifying changes to the data.
34 | *
35 | * Pulled from:
36 | * https://github.com/badoo/Chateau/blob/master/ExampleApp/src/test/java/com/badoo/chateau/example/ui/utils/TestUtils.java
37 | */
38 | public static RecyclerView.AdapterDataObserver fixAdapterForTesting(RecyclerView.Adapter adapter)
39 | throws NoSuchFieldException, IllegalAccessException {
40 | // Observables are not mocked by default so we need to hook the adapter up to an observer so we can track changes
41 | Field observableField = RecyclerView.Adapter.class.getDeclaredField("mObservable");
42 | observableField.setAccessible(true);
43 | Object observable = observableField.get(adapter);
44 | Field observersField = Observable.class.getDeclaredField("mObservers");
45 | observersField.setAccessible(true);
46 | final ArrayList observers = new ArrayList<>();
47 | RecyclerView.AdapterDataObserver dataObserver =
48 | createMock(RecyclerView.AdapterDataObserver.class);
49 | observers.add(dataObserver);
50 | observersField.set(observable, observers);
51 | return dataObserver;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | android.useAndroidX=true
2 | android.enableJetifier=true
3 |
4 | GROUP=com.groupon.android.feature-adapter
5 | VERSION_NAME=2.1.2-SNAPSHOT
6 | POM_DESCRIPTION=Displays feature based screens optimally.
7 | POM_LICENCE_NAME=Apache V2
8 | POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0
9 | POM_LICENCE_DIST=repo
10 | POM_DEVELOPER_ID=Groupon
11 | POM_DEVELOPER_NAME=Groupon
12 | POM_URL=https://github.com/groupon/FeatureAdapter.git
13 | POM_SCM_URL=https://github.com/groupon/FeatureAdapter.git
14 | POM_SCM_CONNECTION=scm:git:git://github.com/groupon/FeatureAdapter.git
15 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/groupon/FeatureAdapter.git
16 |
--------------------------------------------------------------------------------
/gradle/gradle-mvn-push.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Chris Banes
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | apply plugin: 'maven'
18 | apply plugin: 'signing'
19 |
20 | version = VERSION_NAME
21 | group = GROUP
22 |
23 | def isReleaseBuild() {
24 | return VERSION_NAME.contains("SNAPSHOT") == false
25 | }
26 |
27 | def getReleaseRepositoryUrl() {
28 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
29 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
30 | }
31 |
32 | def getSnapshotRepositoryUrl() {
33 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
34 | : "https://oss.sonatype.org/content/repositories/snapshots/"
35 | }
36 |
37 | def getRepositoryUsername() {
38 | return hasProperty('SONATYPE_NEXUS_USERNAME') ? SONATYPE_NEXUS_USERNAME : ""
39 | }
40 |
41 | def getRepositoryPassword() {
42 | return hasProperty('SONATYPE_NEXUS_PASSWORD') ? SONATYPE_NEXUS_PASSWORD : ""
43 | }
44 |
45 | afterEvaluate { project ->
46 | uploadArchives {
47 | repositories {
48 | mavenDeployer {
49 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
50 |
51 | pom.groupId = GROUP
52 | pom.artifactId = POM_ARTIFACT_ID
53 | pom.version = VERSION_NAME
54 |
55 | repository(url: getReleaseRepositoryUrl()) {
56 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
57 | }
58 | snapshotRepository(url: getSnapshotRepositoryUrl()) {
59 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
60 | }
61 |
62 | pom.project {
63 | name POM_NAME
64 | packaging POM_PACKAGING
65 | description POM_DESCRIPTION
66 | url POM_URL
67 |
68 | scm {
69 | url POM_SCM_URL
70 | connection POM_SCM_CONNECTION
71 | developerConnection POM_SCM_DEV_CONNECTION
72 | }
73 |
74 | licenses {
75 | license {
76 | name POM_LICENCE_NAME
77 | url POM_LICENCE_URL
78 | distribution POM_LICENCE_DIST
79 | }
80 | }
81 |
82 | developers {
83 | developer {
84 | id POM_DEVELOPER_ID
85 | name POM_DEVELOPER_NAME
86 | }
87 | }
88 | }
89 | }
90 | }
91 | }
92 |
93 | signing {
94 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
95 | sign configurations.archives
96 | }
97 |
98 | if (project.getPlugins().hasPlugin('com.android.application') ||
99 | project.getPlugins().hasPlugin('com.android.library')) {
100 | task install(type: Upload, dependsOn: assemble) {
101 | repositories.mavenInstaller {
102 | configuration = configurations.archives
103 |
104 | pom.groupId = GROUP
105 | pom.artifactId = POM_ARTIFACT_ID
106 | pom.version = VERSION_NAME
107 |
108 | pom.project {
109 | name POM_NAME
110 | packaging POM_PACKAGING
111 | description POM_DESCRIPTION
112 | url POM_URL
113 |
114 | scm {
115 | url POM_SCM_URL
116 | connection POM_SCM_CONNECTION
117 | developerConnection POM_SCM_DEV_CONNECTION
118 | }
119 |
120 | licenses {
121 | license {
122 | name POM_LICENCE_NAME
123 | url POM_LICENCE_URL
124 | distribution POM_LICENCE_DIST
125 | }
126 | }
127 |
128 | developers {
129 | developer {
130 | id POM_DEVELOPER_ID
131 | name POM_DEVELOPER_NAME
132 | }
133 | }
134 | }
135 | }
136 | }
137 |
138 | task androidJavadocs(type: Javadoc) {
139 | source = android.sourceSets.main.java.source
140 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
141 | }
142 |
143 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
144 | classifier = 'javadoc'
145 | from androidJavadocs.destinationDir
146 | }
147 |
148 | task androidSourcesJar(type: Jar) {
149 | classifier = 'sources'
150 | from android.sourceSets.main.java.source
151 | }
152 | } else {
153 | install {
154 | repositories.mavenInstaller {
155 | pom.groupId = GROUP
156 | pom.artifactId = POM_ARTIFACT_ID
157 | pom.version = VERSION_NAME
158 |
159 | pom.project {
160 | name POM_NAME
161 | packaging POM_PACKAGING
162 | description POM_DESCRIPTION
163 | url POM_URL
164 |
165 | scm {
166 | url POM_SCM_URL
167 | connection POM_SCM_CONNECTION
168 | developerConnection POM_SCM_DEV_CONNECTION
169 | }
170 |
171 | licenses {
172 | license {
173 | name POM_LICENCE_NAME
174 | url POM_LICENCE_URL
175 | distribution POM_LICENCE_DIST
176 | }
177 | }
178 |
179 | developers {
180 | developer {
181 | id POM_DEVELOPER_ID
182 | name POM_DEVELOPER_NAME
183 | }
184 | }
185 | }
186 | }
187 | }
188 |
189 | task sourcesJar(type: Jar, dependsOn:classes) {
190 | classifier = 'sources'
191 | from sourceSets.main.allSource
192 | }
193 |
194 | task javadocJar(type: Jar, dependsOn:javadoc) {
195 | classifier = 'javadoc'
196 | from javadoc.destinationDir
197 | }
198 | }
199 |
200 | if (JavaVersion.current().isJava8Compatible()) {
201 | allprojects {
202 | tasks.withType(Javadoc) {
203 | options.addStringOption('Xdoclint:none', '-quiet')
204 | }
205 | }
206 | }
207 |
208 | artifacts {
209 | if (project.getPlugins().hasPlugin('com.android.application') ||
210 | project.getPlugins().hasPlugin('com.android.library')) {
211 | archives androidSourcesJar
212 | archives androidJavadocsJar
213 | } else {
214 | archives sourcesJar
215 | archives javadocJar
216 | }
217 | }
218 | }
219 |
220 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/groupon/FeatureAdapter/c08586e7d2669f3cca409a6813ab1e2260db9c9a/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed May 31 07:56:02 PDT 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-5.2.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 |
--------------------------------------------------------------------------------
/images/design-overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/groupon/FeatureAdapter/c08586e7d2669f3cca409a6813ab1e2260db9c9a/images/design-overview.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':feature-adapter'
2 | include ':feature-adapter-group'
3 | include ':feature-adapter-rx'
4 | include ':feature-adapter-sample-rx'
5 |
--------------------------------------------------------------------------------