Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add set first day week android #902

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,15 @@ Autolinking is not yet implemented on Windows, so [manual installation ](/docs/m
If you are using RN >= 0.60, only run `npx pod-install`. Then rebuild your project.

## React Native Support
Check the `react-native` version support table below to find the corrosponding `datetimepicker` version to meet support requirements.

| react-native version | version |
| -------------------- | -------- |
| 0.73.0+ | 7.6.3+ |
| <=0.72.0 | <=7.6.2 |
| 0.70.0+ | 7.0.1+ |
| <0.70.0 | <=7.0.0 |
Check the `react-native` version support table below to find the corresponding `datetimepicker` version to meet support requirements.

| react-native version | version |
| -------------------- | ------- |
| 0.73.0+ | 7.6.3+ |
| <=0.72.0 | <=7.6.2 |
| 0.70.0+ | 7.0.1+ |
| <0.70.0 | <=7.0.0 |

## Usage

Expand Down Expand Up @@ -424,7 +424,7 @@ Reference: https://docs.microsoft.com/en-us/uwp/api/windows.globalization.dateti
<RNDateTimePicker dateFormat="dayofweek day month" />
```

#### `firstDayOfWeek` (`optional`, `Windows only`)
#### `firstDayOfWeek` (`optional`, `Android and Windows only`)

Indicates which day is shown as the first day of the week.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,11 @@ private Bundle createFragmentArguments(ReadableMap options) {
if (options.hasKey(RNConstants.ARG_TESTID) && !options.isNull(RNConstants.ARG_TESTID)) {
args.putString(RNConstants.ARG_TESTID, options.getString(RNConstants.ARG_TESTID));
}
if (options.hasKey(RNConstants.FIRST_DAY_OF_WEEK) && !options.isNull(RNConstants.FIRST_DAY_OF_WEEK)) {
// FIRST_DAY_OF_WEEK is 0-indexed, since it uses the same constants DAY_OF_WEEK used in the Windows implementation
// Android DatePicker uses 1-indexed values, SUNDAY being 1 and SATURDAY being 7, so the +1 is necessary in this case
args.putInt(RNConstants.FIRST_DAY_OF_WEEK, options.getInt(RNConstants.FIRST_DAY_OF_WEEK)+1);
}
return args;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public final class RNConstants {
public static final String ACTION_TIME_SET = "timeSetAction";
public static final String ACTION_DISMISSED = "dismissedAction";
public static final String ACTION_NEUTRAL_BUTTON = "neutralButtonAction";
public static final String FIRST_DAY_OF_WEEK = "firstDayOfWeek";

/**
* Minimum date supported by {@link TimePickerDialog}, 01 Jan 1900
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,11 @@ private DatePickerDialog createDialog(Bundle args) {
// the date under certain conditions.
datePicker.setMinDate(RNConstants.DEFAULT_MIN_DATE);
}
if (args.containsKey(RNConstants.ARG_MAXDATE)) {
datePicker.setMaxDate(maxDate);

// Only compatible with SDK 21 and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && args.containsKey(RNConstants.FIRST_DAY_OF_WEEK)) {
final int firstDayOfWeek = args.getInt(RNConstants.FIRST_DAY_OF_WEEK);
datePicker.setFirstDayOfWeek(firstDayOfWeek);
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && (args.containsKey(RNConstants.ARG_MAXDATE) || args.containsKey(RNConstants.ARG_MINDATE))) {
Expand Down
53 changes: 51 additions & 2 deletions example/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export const App = () => {
const [maxDate] = useState(new Date('2021'));
const [minDate] = useState(new Date('2018'));
const [is24Hours, set24Hours] = useState(false);
const [firstDayOfWeek, setFirstDayOfWeek] = useState(DAY_OF_WEEK.Monday);
const [firstDayOfWeek, setFirstDayOfWeek] = useState(DAY_OF_WEEK.Sunday);
const [dateFormat, setDateFormat] = useState('longdate');
const [dayOfWeekFormat, setDayOfWeekFormat] = useState(
'{dayofweek.abbreviated(2)}',
Expand Down Expand Up @@ -168,7 +168,7 @@ export const App = () => {
: `${item} mins`
: item;
return (
<View style={{marginHorizontal: 1}}>
<View style={{marginHorizontal: 1}} testID={`${item}`}>
<Button
title={title || 'undefined'}
onPress={() => {
Expand All @@ -180,6 +180,20 @@ export const App = () => {
);
};

const renderDayOfWeekItem = ({item}) => {
const key = item[0];
const value = item[1];
return (
<View style={{marginHorizontal: 1}} testID={`${key}`}>
<Button
title={`${key}`}
value={value}
onPress={() => setFirstDayOfWeek(value)}
/>
</View>
);
};

const toggleMinMaxDateInUTC = () => {
setTzOffsetInMinutes(0);
setTzName(undefined);
Expand Down Expand Up @@ -253,6 +267,13 @@ export const App = () => {
/>
</>
)}
<Info
testID={'firstDayOfWeek'}
title={'First Day of Week:'}
body={`${Object.keys(DAY_OF_WEEK).find(
(key) => DAY_OF_WEEK[key] === firstDayOfWeek,
)}`}
/>
</View>
</View>
<ScrollView
Expand Down Expand Up @@ -334,6 +355,27 @@ export const App = () => {
testID="neutralButtonLabelTextInput"
/>
</View>

<View
style={{
flexDirection: 'column',
flexWrap: 'wrap',
paddingBottom: 10,
}}>
<ThemedText style={styles.textLabel}>
firstDayOfWeek (android only)
</ThemedText>
<View style={styles.firstDayOfWeekContainer}>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mentioned you needed to add scrolling. Can we make this more compact in terms of height?

I'd prefer if we can avoid scrolling because it's annoying to think about it when maintaining tests.

Also the code seems fit to be replaced by a array.map

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First off, thank you for the lovely comments you've made, they made my day! 😄

Now, with regards to this, Ive implemented a FlatList design for selecting the DAY_OF_WEEK variable (similar to the "timezone" FlatList further down) but, sadly, it seems most of the tests would still need to have some level of scrolling to access the UI elements... Either way I will push the changes shortly and you can check them out!

I've gone through most of the tests and I think it might be reasonable to implement a "ScrollToElement" function that allows you to scroll to the necessary UI element before interacting with it. This would also reduce the size and repetitiveness of some of the tests and hopefully aid with maintainability. I was thinking of maybe creating a separate PR going over them and addressing some of the issues. Let me know what you think!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me, though if we can avoid scrolling, that'd be best imho. I see that now we run in iphone 14. A low-effort fix could be just bumping to a larger device.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will revert the changes to the tests that add scrolling and see if I can find a valid device for iOS and Android that will pass the tests without scrolling, will get back to you shortly

<FlatList
testID="firstDayOfWeekSelector"
style={{marginBottom: 10}}
horizontal={true}
renderItem={renderDayOfWeekItem}
data={Object.entries(DAY_OF_WEEK)}
/>
</View>
</View>

<View style={styles.header}>
<ThemedText style={styles.textLabel}>
[android] show and dismiss picker after 3 secs
Expand Down Expand Up @@ -410,6 +452,7 @@ export const App = () => {
neutralButton={{label: neutralButtonLabel}}
negativeButton={{label: 'Cancel', textColor: 'red'}}
disabled={disabled}
firstDayOfWeek={firstDayOfWeek}
/>
)}
</View>
Expand Down Expand Up @@ -631,6 +674,12 @@ const styles = StyleSheet.create({
paddingTop: 10,
width: 350,
},
firstDayOfWeekContainer: {
flexDirection: 'row',
justifyContent: 'center',
flexWrap: 'wrap',
gap: 5,
},
});

export default App;
77 changes: 68 additions & 9 deletions example/e2e/detoxTest.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ const {
getDatePickerAndroid,
getDateTimePickerControlIOS,
getInlineTimePickerIOS,
getDatePickerButtonIOS,
} = require('./utils/matchers');
const {
userChangesTimeValue,
userOpensPicker,
userTapsCancelButtonAndroid,
userTapsOkButtonAndroid,
userDismissesCompactDatePicker,
userSelectsDayInCalendar,
userSwipesTimezoneListUntilDesiredIsVisible,
} = require('./utils/actions');
const {isIOS, isAndroid, wait, Platform} = require('./utils/utils');
const {device} = require('detox');
Expand Down Expand Up @@ -63,15 +65,15 @@ describe('e2e tests', () => {
await userOpensPicker({mode: 'date', display: 'default'});

if (isIOS()) {
await element(
by.traits(['staticText']).withAncestor(by.label('Date Picker')),
).tap();
await elementById('DateTimePickerScrollView').scrollTo('bottom');
await getDatePickerButtonIOS().tap();

// 'label' maps to 'description' in view hierarchy debugger
const nextMonthArrow = element(by.label('Next Month'));

await nextMonthArrow.tap();
await nextMonthArrow.tap();
await userDismissesCompactDatePicker();
await getDatePickerButtonIOS().tap();
} else {
const calendarHorizontalScrollView = element(
by
Expand Down Expand Up @@ -119,9 +121,11 @@ describe('e2e tests', () => {
ios: 'inline',
android: 'default',
});
await elementById('DateTimePickerScrollView').scrollTo('top');
await userOpensPicker({mode: 'time', display});

if (isIOS()) {
await elementById('DateTimePickerScrollView').scrollTo('bottom');
await expect(getInlineTimePickerIOS()).toBeVisible();
} else {
await expect(element(by.type('android.widget.TimePicker'))).toBeVisible();
Expand Down Expand Up @@ -165,16 +169,19 @@ describe('e2e tests', () => {

await expect(elementById('overriddenTzName')).toHaveText('Europe/Prague');

await elementById('timezone').swipe('left', 'fast', 0.5);
await elementById('DateTimePickerScrollView').scrollTo('bottom');

let timeZone = 'America/Vancouver';
await waitFor(elementById('timezone')).toBeVisible().withTimeout(1000);
JoshBarnesD marked this conversation as resolved.
Show resolved Hide resolved
await userSwipesTimezoneListUntilDesiredIsVisible(timeZone);

if (isAndroid()) {
timeZone = timeZone.toUpperCase();
}

await waitFor(elementByText(timeZone)).toBeVisible().withTimeout(1000);

await elementByText(timeZone).tap();
await elementByText(timeZone).multiTap(2);

await assertTimeLabels({
utcTime: '2021-11-13T01:00:00Z',
Expand All @@ -188,17 +195,21 @@ describe('e2e tests', () => {
});

it('daylight saving should work properly', async () => {
await elementById('timezone').swipe('left', 'fast', 0.5);
await elementById('DateTimePickerScrollView').scrollTo('bottom');

let timeZone = 'America/Vancouver';
await waitFor(elementById('timezone')).toBeVisible().withTimeout(1000);
await userSwipesTimezoneListUntilDesiredIsVisible(timeZone);

if (isAndroid()) {
timeZone = timeZone.toUpperCase();
}

await waitFor(elementByText(timeZone)).toBeVisible().withTimeout(1000);

await elementByText(timeZone).tap();
await elementByText(timeZone).multiTap(2);

await elementById('DateTimePickerScrollView').scrollTo('top');
await userOpensPicker({mode: 'date', display: getPickerDisplay()});

if (isIOS()) {
Expand Down Expand Up @@ -228,6 +239,7 @@ describe('e2e tests', () => {
await uiDevice.pressEnter();
await userTapsOkButtonAndroid();

await elementById('DateTimePickerScrollView').scrollTo('top');
await userOpensPicker({mode: 'time', display: getPickerDisplay()});
await userChangesTimeValue({hours: '2', minutes: '0'});
await userTapsOkButtonAndroid();
Expand Down Expand Up @@ -275,6 +287,8 @@ describe('e2e tests', () => {
tzOffsetPreset = tzOffsetPreset.toUpperCase();
}

await elementById('DateTimePickerScrollView').scrollTo('top');

await userOpensPicker({
mode: 'time',
display: getPickerDisplay(),
Expand Down Expand Up @@ -310,7 +324,9 @@ describe('e2e tests', () => {
await expect(elementById('utcTime')).toHaveText('2021-11-13T00:00:00Z');

// Ensure you can select tomorrow (iOS)
await elementById('DateTimePickerScrollView').scrollTo('top');
await userOpensPicker({mode: 'date', display: getPickerDisplay()});
await elementById('DateTimePickerScrollView').scrollTo('bottom');
await testElement.setDatePickerDate('2021-11-14T01:00:00Z', 'ISO8601');
} else {
const uiDevice = device.getUiDevice();
Expand Down Expand Up @@ -445,4 +461,47 @@ describe('e2e tests', () => {
});
});
});

describe(':android: firstDayOfWeek functionality', () => {
JoshBarnesD marked this conversation as resolved.
Show resolved Hide resolved
it.each([
{
firstDayOfWeekIn: 'Sunday',
selectDayPositions: {xPosIn: -2, yPosIn: 4},
},
{
firstDayOfWeekIn: 'Tuesday',
selectDayPositions: {xPosIn: 3, yPosIn: 3},
},
])(
':android: picker should have $firstDayOfWeekIn as firstDayOfWeek and select Sunday date',
async ({firstDayOfWeekIn, selectDayPositions}) => {
const targetDate = '2021-11-07T01:00:00Z';
const targetDateWithTZ = '2021-11-07T02:00:00+01:00';

await userOpensPicker({
mode: 'date',
display: getPickerDisplay(),
firstDayOfWeek: firstDayOfWeekIn,
});
await expect(getDatePickerAndroid()).toBeVisible();

const uiDevice = device.getUiDevice();
await userSelectsDayInCalendar(uiDevice, {
xPos: selectDayPositions.xPosIn,
yPos: selectDayPositions.yPosIn,
});

await userTapsOkButtonAndroid();

await expect(elementById('firstDayOfWeek')).toHaveText(
firstDayOfWeekIn,
);

await assertTimeLabels({
utcTime: targetDate,
deviceTime: targetDateWithTZ,
});
},
);
});
});
Loading