Skip to content

Commit cc56792

Browse files
authored
Merge pull request #44 from leonardo2204/error_back
Showing error in stepper with Tabs
2 parents 4d392db + d37d992 commit cc56792

20 files changed

+295
-5
lines changed

material-stepper/src/main/java/com/stepstone/stepper/StepperLayout.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import com.stepstone.stepper.internal.TabsContainer;
4848
import com.stepstone.stepper.type.AbstractStepperType;
4949
import com.stepstone.stepper.type.StepperTypeFactory;
50+
import com.stepstone.stepper.type.TabsStepperType;
5051
import com.stepstone.stepper.util.AnimationUtil;
5152
import com.stepstone.stepper.util.TintUtil;
5253
import com.stepstone.stepper.viewmodel.StepViewModel;
@@ -175,6 +176,9 @@ public final void goToPrevStep() {
175176
@ColorInt
176177
private int mSelectedColor;
177178

179+
@ColorInt
180+
private int mErrorColor;
181+
178182
@DrawableRes
179183
private int mBottomNavigationBackground;
180184

@@ -205,6 +209,10 @@ public final void goToPrevStep() {
205209

206210
private int mCurrentStepPosition;
207211

212+
private boolean mShowErrorState;
213+
214+
private boolean mShowErrorStateOnBack;
215+
208216
@NonNull
209217
private StepperListener mListener = StepperListener.NULL;
210218

@@ -295,6 +303,10 @@ public int getUnselectedColor() {
295303
return mUnselectedColor;
296304
}
297305

306+
public int getErrorColor() {
307+
return mErrorColor;
308+
}
309+
298310
public int getTabStepDividerWidth() {
299311
return mTabStepDividerWidth;
300312
}
@@ -326,6 +338,23 @@ public void setCompleteButtonVerificationFailed(boolean verificationFailed) {
326338
mCompleteNavigationButton.setVerificationFailed(verificationFailed);
327339
}
328340

341+
/**
342+
343+
* Set whether when going backwards should clear the error state from the Tab. Default is false.
344+
* @param mShowErrorStateOnBack
345+
*/
346+
public void setShowErrorStateOnBack(boolean mShowErrorStateOnBack) {
347+
this.mShowErrorStateOnBack = mShowErrorStateOnBack;
348+
}
349+
350+
/**
351+
* Set whether the tab should display error or not. Default false.
352+
* @param mShowErrorState
353+
*/
354+
public void setShowErrorState(boolean mShowErrorState) {
355+
this.mShowErrorState = mShowErrorState;
356+
}
357+
329358
/**
330359
* Set the number of steps that should be retained to either side of the
331360
* current step in the view hierarchy in an idle state. Steps beyond this
@@ -427,13 +456,15 @@ private void extractValuesFromAttributes(AttributeSet attrs, @AttrRes int defSty
427456
if (a.hasValue(R.styleable.StepperLayout_ms_completeButtonColor)) {
428457
mCompleteButtonColor = a.getColorStateList(R.styleable.StepperLayout_ms_completeButtonColor);
429458
}
430-
431459
if (a.hasValue(R.styleable.StepperLayout_ms_activeStepColor)) {
432460
mSelectedColor = a.getColor(R.styleable.StepperLayout_ms_activeStepColor, mSelectedColor);
433461
}
434462
if (a.hasValue(R.styleable.StepperLayout_ms_inactiveStepColor)) {
435463
mUnselectedColor = a.getColor(R.styleable.StepperLayout_ms_inactiveStepColor, mUnselectedColor);
436464
}
465+
if (a.hasValue(R.styleable.StepperLayout_ms_errorColor)) {
466+
mErrorColor = a.getColor(R.styleable.StepperLayout_ms_errorColor, mErrorColor);
467+
}
437468
if (a.hasValue(R.styleable.StepperLayout_ms_bottomNavigationBackground)) {
438469
mBottomNavigationBackground = a.getResourceId(R.styleable.StepperLayout_ms_bottomNavigationBackground, mBottomNavigationBackground);
439470
}
@@ -464,10 +495,14 @@ private void extractValuesFromAttributes(AttributeSet attrs, @AttrRes int defSty
464495

465496
mShowBackButtonOnFirstStep = a.getBoolean(R.styleable.StepperLayout_ms_showBackButtonOnFirstStep, false);
466497

498+
mShowErrorState = a.getBoolean(R.styleable.StepperLayout_ms_showErrorState, false);
499+
467500
if (a.hasValue(R.styleable.StepperLayout_ms_stepperType)) {
468501
mTypeIdentifier = a.getInt(R.styleable.StepperLayout_ms_stepperType, DEFAULT_TAB_DIVIDER_WIDTH);
469502
}
470503

504+
mShowErrorStateOnBack = a.getBoolean(R.styleable.StepperLayout_ms_showErrorStateOnBack, false);
505+
471506
a.recycle();
472507
}
473508
}
@@ -477,6 +512,7 @@ private void initDefaultValues() {
477512
ContextCompat.getColorStateList(getContext(), R.color.ms_bottomNavigationButtonTextColor);
478513
mSelectedColor = ContextCompat.getColor(getContext(), R.color.ms_selectedColor);
479514
mUnselectedColor = ContextCompat.getColor(getContext(), R.color.ms_unselectedColor);
515+
mErrorColor = ContextCompat.getColor(getContext(), R.color.ms_errorColor);
480516
mBottomNavigationBackground = R.color.ms_bottomNavigationBackgroundColor;
481517
mBackButtonText = getContext().getString(R.string.ms_back);
482518
mNextButtonText = getContext().getString(R.string.ms_next);
@@ -509,6 +545,11 @@ private void onNext() {
509545
if (verifyCurrentStep(step)) {
510546
return;
511547
}
548+
549+
//if moving forward and got no errors, set hasError to false, so we can have the tab with the check mark.
550+
if(mShowErrorState)
551+
mStepperType.setErrorStep(mCurrentStepPosition, false);
552+
512553
OnNextClickedCallback onNextClickedCallback = new OnNextClickedCallback();
513554
if (step instanceof BlockingStep) {
514555
((BlockingStep) step).onNextClicked(onNextClickedCallback);
@@ -530,6 +571,11 @@ private void onError(@NonNull VerificationError verificationError) {
530571
Step step = findCurrentStep();
531572
if (step != null) {
532573
step.onError(verificationError);
574+
575+
//if moving forward and got errors, set hasError to true, showing the error drawable.
576+
if(mShowErrorState)
577+
mStepperType.setErrorStep(mCurrentStepPosition, true);
578+
533579
}
534580
mListener.onError(verificationError);
535581
}
@@ -539,6 +585,7 @@ private void onComplete(View completeButton) {
539585
if (verifyCurrentStep(step)) {
540586
return;
541587
}
588+
mStepperType.setErrorStep(mCurrentStepPosition, false);
542589
mListener.onCompleted(completeButton);
543590
}
544591

@@ -558,6 +605,8 @@ private void onUpdate(int newStepPosition, boolean animate) {
558605
updateNextButtonText(viewModel);
559606
}
560607

608+
//needs to be here in case user for any reason decide to change whether or not to show errors when going back.
609+
mStepperType.showErrorStateOnBack(mShowErrorStateOnBack);
561610
mStepperType.onStepSelected(newStepPosition);
562611
mListener.onStepSelected(newStepPosition);
563612
Step step = mStepAdapter.findStep(mPager, newStepPosition);

material-stepper/src/main/java/com/stepstone/stepper/internal/StepTab.java

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@
1717
package com.stepstone.stepper.internal;
1818

1919
import android.content.Context;
20+
import android.content.res.ColorStateList;
2021
import android.graphics.Typeface;
2122
import android.graphics.drawable.Drawable;
2223
import android.support.annotation.ColorInt;
2324
import android.support.v4.content.ContextCompat;
25+
import android.support.v4.graphics.drawable.DrawableCompat;
26+
import android.support.v7.widget.AppCompatImageView;
2427
import android.util.AttributeSet;
2528
import android.view.LayoutInflater;
2629
import android.view.View;
@@ -47,6 +50,9 @@ public class StepTab extends RelativeLayout {
4750
@ColorInt
4851
private int mSelectedColor;
4952

53+
@ColorInt
54+
private int mErrorColor;
55+
5056
private final TextView mStepNumber;
5157

5258
private final View mStepDivider;
@@ -55,8 +61,12 @@ public class StepTab extends RelativeLayout {
5561

5662
private final ImageView mStepDoneIndicator;
5763

64+
private final AppCompatImageView mStepErrorIndicator;
65+
5866
private int mDividerWidth = StepperLayout.DEFAULT_TAB_DIVIDER_WIDTH;
5967

68+
private boolean hasError;
69+
6070
public StepTab(Context context) {
6171
this(context, null);
6272
}
@@ -71,9 +81,11 @@ public StepTab(Context context, AttributeSet attrs, int defStyleAttr) {
7181

7282
mSelectedColor = ContextCompat.getColor(context, R.color.ms_selectedColor);
7383
mUnselectedColor = ContextCompat.getColor(context, R.color.ms_unselectedColor);
84+
mErrorColor = ContextCompat.getColor(context, R.color.ms_errorColor);
7485

7586
mStepNumber = (TextView) findViewById(R.id.ms_stepNumber);
7687
mStepDoneIndicator = (ImageView) findViewById(R.id.ms_stepDoneIndicator);
88+
mStepErrorIndicator = (AppCompatImageView) findViewById(R.id.ms_stepErrorIndicator);
7789
mStepDivider = findViewById(R.id.ms_stepDivider);
7890
mStepTitle = ((TextView) findViewById(R.id.ms_stepTitle));
7991
}
@@ -91,15 +103,45 @@ public void toggleDividerVisibility(boolean show) {
91103
* @param done true if the step is done and the step's number should be replaced with a <i>done</i> icon, false otherwise
92104
* @param current true if the step is the current step, false otherwise
93105
*/
94-
public void updateState(final boolean done, final boolean current) {
106+
public void updateState(final boolean done, final boolean showErrorOnBack, final boolean current) {
107+
//if this tab has errors and the user decide not to clear when going backwards, simply ignore the update
108+
if(this.hasError && showErrorOnBack)
109+
return;
110+
95111
mStepDoneIndicator.setVisibility(done ? View.VISIBLE : View.GONE);
96112
mStepNumber.setVisibility(!done ? View.VISIBLE : View.GONE);
113+
mStepErrorIndicator.setVisibility(GONE);
97114
colorViewBackground(done ? mStepDoneIndicator : mStepNumber, done || current);
98115

116+
this.hasError = false;
117+
118+
mStepTitle.setTextColor(ContextCompat.getColor(getContext(), R.color.ms_black));
99119
mStepTitle.setTypeface(current ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT);
100120
mStepTitle.setAlpha(done || current ? OPAQUE_ALPHA : INACTIVE_STEP_TITLE_ALPHA);
101121
}
102122

123+
/**
124+
* Update the error state of this tab. If it has error, show the error drawable.
125+
* @param hasError whether the tab has errors or not.
126+
*/
127+
public void updateErrorState(boolean done, boolean hasError) {
128+
if(hasError) {
129+
mStepDoneIndicator.setVisibility(View.GONE);
130+
mStepNumber.setVisibility(View.GONE);
131+
mStepErrorIndicator.setVisibility(VISIBLE);
132+
mStepErrorIndicator.setColorFilter(mErrorColor);
133+
mStepTitle.setTextColor(mErrorColor);
134+
} else if(done) {
135+
mStepDoneIndicator.setVisibility(View.VISIBLE);
136+
mStepErrorIndicator.setVisibility(GONE);
137+
colorViewBackground(mStepDoneIndicator, true);
138+
139+
mStepTitle.setTextColor(ContextCompat.getColor(getContext(), R.color.ms_black));
140+
}
141+
142+
this.hasError = hasError;
143+
}
144+
103145
/**
104146
* Sets the name of the step
105147
* @param title step name
@@ -124,6 +166,10 @@ public void setSelectedColor(int selectedColor) {
124166
this.mSelectedColor = selectedColor;
125167
}
126168

169+
public void setErrorColor(int errorColor) {
170+
this.mErrorColor = errorColor;
171+
}
172+
127173
private void colorViewBackground(View view, boolean selected) {
128174
Drawable d = view.getBackground();
129175
TintUtil.tintDrawable(d, selected ? mSelectedColor : mUnselectedColor);

material-stepper/src/main/java/com/stepstone/stepper/internal/TabsContainer.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ public void onTabClicked(int position) {
6767
@ColorInt
6868
private int mSelectedColor;
6969

70+
@ColorInt
71+
private int mErrorColor;
72+
7073
private int mDividerWidth = StepperLayout.DEFAULT_TAB_DIVIDER_WIDTH;
7174

7275
private final int mContainerLateralPadding;
@@ -79,6 +82,8 @@ public void onTabClicked(int position) {
7982

8083
private List<CharSequence> mStepTitles;
8184

85+
private boolean mShowErrorStateOnBack;
86+
8287
public TabsContainer(Context context) {
8388
this(context, null);
8489
}
@@ -93,6 +98,7 @@ public TabsContainer(Context context, AttributeSet attrs, int defStyleAttr) {
9398

9499
mSelectedColor = ContextCompat.getColor(context, R.color.ms_selectedColor);
95100
mUnselectedColor = ContextCompat.getColor(context, R.color.ms_unselectedColor);
101+
mErrorColor = ContextCompat.getColor(context, R.color.ms_errorColor);
96102
if (attrs != null) {
97103
final TypedArray a = getContext().obtainStyledAttributes(
98104
attrs, R.styleable.TabsContainer, defStyleAttr, 0);
@@ -104,6 +110,10 @@ public TabsContainer(Context context, AttributeSet attrs, int defStyleAttr) {
104110
mUnselectedColor = a.getColor(R.styleable.TabsContainer_ms_inactiveTabColor, mUnselectedColor);
105111
}
106112

113+
if (a.hasValue(R.styleable.StepperLayout_ms_errorColor)) {
114+
mErrorColor = a.getColor(R.styleable.StepperLayout_ms_errorColor, mErrorColor);
115+
}
116+
107117
a.recycle();
108118
}
109119
mContainerLateralPadding = context.getResources().getDimensionPixelOffset(R.dimen.ms_tabs_container_lateral_padding);
@@ -120,6 +130,10 @@ public void setSelectedColor(@ColorInt int selectedColor) {
120130
this.mSelectedColor = selectedColor;
121131
}
122132

133+
public void setErrorColor(@ColorInt int mErrorColor) {
134+
this.mErrorColor = mErrorColor;
135+
}
136+
123137
public void setDividerWidth(int dividerWidth) {
124138
this.mDividerWidth = dividerWidth;
125139
}
@@ -152,20 +166,37 @@ public void setCurrentStep(int newStepPosition) {
152166
StepTab childTab = (StepTab) mTabsInnerContainer.getChildAt(i);
153167
boolean done = i < newStepPosition;
154168
final boolean current = i == newStepPosition;
155-
childTab.updateState(done, current);
169+
childTab.updateState(done, mShowErrorStateOnBack, current);
156170
if (current) {
157171
mTabsScrollView.smoothScrollTo(childTab.getLeft() - mContainerLateralPadding, 0);
158172
}
159173
}
160174
}
161175

176+
/**
177+
* Set whether when going backwards should clear the error state from the Tab. Default is false
178+
* @param mShowErrorStateOnBack
179+
*/
180+
public void setShowErrorStateOnBack(boolean mShowErrorStateOnBack) {
181+
this.mShowErrorStateOnBack = mShowErrorStateOnBack;
182+
}
183+
184+
public void setErrorStep(int stepPosition, boolean hasError){
185+
if(mStepTitles.size() < stepPosition)
186+
return;
187+
188+
StepTab childTab = (StepTab) mTabsInnerContainer.getChildAt(stepPosition);
189+
childTab.updateErrorState(mStepTitles.size() - 1 == stepPosition ,hasError);
190+
}
191+
162192
private View createStepTab(final int position, @Nullable CharSequence title) {
163193
StepTab view = (StepTab) LayoutInflater.from(getContext()).inflate(R.layout.ms_step_tab_container, mTabsInnerContainer, false);
164194
view.setStepNumber(String.valueOf(position + 1));
165195
view.toggleDividerVisibility(!isLastPosition(position));
166196
view.setStepTitle(title);
167197
view.setSelectedColor(mSelectedColor);
168198
view.setUnselectedColor(mUnselectedColor);
199+
view.setErrorColor(mErrorColor);
169200
view.setDividerWidth(mDividerWidth);
170201

171202
view.setOnClickListener(new View.OnClickListener() {

material-stepper/src/main/java/com/stepstone/stepper/type/AbstractStepperType.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,19 @@ public AbstractStepperType(StepperLayout stepperLayout) {
5454
*/
5555
public abstract void onStepSelected(int newStepPosition);
5656

57+
/**
58+
* Called to set whether the stepPosition has an error or not, changing it's appearance.
59+
* @param stepPosition the step to set the error
60+
* @param hasError whether it has error or not
61+
*/
62+
public void setErrorStep(int stepPosition, boolean hasError){ }
63+
64+
/**
65+
* Called to set whether navigating backwards should keep the error state.
66+
* @param mShowErrorStateOnBack
67+
*/
68+
public void showErrorStateOnBack(boolean mShowErrorStateOnBack){ }
69+
5770
/**
5871
* Called when {@link StepperLayout}'s adapter gets changed
5972
* @param stepAdapter new stepper adapter

material-stepper/src/main/java/com/stepstone/stepper/type/TabsStepperType.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public TabsStepperType(StepperLayout stepperLayout) {
4141
mTabsContainer.setVisibility(View.VISIBLE);
4242
mTabsContainer.setSelectedColor(stepperLayout.getSelectedColor());
4343
mTabsContainer.setUnselectedColor(stepperLayout.getUnselectedColor());
44+
mTabsContainer.setErrorColor(stepperLayout.getErrorColor());
4445
mTabsContainer.setDividerWidth(stepperLayout.getTabStepDividerWidth());
4546
mTabsContainer.setListener(stepperLayout);
4647
}
@@ -53,6 +54,22 @@ public void onStepSelected(int newStepPosition) {
5354
mTabsContainer.setCurrentStep(newStepPosition);
5455
}
5556

57+
/**
58+
* {@inheritDoc}
59+
*/
60+
@Override
61+
public void setErrorStep(int stepPosition, boolean hasError) {
62+
mTabsContainer.setErrorStep(stepPosition, hasError);
63+
}
64+
65+
/**
66+
* {@inheritDoc}
67+
*/
68+
@Override
69+
public void showErrorStateOnBack(boolean mShowErrorStateOnBack) {
70+
mTabsContainer.setShowErrorStateOnBack(mShowErrorStateOnBack);
71+
}
72+
5673
/**
5774
* {@inheritDoc}
5875
*/

0 commit comments

Comments
 (0)