Skip to content

Commit d2a295b

Browse files
Piotr Zawadzkizawadz88
authored andcommitted
- Refactored the way tabs are updated
- Introduced the state pattern for StepTabs UI state - Added a transition between warning icon and step number/done indicator circle background (work in progress) - renamed error state attributes and deprecated the old names for compatibility
1 parent 1052890 commit d2a295b

28 files changed

+582
-170
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,8 @@ For other examples, e.g. persisting state on rotation, displaying errors, changi
355355
| *ms_tabStepDividerWidth* | dimension or reference | The width of the horizontal tab divider used in tabs stepper type |
356356
| *ms_showBackButtonOnFirstStep* | boolean | Flag indicating if the Back (Previous step) button should be shown on the first step. False by default. |
357357
| *ms_errorColor* | color or reference | Error color in Tabs stepper |
358-
| *ms_showErrorState* | boolean | Flag indicating whether to show the error state. Only available with 'tabs' type. False by default. |
359-
| *ms_showErrorStateOnBack* | boolean | Flag indicating whether to keep showing the error state when user moves back. Only available with 'tabs' type. False by default. |
358+
| *ms_showErrorStateEnabled* | boolean | Flag indicating whether to show the error state. Only available with 'tabs' type. False by default. |
359+
| *ms_showErrorStateOnBackEnabled*| boolean | Flag indicating whether to keep showing the error state when user moves back. Only available with 'tabs' type. False by default. |
360360
| *ms_stepperLayoutTheme* | reference | Theme to use for even more custom styling of the stepper layout. It is recommended that it should extend @style/MSDefaultStepperLayoutTheme, which is the default theme used. |
361361

362362
### StepperLayout style attributes
@@ -375,7 +375,7 @@ A list of `ms_stepperLayoutTheme` attributes responsible for styling of StepperL
375375
| *ms_stepTabContainerStyle* | Used in layout/ms_step_tab_container |
376376
| *ms_stepTabNumberStyle* | Used by ms_stepNumber in layout/ms_step_tab |
377377
| *ms_stepTabDoneIndicatorStyle* | Used by ms_stepDoneIndicator in layout/ms_step_tab |
378-
| *ms_stepTabErrorIndicatorStyle* | Used by ms_stepErrorIndicator in layout/ms_step_tab |
378+
| *ms_stepTabIconBackgroundStyle* | Used by ms_stepIconBackground in layout/ms_step_tab |
379379
| *ms_stepTabTitleStyle* | Used by ms_stepTitle in layout/ms_step_tab |
380380
| *ms_stepTabDividerStyle* | Used by ms_stepDivider in layout/ms_step_tab |
381381

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

Lines changed: 84 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@
4343
import android.widget.LinearLayout;
4444

4545
import com.stepstone.stepper.adapter.StepAdapter;
46-
import com.stepstone.stepper.internal.widget.ColorableProgressBar;
47-
import com.stepstone.stepper.internal.widget.DottedProgressBar;
48-
import com.stepstone.stepper.internal.widget.RightNavigationButton;
49-
import com.stepstone.stepper.internal.widget.TabsContainer;
5046
import com.stepstone.stepper.internal.type.AbstractStepperType;
5147
import com.stepstone.stepper.internal.type.StepperTypeFactory;
5248
import com.stepstone.stepper.internal.util.AnimationUtil;
5349
import com.stepstone.stepper.internal.util.TintUtil;
50+
import com.stepstone.stepper.internal.widget.ColorableProgressBar;
51+
import com.stepstone.stepper.internal.widget.DottedProgressBar;
52+
import com.stepstone.stepper.internal.widget.RightNavigationButton;
53+
import com.stepstone.stepper.internal.widget.TabsContainer;
5454
import com.stepstone.stepper.viewmodel.StepViewModel;
5555

5656
/**
@@ -218,9 +218,9 @@ public void goToPrevStep() {
218218

219219
private int mCurrentStepPosition;
220220

221-
private boolean mShowErrorState;
221+
private boolean mShowErrorStateEnabled;
222222

223-
private boolean mShowErrorStateOnBack;
223+
private boolean mShowErrorStateOnBackEnabled;
224224

225225
@StyleRes
226226
private int mStepperLayoutTheme;
@@ -283,6 +283,7 @@ public StepAdapter getAdapter() {
283283
*/
284284
public void setAdapter(@NonNull StepAdapter stepAdapter) {
285285
this.mStepAdapter = stepAdapter;
286+
286287
mPager.setAdapter(stepAdapter.getPagerAdapter());
287288

288289
mStepperType.onNewAdapter(stepAdapter);
@@ -361,7 +362,12 @@ public void proceed() {
361362
* @param currentStepPosition new current step position
362363
*/
363364
public void setCurrentStepPosition(int currentStepPosition) {
364-
this.mCurrentStepPosition = currentStepPosition;
365+
int previousStepPosition = mCurrentStepPosition;
366+
if (currentStepPosition < previousStepPosition) {
367+
updateErrorFlagWhenGoingBack();
368+
}
369+
mCurrentStepPosition = currentStepPosition;
370+
365371
onUpdate(currentStepPosition, true);
366372
}
367373

@@ -381,30 +387,66 @@ public void setCompleteButtonVerificationFailed(boolean verificationFailed) {
381387
* Set whether when going backwards should clear the error state from the Tab. Default is <code>false</code>.
382388
*
383389
* @param showErrorStateOnBack true if navigating backwards should keep the error state, false otherwise
390+
* @deprecated see {@link #setShowErrorStateOnBackEnabled(boolean)}
384391
*/
392+
@Deprecated
385393
public void setShowErrorStateOnBack(boolean showErrorStateOnBack) {
386-
this.mShowErrorStateOnBack = showErrorStateOnBack;
394+
this.mShowErrorStateOnBackEnabled = showErrorStateOnBack;
387395
}
388396

397+
/**
398+
* Set whether when going backwards should clear the error state from the Tab. Default is <code>false</code>.
399+
*
400+
* @param showErrorStateOnBackEnabled true if navigating backwards should keep the error state, false otherwise
401+
*/
402+
public void setShowErrorStateOnBackEnabled(boolean showErrorStateOnBackEnabled) {
403+
this.mShowErrorStateOnBackEnabled = showErrorStateOnBackEnabled;
404+
}
389405
/**
390406
* Set whether the errors should be displayed when they occur or not. Default is <code>false</code>.
391407
*
392408
* @param showErrorState true if the errors should be displayed when they occur, false otherwise
409+
* @deprecated see {@link #setShowErrorStateEnabled(boolean)}
393410
*/
411+
@Deprecated
394412
public void setShowErrorState(boolean showErrorState) {
395-
this.mShowErrorState = showErrorState;
413+
setShowErrorStateEnabled(showErrorState);
414+
}
415+
416+
/**
417+
* Set whether the errors should be displayed when they occur or not. Default is <code>false</code>.
418+
*
419+
* @param showErrorStateEnabled true if the errors should be displayed when they occur, false otherwise
420+
*/
421+
public void setShowErrorStateEnabled(boolean showErrorStateEnabled) {
422+
this.mShowErrorStateEnabled = showErrorStateEnabled;
423+
}
424+
425+
/**
426+
* @return true if errors should be displayed when they occur
427+
*/
428+
public boolean isShowErrorStateEnabled() {
429+
return mShowErrorStateEnabled;
430+
}
431+
432+
/**
433+
* @return true if when going backwards the error state from the Tab should be cleared
434+
*/
435+
public boolean isShowErrorStateOnBackEnabled() {
436+
return mShowErrorStateOnBackEnabled;
396437
}
397438

398439
/**
399440
* Updates the error state in the UI.
400441
* It does nothing if showing error state is disabled.
401442
* This is used internally to show the error on tabs.
402443
* @param hasError true if error should be shown, false otherwise
403-
* @see #setShowErrorState(boolean)
444+
* @see #setShowErrorStateEnabled(boolean)
404445
*/
405446
public void updateErrorState(boolean hasError) {
406-
if (mShowErrorState) {
407-
mStepperType.setErrorState(mCurrentStepPosition, hasError);
447+
updateErrorFlag(hasError);
448+
if (mShowErrorStateEnabled) {
449+
invalidateCurrentPosition();
408450
}
409451
}
410452

@@ -420,6 +462,10 @@ public void setOffscreenPageLimit(int limit) {
420462
mPager.setOffscreenPageLimit(limit);
421463
}
422464

465+
public void updateErrorFlag(boolean hasError) {
466+
mStepperType.setErrorFlag(mCurrentStepPosition, hasError);
467+
}
468+
423469
@SuppressWarnings("RestrictedApi")
424470
private void init(AttributeSet attrs, @AttrRes int defStyleAttr) {
425471
initDefaultValues();
@@ -565,13 +611,15 @@ private void extractValuesFromAttributes(AttributeSet attrs, @AttrRes int defSty
565611

566612
mShowBackButtonOnFirstStep = a.getBoolean(R.styleable.StepperLayout_ms_showBackButtonOnFirstStep, false);
567613

568-
mShowErrorState = a.getBoolean(R.styleable.StepperLayout_ms_showErrorState, false);
614+
mShowErrorStateEnabled = a.getBoolean(R.styleable.StepperLayout_ms_showErrorState, false);
615+
mShowErrorStateEnabled = a.getBoolean(R.styleable.StepperLayout_ms_showErrorStateEnabled, mShowErrorStateEnabled);
569616

570617
if (a.hasValue(R.styleable.StepperLayout_ms_stepperType)) {
571618
mTypeIdentifier = a.getInt(R.styleable.StepperLayout_ms_stepperType, DEFAULT_TAB_DIVIDER_WIDTH);
572619
}
573620

574-
mShowErrorStateOnBack = a.getBoolean(R.styleable.StepperLayout_ms_showErrorStateOnBack, false);
621+
mShowErrorStateOnBackEnabled = a.getBoolean(R.styleable.StepperLayout_ms_showErrorStateOnBack, false);
622+
mShowErrorStateOnBackEnabled = a.getBoolean(R.styleable.StepperLayout_ms_showErrorStateOnBackEnabled, mShowErrorStateOnBackEnabled);
575623

576624
mStepperLayoutTheme = a.getResourceId(R.styleable.StepperLayout_ms_stepperLayoutTheme, R.style.MSDefaultStepperLayoutTheme);
577625

@@ -601,6 +649,8 @@ private Step findCurrentStep() {
601649
private void onPrevious() {
602650
Step step = findCurrentStep();
603651

652+
updateErrorFlagWhenGoingBack();
653+
604654
OnBackClickedCallback onBackClickedCallback = new OnBackClickedCallback();
605655
if (step instanceof BlockingStep) {
606656
((BlockingStep) step).onBackClicked(onBackClickedCallback);
@@ -609,17 +659,19 @@ private void onPrevious() {
609659
}
610660
}
611661

662+
private void updateErrorFlagWhenGoingBack() {
663+
updateErrorFlag(mShowErrorStateOnBackEnabled && mStepperType.getErrorAtPosition(mCurrentStepPosition));
664+
}
665+
612666
@UiThread
613667
private void onNext() {
614668
Step step = findCurrentStep();
615669

616670
if (verifyCurrentStep(step)) {
671+
invalidateCurrentPosition();
617672
return;
618673
}
619674

620-
//if moving forward and got no errors, set hasError to false, so we can have the tab with the check mark.
621-
updateErrorState(false);
622-
623675
OnNextClickedCallback onNextClickedCallback = new OnNextClickedCallback();
624676
if (step instanceof BlockingStep) {
625677
((BlockingStep) step).onNextClicked(onNextClickedCallback);
@@ -628,43 +680,47 @@ private void onNext() {
628680
}
629681
}
630682

683+
private void invalidateCurrentPosition() {
684+
mStepperType.onStepSelected(mCurrentStepPosition);
685+
}
686+
631687
private boolean verifyCurrentStep(Step step) {
632688
final VerificationError verificationError = step.verifyStep();
689+
boolean result = false;
633690
if (verificationError != null) {
634691
onError(verificationError);
635-
return true;
692+
result = true;
636693
}
637-
return false;
694+
695+
updateErrorFlag(result);
696+
return result;
638697
}
639698

640699
private void onError(@NonNull VerificationError verificationError) {
641700
Step step = findCurrentStep();
642701
if (step != null) {
643702
step.onError(verificationError);
644-
645-
//if moving forward and got errors, set hasError to true, showing the error drawable.
646-
updateErrorState(true);
647-
648703
}
649704
mListener.onError(verificationError);
650705
}
651706

652707
private void onComplete() {
653708
Step step = findCurrentStep();
654709
if (verifyCurrentStep(step)) {
710+
invalidateCurrentPosition();
655711
return;
656712
}
657-
updateErrorState(false);
713+
invalidateCurrentPosition();
658714
mListener.onCompleted(mCompleteNavigationButton);
659715
}
660716

661-
private void onUpdate(int newStepPosition, boolean animate) {
717+
private void onUpdate(int newStepPosition, boolean animateButtons) {
662718
mPager.setCurrentItem(newStepPosition);
663719
final boolean isLast = isLastPosition(newStepPosition);
664720
final boolean isFirst = newStepPosition == 0;
665-
AnimationUtil.fadeViewVisibility(mNextNavigationButton, isLast ? View.GONE : View.VISIBLE, animate);
666-
AnimationUtil.fadeViewVisibility(mCompleteNavigationButton, !isLast ? View.GONE : View.VISIBLE, animate);
667-
AnimationUtil.fadeViewVisibility(mBackNavigationButton, isFirst && !mShowBackButtonOnFirstStep ? View.GONE : View.VISIBLE, animate);
721+
AnimationUtil.fadeViewVisibility(mNextNavigationButton, isLast ? View.GONE : View.VISIBLE, animateButtons);
722+
AnimationUtil.fadeViewVisibility(mCompleteNavigationButton, !isLast ? View.GONE : View.VISIBLE, animateButtons);
723+
AnimationUtil.fadeViewVisibility(mBackNavigationButton, isFirst && !mShowBackButtonOnFirstStep ? View.GONE : View.VISIBLE, animateButtons);
668724

669725
final StepViewModel viewModel = mStepAdapter.getViewModel(newStepPosition);
670726

@@ -676,8 +732,6 @@ private void onUpdate(int newStepPosition, boolean animate) {
676732

677733
setCompoundDrawablesForNavigationButtons(viewModel.getBackButtonStartDrawableResId(), viewModel.getNextButtonEndDrawableResId());
678734

679-
//needs to be here in case user for any reason decide to change whether or not to show errors when going back.
680-
mStepperType.showErrorStateOnBack(mShowErrorStateOnBack);
681735
mStepperType.onStepSelected(newStepPosition);
682736
mListener.onStepSelected(newStepPosition);
683737
Step step = mStepAdapter.findStep(newStepPosition);

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

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616

1717
package com.stepstone.stepper.internal.type;
1818

19+
import android.support.annotation.CallSuper;
1920
import android.support.annotation.ColorInt;
2021
import android.support.annotation.NonNull;
2122
import android.support.annotation.RestrictTo;
23+
import android.util.SparseBooleanArray;
2224

2325
import com.stepstone.stepper.StepperLayout;
2426
import com.stepstone.stepper.adapter.StepAdapter;
@@ -46,7 +48,9 @@ public abstract class AbstractStepperType {
4648
*/
4749
public static final int TABS = 0x03;
4850

49-
protected final StepperLayout stepperLayout;
51+
final StepperLayout stepperLayout;
52+
53+
final SparseBooleanArray mStepErrors = new SparseBooleanArray();
5054

5155
public AbstractStepperType(StepperLayout stepperLayout) {
5256
this.stepperLayout = stepperLayout;
@@ -63,19 +67,28 @@ public AbstractStepperType(StepperLayout stepperLayout) {
6367
* @param stepPosition the step to set the error
6468
* @param hasError whether it has error or not
6569
*/
66-
public void setErrorState(int stepPosition, boolean hasError){ }
70+
public void setErrorFlag(int stepPosition, boolean hasError) {
71+
mStepErrors.put(stepPosition, hasError);
72+
}
6773

6874
/**
69-
* Called to set whether navigating backwards should keep the error state.
70-
* @param mShowErrorStateOnBack true if navigating backwards should keep the error state, false otherwise
75+
* Checks if there's an error for the step.
76+
*
77+
* @param stepPosition the step to check for error
78+
* @return true if there's an error for this step
7179
*/
72-
public void showErrorStateOnBack(boolean mShowErrorStateOnBack){ }
80+
public boolean getErrorAtPosition(int stepPosition) {
81+
return mStepErrors.get(stepPosition);
82+
}
7383

7484
/**
7585
* Called when {@link StepperLayout}'s adapter gets changed
7686
* @param stepAdapter new stepper adapter
7787
*/
78-
public abstract void onNewAdapter(@NonNull StepAdapter stepAdapter);
88+
@CallSuper
89+
public void onNewAdapter(@NonNull StepAdapter stepAdapter) {
90+
mStepErrors.clear();
91+
}
7992

8093
@ColorInt
8194
protected int getSelectedColor() {

material-stepper/src/main/java/com/stepstone/stepper/internal/type/DotsStepperType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public void onStepSelected(int newStepPosition) {
6363
*/
6464
@Override
6565
public void onNewAdapter(@NonNull StepAdapter stepAdapter) {
66+
super.onNewAdapter(stepAdapter);
6667
final int stepCount = stepAdapter.getCount();
6768
mDottedProgressBar.setDotCount(stepCount);
6869
mDottedProgressBar.setVisibility(stepCount > 1 ? View.VISIBLE : View.GONE);

material-stepper/src/main/java/com/stepstone/stepper/internal/type/ProgressBarStepperType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public void onStepSelected(int newStepPosition) {
5656
*/
5757
@Override
5858
public void onNewAdapter(@NonNull StepAdapter stepAdapter) {
59+
super.onNewAdapter(stepAdapter);
5960
final int stepCount = stepAdapter.getCount();
6061
mProgressBar.setMax(stepAdapter.getCount());
6162
mProgressBar.setVisibility(stepCount > 1 ? View.VISIBLE : View.GONE);

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

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -63,30 +63,18 @@ public TabsStepperType(StepperLayout stepperLayout) {
6363
*/
6464
@Override
6565
public void onStepSelected(int newStepPosition) {
66-
mTabsContainer.setCurrentStep(newStepPosition);
67-
}
68-
69-
/**
70-
* {@inheritDoc}
71-
*/
72-
@Override
73-
public void setErrorState(int stepPosition, boolean hasError) {
74-
mTabsContainer.setErrorStep(stepPosition, hasError);
75-
}
76-
77-
/**
78-
* {@inheritDoc}
79-
*/
80-
@Override
81-
public void showErrorStateOnBack(boolean mShowErrorStateOnBack) {
82-
mTabsContainer.setShowErrorStateOnBack(mShowErrorStateOnBack);
66+
if (!stepperLayout.isShowErrorStateEnabled()) {
67+
mStepErrors.clear();
68+
}
69+
mTabsContainer.updateSteps(newStepPosition, mStepErrors);
8370
}
8471

8572
/**
8673
* {@inheritDoc}
8774
*/
8875
@Override
8976
public void onNewAdapter(@NonNull StepAdapter stepAdapter) {
77+
super.onNewAdapter(stepAdapter);
9078
List<CharSequence> titles = new ArrayList<>();
9179
final int stepCount = stepAdapter.getCount();
9280
for (int i = 0; i < stepCount; i++) {

0 commit comments

Comments
 (0)