-
Notifications
You must be signed in to change notification settings - Fork 57
/
index.tsx
203 lines (179 loc) · 5.36 KB
/
index.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
import React, {
useEffect,
useRef,
useState,
} from 'react';
import {
Keyboard,
PanResponder,
StyleProp,
View,
ViewStyle,
} from 'react-native';
import {
defaultTimeoutHandler,
TimeoutHandler,
useTimeout,
} from 'usetimeout-react-hook';
const defaultTimeForInactivity = 10000;
const defaultStyle: ViewStyle = {
flex: 1,
};
export interface UserInactivityProps<T = unknown> {
/**
* Number of milliseconds after which the view is considered inactive.
* If it changed, the timer restarts and the view is considered active until
* the new timer expires.
* It defaults to 1000.
*/
timeForInactivity?: number;
/**
* If it's explicitly set to `true` after the component has already been initialized,
* the timer restarts and the view is considered active until the new timer expires.
* It defaults to true.
*/
isActive?: boolean;
/**
* Generic usetimeout-react-hook's TimeoutHandler implementation.
* It defaults to the standard setTimeout/clearTimeout implementation.
* See https://github.com/jkomyno/usetimeout-react-hook/#-how-to-use.
*/
timeoutHandler?: TimeoutHandler<T>;
/**
* Children components to embed inside UserInactivity's View.
* If any children component is pressed, `onAction` is called after
* `timeForInactivity` milliseconds.
*/
children: React.ReactNode;
/**
* If set to true, the timer is not reset when the keyboard appears
* or disappears.
*/
skipKeyboard?: boolean;
/**
* Optional custom style for UserInactivity's View.
* It defaults to { flex: 1 }.
*/
style?: StyleProp<ViewStyle>;
/**
* Callback triggered anytime UserInactivity's View isn't touched for more than
* `timeForInactivity` seconds.
* It's `active` argument is true if and only if the View wasn't touched for more
* than `timeForInactivity` milliseconds.
*/
onAction: (active: boolean) => void;
}
const UserInactivity: React.FC<UserInactivityProps> = ({
children,
isActive,
onAction,
skipKeyboard,
style,
timeForInactivity,
timeoutHandler,
}) => {
const actualStyle = style || defaultStyle;
/**
* If the user has provided a custom timeout handler, it is used directly,
* otherwise it defaults to the default timeout handler (setTimeout/clearTimeout).
*/
const actualTimeoutHandler = timeoutHandler || defaultTimeoutHandler;
const timeout = timeForInactivity || defaultTimeForInactivity;
/**
* If the `isActive` prop is manually changed to `true`, call `resetTimerDueToActivity`
* to reset the timer and set the current state to active until the timeout expires.
* If the `isActive` is changed to `false`, nothing is done.
* Note however that toggling `isActive` manually is discouraged for normal use.
* It should only be used in those cases where React Native doesnt't seem to
* inform the `PanResponder` instance about touch events, such as when tapping
* over the keyboard.
*/
const initialActive = isActive === undefined ? true : isActive;
const [active, setActive] = useState(initialActive);
useEffect(() => {
if (isActive) {
resetTimerDueToActivity();
}
}, [isActive]);
const [date, setDate] = useState(Date.now());
/**
* The timeout is reset when either `date` or `timeout` change.
*/
const cancelTimer = useTimeout(() => {
setActive(false);
onAction(false);
// @ts-ignore
}, timeout, actualTimeoutHandler, [date, timeout]);
const isFirstRender = useRef(true);
/**
* Triggers `onAction` each time the `active` state turns true
* after the initial render.
*/
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
} else {
if (active) {
onAction(true);
}
}
}, [active]);
/**
* Resets the timer every time the keyboard appears or disappears,
* unless skipKeyboard is true.
*/
useEffect(() => {
if (skipKeyboard) {
return;
}
const hideEvent = Keyboard.addListener('keyboardDidHide', resetTimerDueToActivity);
const showEvent = Keyboard.addListener('keyboardDidShow', resetTimerDueToActivity);
// release event listeners on destruction
return () => {
hideEvent.remove();
showEvent.remove();
};
}, []);
/**
* This method is called whenever a touch is detected. If no touch is
* detected after `this.props.timeForInactivity` milliseconds, then
* `this.state.inactive` turns to true.
*/
function resetTimerDueToActivity() {
cancelTimer();
setActive(true);
/**
* Causes `useTimeout` to restart.
*/
setDate(Date.now());
}
/**
* In order not to steal any touches from the children components, this method
* must return false.
*/
function resetTimerForPanResponder(/* event: GestureResponderEvent */) {
// const { identifier: touchID } = event.nativeEvent;
resetTimerDueToActivity();
return false;
}
/**
* The PanResponder instance is initialized only once.
*/
const [panResponder, _] = useState(
PanResponder.create({
onMoveShouldSetPanResponderCapture: resetTimerForPanResponder,
onPanResponderTerminationRequest: resetTimerForPanResponder,
onStartShouldSetPanResponderCapture: resetTimerForPanResponder,
}),
);
return (
<View
style={actualStyle}
collapsable={false}
{...panResponder.panHandlers}
>
{children}
</View>
);
};
export default UserInactivity;