Skip to content

Commit 2a9be30

Browse files
authored
fix: android native variant min/max scrolling issues (#286)
* fix: native android variant min/max dates scrolling issues * add prop check * keep track of animating state * update github actions * temp test * change work dir * restore compile sdk
1 parent 3d54efe commit 2a9be30

File tree

7 files changed

+126
-31
lines changed

7 files changed

+126
-31
lines changed

.github/workflows/android-detox.yml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ on:
99
- master
1010

1111
jobs:
12-
unit_tests:
13-
name: Unit tests
12+
javascript_unit_tests:
13+
name: Unit tests - javascript
1414
runs-on: macos-latest
1515
timeout-minutes: 5
1616

@@ -31,6 +31,26 @@ jobs:
3131
run: |
3232
yarn test
3333
34+
java_unit_tests:
35+
name: Unit tests - java
36+
runs-on: ubuntu-latest
37+
38+
steps:
39+
- uses: actions/checkout@v2
40+
- name: Set up JDK 1.8
41+
uses: actions/setup-java@v1
42+
with:
43+
java-version: 1.8
44+
45+
- name: Install npm dependencies
46+
working-directory: ./examples/detox
47+
run: |
48+
yarn install --frozen-lockfile
49+
50+
- name: Run unit tests
51+
working-directory: ./examples/detox/android
52+
run: ./gradlew testDebugUnitTest
53+
3454
end_to_end_tests:
3555
name: End to end tests
3656
runs-on: macos-latest

android/src/main/java/com/henninghall/date_picker/Utils.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,17 @@ public static WheelType patternCharToWheelType(char patternChar) throws Exceptio
8585
default: throw new Exception("Invalid pattern char: " + patternChar);
8686
}
8787
}
88+
89+
90+
public static int getShortestScrollOption(int from, int to, final int maxValue, boolean isWrapping) {
91+
int size = maxValue + 1;
92+
int option1 = to - from;
93+
int option2 = option1 > 0 ? option1 - size : option1 + size;
94+
if (isWrapping) {
95+
return Math.abs(option1) < Math.abs(option2) ? option1 : option2;
96+
}
97+
if (from + option1 > maxValue) return option2;
98+
if (from + option1 < 0) return option2;
99+
return option1;
100+
}
88101
}

android/src/main/java/com/henninghall/date_picker/pickers/AndroidNative.java

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import android.widget.EditText;
1010
import android.widget.NumberPicker;
1111

12+
import com.henninghall.date_picker.Utils;
13+
1214
import java.lang.reflect.Field;
1315
import java.lang.reflect.InvocationTargetException;
1416
import java.lang.reflect.Method;
@@ -21,6 +23,7 @@ public class AndroidNative extends NumberPicker implements Picker {
2123
private Picker.OnValueChangeListener onValueChangedListener;
2224
private int state = SCROLL_STATE_IDLE;
2325
private OnValueChangeListenerInScrolling listenerInScrolling;
26+
private boolean isAnimating;
2427

2528
public AndroidNative(Context context) {
2629
super(context);
@@ -86,47 +89,39 @@ public View getView() {
8689
public void setDividerHeight(int height) {
8790
// not supported
8891
}
89-
92+
9093
@Override
9194
public void setItemPaddingHorizontal(int padding) {
9295
// Not needed for this picker
9396
}
9497

9598
@Override
9699
public boolean isSpinning() {
97-
return state == SCROLL_STATE_FLING;
100+
return state == SCROLL_STATE_FLING || isAnimating;
98101
}
99102

100103
@Override
101104
public void smoothScrollToValue(final int value) {
102105
final AndroidNative self = this;
103106

107+
int currentValue = self.getValue();
108+
if (value == currentValue) return;
109+
int shortestScrollOption = Utils.getShortestScrollOption(currentValue, value, getMaxValue(), getWrapSelectorWheel());
110+
final int moves = Math.abs(shortestScrollOption);
111+
int timeBetweenScrollsMs = 100;
112+
int willStopScrollingInMs = timeBetweenScrollsMs * moves;
113+
isAnimating = true;
104114
new Handler().postDelayed(new Runnable() {
115+
@Override
105116
public void run() {
106-
int currentValue = self.getValue();
107-
if (value == currentValue) return;
108-
int shortestScrollOption = getShortestScrollOption(currentValue, value);
109-
final int moves = Math.abs(shortestScrollOption);
110-
for (int i = 0; i < moves; i++) {
111-
// need some delay between each scroll step to make sure it scrolls to correct value
112-
changeValueByOne(shortestScrollOption > 0, i * 100, i == moves - 1);
113-
}
117+
isAnimating = false;
114118
}
115-
// since the SCROLL_STATE_IDLE event is dispatched before the wheel actually has stopped
116-
// an extra delay has to be added before starting to scroll to correct value
117-
}, 500);
118-
}
119+
}, willStopScrollingInMs);
119120

120-
private int getShortestScrollOption(int currentValue, int value) {
121-
final int maxValue = getMaxValue();
122-
int option1 = value - currentValue;
123-
int option2 = maxValue + 1 - Math.abs(option1);
124-
if (getWrapSelectorWheel()) {
125-
return Math.abs(option1) < Math.abs(option2) ? option1 : option2;
121+
for (int i = 0; i < moves; i++) {
122+
// need some delay between each scroll step to make sure it scrolls to correct value
123+
changeValueByOne(shortestScrollOption > 0, i * timeBetweenScrollsMs, i == moves - 1);
126124
}
127-
if (currentValue + option1 > maxValue) return option2;
128-
if (currentValue + option1 < 0) return option2;
129-
return option1;
130125
}
131126

132127
private void changeValueByOne(final NumberPicker higherPicker, final boolean increment) {
@@ -185,16 +180,16 @@ public void onValueChange(NumberPicker numberPicker, int oldVal, int newVal) {
185180
// to send event during scrolling we make sure wheel is still. This particular
186181
// case happens when wheel is tapped, not scrolled.
187182
if(state == SCROLL_STATE_IDLE) {
188-
sendEventIn500ms();
183+
sendEventIn500ms();
189184
}
190185
}
191186
});
192187

193188
super.setOnScrollListener(new OnScrollListener() {
194189
@Override
195190
public void onScrollStateChange(NumberPicker numberPicker, int nextState) {
196-
sendEventIfStopped(nextState);
197-
state = nextState;
191+
sendEventIfStopped(nextState);
192+
state = nextState;
198193
}
199194
});
200195
}

android/src/main/java/com/henninghall/date_picker/ui/WheelChangeListenerImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ private SimpleDateFormat getDateFormat(){
4040
public void onChange(Wheel picker) {
4141
if(wheels.hasSpinningWheel()) return;
4242

43-
if(!exists()){
43+
if(!dateExists()){
4444
Calendar closestExistingDate = getClosestExistingDateInPast();
4545
if(closestExistingDate != null) {
4646
uiManager.animateToDate(closestExistingDate);
@@ -67,7 +67,7 @@ public void onChange(Wheel picker) {
6767
}
6868

6969
// Example: Jan 1 returns true, April 31 returns false.
70-
private boolean exists(){
70+
private boolean dateExists(){
7171
SimpleDateFormat dateFormat = getDateFormat();
7272
String toParse = wheels.getDateTimeString();
7373
try {

examples/detox/android/app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ dependencies {
7878

7979
androidTestImplementation('com.wix:detox:+') { transitive = true }
8080
androidTestImplementation 'junit:junit:4.12'
81+
testImplementation 'junit:junit:4.12'
8182
}
8283

8384
// Run this once to be able to run the application with BUCK
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
2+
import com.henninghall.date_picker.Utils;
3+
4+
import org.junit.Test;
5+
6+
import static org.junit.Assert.assertEquals;
7+
8+
public class ShortestScrollOption {
9+
10+
@Test
11+
public void decreaseOne() {
12+
assertEquals( -1, Utils.getShortestScrollOption(1, 0, 10, false));
13+
}
14+
15+
@Test
16+
public void increaseOne() {
17+
assertEquals(1, Utils.getShortestScrollOption(0, 1, 10, false));
18+
}
19+
20+
@Test
21+
public void increaseFive() {
22+
assertEquals( 5, Utils.getShortestScrollOption(0, 5, 10, false));
23+
}
24+
25+
@Test
26+
public void noChange() {
27+
assertEquals( 0, Utils.getShortestScrollOption(0, 0, 10, false));
28+
}
29+
30+
@Test
31+
public void noWrapping() {
32+
assertEquals( 10, Utils.getShortestScrollOption(0, 10, 10, false));
33+
}
34+
35+
@Test
36+
public void wrapping() {
37+
assertEquals( -1, Utils.getShortestScrollOption(0, 10, 10, true));
38+
}
39+
40+
@Test
41+
public void findingClosestByIncreaseNoWrap() {
42+
assertEquals( 4, Utils.getShortestScrollOption(0, 4, 9, true));
43+
}
44+
45+
@Test
46+
public void findingClosestByIncreaseWrap() {
47+
assertEquals( 4, Utils.getShortestScrollOption(6, 0, 9, true));
48+
}
49+
50+
@Test
51+
public void findingClosestByDecreaseNoWrap() {
52+
assertEquals( -4, Utils.getShortestScrollOption(5, 1, 9, true));
53+
}
54+
55+
@Test
56+
public void findingClosestByDecreaseWrap() {
57+
assertEquals( -4, Utils.getShortestScrollOption(0, 6, 9, true));
58+
}
59+
60+
}

src/propChecker.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,10 @@ const modeCheck = new PropCheck(
4040
"Invalid mode. Valid modes: 'datetime', 'date', 'time'"
4141
)
4242

43-
const checks = [widthCheck, heightCheck, modeCheck]
43+
const androidVariantCheck = new PropCheck(
44+
props =>
45+
props && props.androidVariant && !['nativeAndroid', 'iosClone'].includes(props.androidVariant),
46+
"Invalid android variant. Valid modes: 'nativeAndroid', 'iosClone'"
47+
)
48+
49+
const checks = [widthCheck, heightCheck, modeCheck, androidVariantCheck]

0 commit comments

Comments
 (0)