1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-11-24 08:12:24 +02:00

Fixed Select Alarm dialog and PoorManIntervals class

This commit is contained in:
Laurent Cozic 2020-10-13 21:54:52 +01:00
parent 8296676fd5
commit 1077ad8f16
15 changed files with 273 additions and 204 deletions

View File

@ -201,6 +201,7 @@ ReactNativeClient/lib/commands/historyForward.js
ReactNativeClient/lib/commands/synchronize.js
ReactNativeClient/lib/components/BackButtonDialogBox.js
ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js
ReactNativeClient/lib/components/SelectDateTimeDialog.js
ReactNativeClient/lib/errorUtils.js
ReactNativeClient/lib/eventManager.js
ReactNativeClient/lib/hooks/useEffectDebugger.js
@ -218,6 +219,7 @@ ReactNativeClient/lib/markdownUtils.js
ReactNativeClient/lib/models/Alarm.js
ReactNativeClient/lib/models/Setting.js
ReactNativeClient/lib/ntpDate.js
ReactNativeClient/lib/PoorManIntervals.js
ReactNativeClient/lib/reducer.js
ReactNativeClient/lib/services/AlarmService.js
ReactNativeClient/lib/services/AlarmServiceDriver.android.js

2
.gitignore vendored
View File

@ -195,6 +195,7 @@ ReactNativeClient/lib/commands/historyForward.js
ReactNativeClient/lib/commands/synchronize.js
ReactNativeClient/lib/components/BackButtonDialogBox.js
ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js
ReactNativeClient/lib/components/SelectDateTimeDialog.js
ReactNativeClient/lib/errorUtils.js
ReactNativeClient/lib/eventManager.js
ReactNativeClient/lib/hooks/useEffectDebugger.js
@ -212,6 +213,7 @@ ReactNativeClient/lib/markdownUtils.js
ReactNativeClient/lib/models/Alarm.js
ReactNativeClient/lib/models/Setting.js
ReactNativeClient/lib/ntpDate.js
ReactNativeClient/lib/PoorManIntervals.js
ReactNativeClient/lib/reducer.js
ReactNativeClient/lib/services/AlarmService.js
ReactNativeClient/lib/services/AlarmServiceDriver.android.js

View File

@ -17,9 +17,14 @@ LogBox.ignoreLogs([
// What's the point of printing warnings for non-user code. Of all the things that
// are broken and unreliable in React Native, require cycles are just a minor annoyance
// which shouldn't forever print warnings.
// To make it more fun, they don't normalise paths so forward slashes and backward slashes
// need to be handled to support Windows.
'Require cycle: node_modules/react-native-',
'Require cycle: node_modules\\react-native-',
'Require cycle: node_modules/rn-fetch-blob',
'Require cycle: node_modules\\rn-fetch-blob',
'Require cycle: node_modules/aws-sdk',
'Require cycle: node_modules\\aws-sdk',
// It's being updated over time and we don't need to see these warnings all the time
'Warning: componentWillReceiveProps has been renamed',

View File

@ -0,0 +1,94 @@
const { time } = require('lib/time-utils.js');
type IntervalId = number;
interface Interval {
id: IntervalId,
callback: Function,
interval: number,
lastIntervalTime: number,
isTimeout: boolean,
}
interface Intervals {
[key: number]: Interval;
}
export default class PoorManIntervals {
private static maxNativeTimerDuration_ = 10 * 1000;
private static lastUpdateTime_:number = 0;
private static intervalId_:IntervalId = 0;
private static intervals_:Intervals = {};
public static setInterval(callback:Function, interval:number):IntervalId {
if (interval <= this.maxNativeTimerDuration_) return setInterval(callback, interval);
this.intervalId_++;
const id = this.intervalId_;
this.intervals_[id] = {
id: id,
callback: callback,
interval: interval,
lastIntervalTime: time.unixMs(),
isTimeout: false,
};
return id;
}
public static setTimeout(callback:Function, interval:number):IntervalId {
if (interval <= this.maxNativeTimerDuration_) return setTimeout(callback, interval);
this.intervalId_++;
const id = this.intervalId_;
this.intervals_[id] = {
id: id,
callback: callback,
interval: interval,
lastIntervalTime: time.unixMs(),
isTimeout: true,
};
return id;
}
public static clearInterval(id:IntervalId) {
const r = this.intervals_[id];
if (!r) {
clearInterval(id);
} else {
delete this.intervals_[id];
}
}
public static clearTimeout(id:IntervalId) {
const r = this.intervals_[id];
if (!r) {
clearTimeout(id);
} else {
delete this.intervals_[id];
}
}
public static update() {
// Don't update more than once a second
if (this.lastUpdateTime_ + 1000 > time.unixMs()) return;
for (const id in this.intervals_) {
const interval = this.intervals_[id];
const now = time.unixMs();
if (now - interval.lastIntervalTime >= interval.interval) {
interval.lastIntervalTime = now;
interval.callback();
if (interval.isTimeout) {
this.clearTimeout(interval.id);
}
}
}
this.lastUpdateTime_ = time.unixMs();
}
}

View File

@ -0,0 +1,125 @@
import * as React from 'react';
import { View, Button, Text } from 'react-native';
import { themeStyle } from 'lib/theme';
import { _ } from 'lib/locale';
const PopupDialog = require('react-native-popup-dialog').default;
const { DialogTitle, DialogButton } = require('react-native-popup-dialog');
const { time } = require('lib/time-utils.js');
const DateTimePickerModal = require('react-native-modal-datetime-picker').default;
export default class SelectDateTimeDialog extends React.PureComponent<any, any> {
private dialog_:any = null;
private shown_:boolean = false;
constructor(props:any) {
super(props);
this.state = {
date: null,
mode: 'date',
showPicker: false,
};
this.onReject = this.onReject.bind(this);
this.onPickerConfirm = this.onPickerConfirm.bind(this);
this.onPickerCancel = this.onPickerCancel.bind(this);
this.onSetDate = this.onSetDate.bind(this);
}
UNSAFE_componentWillReceiveProps(newProps:any) {
if (newProps.date != this.state.date) {
this.setState({ date: newProps.date });
}
if ('shown' in newProps && newProps.shown != this.shown_) {
this.show(newProps.shown);
}
}
show(doShow:boolean = true) {
if (doShow) {
this.dialog_.show();
} else {
this.dialog_.dismiss();
}
this.shown_ = doShow;
}
dismiss() {
this.show(false);
}
onAccept() {
if (this.props.onAccept) this.props.onAccept(this.state.date);
}
onReject() {
if (this.props.onReject) this.props.onReject();
}
onClear() {
if (this.props.onAccept) this.props.onAccept(null);
}
onPickerConfirm(selectedDate:Date) {
this.setState({ date: selectedDate, showPicker: false });
}
onPickerCancel() {
this.setState({ showPicker: false });
}
onSetDate() {
this.setState({ showPicker: true });
}
renderContent() {
if (!this.shown_) return <View/>;
const theme = themeStyle(this.props.themeId);
return (
<View style={{ flex: 1, margin: 20, alignItems: 'center' }}>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
{ this.state.date && <Text style={{ ...theme.normalText, marginRight: 10 }}>{time.formatDateToLocal(this.state.date)}</Text> }
<Button title="Set date" onPress={this.onSetDate} />
</View>
<DateTimePickerModal
date={this.state.date ? this.state.date : new Date()}
is24Hour={time.use24HourFormat()}
isVisible={this.state.showPicker}
mode="datetime"
onConfirm={this.onPickerConfirm}
onCancel={this.onPickerCancel}
/>
</View>
);
}
render() {
const clearAlarmText = _('Clear alarm'); // For unknown reasons, this particular string doesn't get translated if it's directly in the text property below
const popupActions = [
<DialogButton text={_('Save alarm')} align="center" onPress={() => this.onAccept()} key="saveButton" />,
<DialogButton text={clearAlarmText} align="center" onPress={() => this.onClear()} key="clearButton" />,
<DialogButton text={_('Cancel')} align="center" onPress={() => this.onReject()} key="cancelButton" />,
];
return (
<PopupDialog
ref={(dialog:any) => { this.dialog_ = dialog; }}
dialogTitle={<DialogTitle title={_('Set alarm')} />}
actions={popupActions}
dismissOnTouchOutside={false}
width={0.9}
height={350}
>
{this.renderContent()}
</PopupDialog>
);
}
}

View File

@ -481,7 +481,7 @@ class ScreenHeaderComponent extends React.PureComponent {
!menuOptionComponents.length || !showContextMenuButton ? null : (
<Menu onSelect={value => this.menu_select(value)} style={this.styles().contextMenu}>
<MenuTrigger style={contextMenuStyle}>
<Icon name="more-vert" style={this.styles().contextMenuTrigger} />
<Icon name="md-ellipsis-vertical" style={this.styles().contextMenuTrigger} />
</MenuTrigger>
<MenuOptions>
<ScrollView style={{ maxHeight: windowHeight }}>{menuOptionComponents}</ScrollView>

View File

@ -37,7 +37,7 @@ const { DocumentPicker, DocumentPickerUtil } = require('react-native-document-pi
const ImageResizer = require('react-native-image-resizer').default;
const shared = require('lib/components/shared/note-screen-shared.js');
const ImagePicker = require('react-native-image-picker');
const { SelectDateTimeDialog } = require('lib/components/select-date-time-dialog.js');
const SelectDateTimeDialog = require('lib/components/SelectDateTimeDialog').default;
const ShareExtension = require('lib/ShareExtension.js').default;
const CameraView = require('lib/components/CameraView');
const urlUtils = require('lib/urlUtils');
@ -1178,7 +1178,7 @@ class NoteScreenComponent extends BaseScreenComponent {
{bodyComponent}
{!this.useBetaEditor() && actionButtonComp}
<SelectDateTimeDialog shown={this.state.alarmDialogShown} date={dueDate} onAccept={this.onAlarmDialogAccept} onReject={this.onAlarmDialogReject} />
<SelectDateTimeDialog themeId={this.props.themeId} shown={this.state.alarmDialogShown} date={dueDate} onAccept={this.onAlarmDialogAccept} onReject={this.onAlarmDialogReject} />
<DialogBox
ref={dialogbox => {

View File

@ -1,109 +0,0 @@
import React from 'react';
import { View } from 'react-native';
import PopupDialog, { DialogTitle, DialogButton } from 'react-native-popup-dialog';
import DatePicker from 'react-native-datepicker';
import moment from 'moment';
import { _ } from 'lib/locale.js';
const { time } = require('lib/time-utils.js');
class SelectDateTimeDialog extends React.PureComponent {
constructor() {
super();
this.dialog_ = null;
this.shown_ = false;
this.state = { date: null };
this.onReject = this.onReject.bind(this);
}
UNSAFE_componentWillReceiveProps(newProps) {
if (newProps.date != this.state.date) {
this.setState({ date: newProps.date });
}
if ('shown' in newProps && newProps.shown != this.shown_) {
this.show(newProps.shown);
}
}
show(doShow = true) {
if (doShow) {
this.dialog_.show();
} else {
this.dialog_.dismiss();
}
this.shown_ = doShow;
}
dismiss() {
this.show(false);
}
dateTimeFormat() {
return time.dateTimeFormat();
}
stringToDate(s) {
return moment(s, this.dateTimeFormat()).toDate();
}
onAccept() {
if (this.props.onAccept) this.props.onAccept(this.state.date);
}
onReject() {
if (this.props.onReject) this.props.onReject();
}
onClear() {
if (this.props.onAccept) this.props.onAccept(null);
}
render() {
const clearAlarmText = _('Clear alarm'); // For unknown reasons, this particular string doesn't get translated if it's directly in the text property below
const popupActions = [
<DialogButton text={_('Save alarm')} align="center" onPress={() => this.onAccept()} key="saveButton" />,
<DialogButton text={clearAlarmText} align="center" onPress={() => this.onClear()} key="clearButton" />,
<DialogButton text={_('Cancel')} align="center" onPress={() => this.onReject()} key="cancelButton" />,
];
return (
<PopupDialog
ref={(dialog) => { this.dialog_ = dialog; }}
dialogTitle={<DialogTitle title={_('Set alarm')} />}
actions={popupActions}
dismissOnTouchOutside={false}
width={0.9}
height={350}
>
<View style={{ flex: 1, margin: 20, alignItems: 'center' }}>
<DatePicker
date={this.state.date}
mode="datetime"
placeholder={_('Select date')}
format={this.dateTimeFormat()}
confirmBtnText={_('Confirm')}
cancelBtnText={_('Cancel')}
onDateChange={(date) => { this.setState({ date: this.stringToDate(date) }); }}
style={{ width: 300 }}
customStyles={{
btnConfirm: {
paddingVertical: 0,
},
btnCancel: {
paddingVertical: 0,
},
}}
/>
</View>
</PopupDialog>
);
}
}
// eslint-disable-next-line import/prefer-default-export
export { SelectDateTimeDialog };

View File

@ -1,71 +0,0 @@
const { time } = require('lib/time-utils.js');
class PoorManIntervals {
static setInterval(callback, interval) {
PoorManIntervals.intervalId_++;
PoorManIntervals.intervals_.push({
id: PoorManIntervals.intervalId_,
callback: callback,
interval: interval,
lastIntervalTime: time.unixMs(),
});
return PoorManIntervals.intervalId_;
}
static setTimeout(callback, interval) {
PoorManIntervals.intervalId_++;
PoorManIntervals.intervals_.push({
id: PoorManIntervals.intervalId_,
callback: callback,
interval: interval,
lastIntervalTime: time.unixMs(),
oneOff: true,
});
return PoorManIntervals.intervalId_;
}
static intervalById(id) {
for (let i = 0; i < PoorManIntervals.intervals_.length; i++) {
if (PoorManIntervals.intervals_[i].id == id) return PoorManIntervals.intervals_[id];
}
return null;
}
static clearInterval(id) {
for (let i = 0; i < PoorManIntervals.intervals_.length; i++) {
if (PoorManIntervals.intervals_[i].id == id) {
PoorManIntervals.intervals_.splice(i, 1);
break;
}
}
}
static update() {
// Don't update more than once a second
if (PoorManIntervals.lastUpdateTime_ + 1000 > time.unixMs()) return;
for (let i = 0; i < PoorManIntervals.intervals_.length; i++) {
const interval = PoorManIntervals.intervals_[i];
const now = time.unixMs();
if (now - interval.lastIntervalTime >= interval.interval) {
interval.lastIntervalTime = now;
interval.callback();
if (interval.oneOff) {
this.clearInterval(interval.id);
}
}
}
PoorManIntervals.lastUpdateTime_ = time.unixMs();
}
}
PoorManIntervals.lastUpdateTime_ = 0;
PoorManIntervals.intervalId_ = 0;
PoorManIntervals.intervals_ = [];
module.exports = { PoorManIntervals };

View File

@ -1,6 +1,6 @@
const shim = require('lib/shim').default;
const { GeolocationReact } = require('lib/geolocation-react.js');
const { PoorManIntervals } = require('lib/poor-man-intervals.js');
const PoorManIntervals = require('lib/PoorManIntervals').default;
const RNFetchBlob = require('rn-fetch-blob').default;
const { generateSecureRandom } = require('react-native-securerandom');
const FsDriverRN = require('lib/fs-driver-rn.js').FsDriverRN;
@ -18,8 +18,6 @@ const injectedJs = {
function shimInit() {
shim.Geolocation = GeolocationReact;
shim.setInterval = PoorManIntervals.setInterval;
shim.clearInterval = PoorManIntervals.clearInterval;
shim.sjclModule = require('lib/vendor/sjcl-rn.js');
shim.fsDriver = () => {
@ -199,19 +197,19 @@ function shimInit() {
};
shim.setTimeout = (fn, interval) => {
return setTimeout(fn, interval);
return PoorManIntervals.setTimeout(fn, interval);
};
shim.setInterval = (fn, interval) => {
return setInterval(fn, interval);
return PoorManIntervals.setInterval(fn, interval);
};
shim.clearTimeout = (id) => {
return clearTimeout(id);
return PoorManIntervals.clearTimeout(id);
};
shim.clearInterval = (id) => {
return clearInterval(id);
return PoorManIntervals.clearInterval(id);
};
}

View File

@ -33,6 +33,14 @@ class Time {
this.timeFormat_ = v;
}
use24HourFormat() {
return this.timeFormat() ? this.timeFormat().includes('HH') : true;
}
formatDateToLocal(date, format = null) {
return this.formatMsToLocal(date.getTime(), format);
}
dateTimeFormat() {
return `${this.dateFormat()} ${this.timeFormat()}`;
}

View File

@ -1435,6 +1435,14 @@
"resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-4.10.1.tgz",
"integrity": "sha512-ael2f1onoPF3vF7YqHGWy7NnafzGu+yp88BbFbP0ydoCP2xGSUzmZVw0zakPTC040Id+JQ9WeFczujMkDy6jYQ=="
},
"@react-native-community/datetimepicker": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-3.0.3.tgz",
"integrity": "sha512-znltGqSyELRpS/JchT/6MraTRqC83INekEyAkH+0gBrDzv0SwFKwVWacv1tF5D1y5VrHcSrcFB3T/tBsksUgwA==",
"requires": {
"invariant": "^2.2.4"
}
},
"@react-native-community/eslint-config": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@react-native-community/eslint-config/-/eslint-config-2.0.0.tgz",
@ -10765,14 +10773,6 @@
"prop-types": "^15.6.2"
}
},
"react-native-datepicker": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/react-native-datepicker/-/react-native-datepicker-1.7.2.tgz",
"integrity": "sha1-WNCCJZGgrJsyq6CCZQIioO4pZp0=",
"requires": {
"moment": "^2.22.0"
}
},
"react-native-device-info": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-6.2.0.tgz",
@ -10870,6 +10870,14 @@
"commander": "^2.19.0"
}
},
"react-native-modal-datetime-picker": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/react-native-modal-datetime-picker/-/react-native-modal-datetime-picker-9.0.0.tgz",
"integrity": "sha512-8856SLrlVokhBaNWQmkwKbrwjfZP0AfRFIaA4CGXtXq83Hd+mvdmGbwKSfrAGQspAsqFMi7CYxSf4s0n16xCeg==",
"requires": {
"prop-types": "^15.7.2"
}
},
"react-native-popup-dialog": {
"version": "0.9.41",
"resolved": "https://registry.npmjs.org/react-native-popup-dialog/-/react-native-popup-dialog-0.9.41.tgz",

View File

@ -10,6 +10,7 @@
"lint": "eslint ."
},
"dependencies": {
"@react-native-community/datetimepicker": "^3.0.3",
"@react-native-community/geolocation": "^2.0.2",
"@react-native-community/slider": "^3.0.3",
"async-mutex": "^0.1.3",
@ -56,7 +57,6 @@
"react-native": "0.63.3",
"react-native-action-button": "^2.8.5",
"react-native-camera": "^3.40.0",
"react-native-datepicker": "^1.7.2",
"react-native-device-info": "^6.2.0",
"react-native-dialogbox": "^0.6.10",
"react-native-document-picker": "^4.0.0",
@ -65,6 +65,7 @@
"react-native-fs": "^2.16.6",
"react-native-image-picker": "^2.3.4",
"react-native-image-resizer": "^1.3.0",
"react-native-modal-datetime-picker": "^9.0.0",
"react-native-popup-dialog": "^0.9.41",
"react-native-popup-menu": "^0.10.0",
"react-native-quick-actions": "^0.3.13",

View File

@ -54,7 +54,7 @@ const { DatabaseDriverReactNative } = require('lib/database-driver-react-native'
const { reg } = require('lib/registry.js');
const { setLocale, closestSupportedLocale, defaultLocale } = require('lib/locale');
const RNFetchBlob = require('rn-fetch-blob').default;
const { PoorManIntervals } = require('lib/poor-man-intervals.js');
const PoorManIntervals = require('lib/PoorManIntervals').default;
const reducer = require('lib/reducer').default;
const { defaultState } = require('lib/reducer');
const { FileApiDriverLocal } = require('lib/file-api-driver-local.js');

View File

@ -140,7 +140,6 @@
"./ReactNativeClient/fastlane/Preview.html": true,
"./ReactNativeClient/fastlane/screenshots": true,
"./ReactNativeClient/android/build/": true,
// "./ReactNativeClient/android/app/build*": true,
"./ReactNativeClient/android/.project": true,
"./ReactNativeClient/**/android/.settings/": true,
"./ReactNativeClient/android/app/.classpath": true,
@ -494,8 +493,6 @@
"ReactNativeClient/fastlane/report.xml": true,
"ReactNativeClient/fastlane/Preview.html": true,
"ReactNativeClient/fastlane/screenshots": true,
// "ReactNativeClient/android/build*": true,
// "ReactNativeClient/android/app/build*": true,
"ReactNativeClient/android/.project": true,
"ReactNativeClient/**/android/.settings/": true,
"ReactNativeClient/android/app/.classpath": true,
@ -696,7 +693,16 @@
"ReactNativeClient/lib/markdownUtils.js": true,
"ReactNativeClient/lib/services/plugins/api/types.js": true,
"ReactNativeClient/lib/services/rest/Api.js": true,
"CliClient/tests/services_rest_Api.js": true
"CliClient/tests/services_rest_Api.js": true,
"ReactNativeClient/*/fastlane/report.xml": true,
"ReactNativeClient/*/fastlane/Preview.html": true,
"ReactNativeClient/*/fastlane/screenshots": true,
"ReactNativeClient/**/*.jsbundle": true,
"ReactNativeClient/ios/Pods/": true,
"QtClient/build-*": true,
"ReactNativeClient/lib/components/BackButtonDialogBox.js": true,
"ReactNativeClient/lib/components/SelectDateTimeDialog.js": true,
"ReactNativeClient/lib/PoorManIntervals.js": true
},
"spellright.language": [
"en"