react-native-in-app-debugger 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Api.jsx +131 -62
- package/README.md +3 -119
- package/index.jsx +116 -50
- package/package.json +6 -3
package/Api.jsx
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
import React, { useState } from
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Text,
|
|
4
|
+
SectionList,
|
|
5
|
+
TextInput,
|
|
6
|
+
View,
|
|
7
|
+
Alert,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
TouchableOpacity,
|
|
10
|
+
} from "react-native";
|
|
11
|
+
// import Clipboard from '@react-native-clipboard/clipboard';
|
|
12
|
+
let Clipboard;
|
|
13
|
+
try {
|
|
14
|
+
Clipboard = require("@react-native-clipboard/clipboard");
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error("Error importing Clipboard:", error);
|
|
17
|
+
}
|
|
4
18
|
|
|
5
19
|
const MAX_URL_LENGTH = 100;
|
|
6
20
|
|
|
7
21
|
const Row = ({ item }) => {
|
|
8
|
-
const [tab, setTab] = useState(
|
|
22
|
+
const [tab, setTab] = useState("response-body");
|
|
9
23
|
const hasResponse = item.response;
|
|
10
24
|
const Tab = ({ value, label }) => {
|
|
11
25
|
const isSelected = value === tab;
|
|
@@ -13,32 +27,49 @@ const Row = ({ item }) => {
|
|
|
13
27
|
<TouchableOpacity
|
|
14
28
|
activeOpacity={isSelected ? 1 : 0.7}
|
|
15
29
|
onPress={() => setTab(value)}
|
|
16
|
-
style={[
|
|
30
|
+
style={[
|
|
31
|
+
styles.selectionTab,
|
|
32
|
+
{ backgroundColor: isSelected ? "white" : undefined },
|
|
33
|
+
]}
|
|
17
34
|
>
|
|
18
|
-
<Text
|
|
35
|
+
<Text
|
|
36
|
+
style={{ color: isSelected ? "#000" : "#fff", textAlign: "center" }}
|
|
37
|
+
>
|
|
38
|
+
{label}
|
|
39
|
+
</Text>
|
|
19
40
|
</TouchableOpacity>
|
|
20
41
|
);
|
|
21
42
|
};
|
|
22
43
|
return (
|
|
23
44
|
<View style={styles.details}>
|
|
24
45
|
{item.request.url.length > MAX_URL_LENGTH && (
|
|
25
|
-
<Text style={{ color:
|
|
46
|
+
<Text style={{ color: "#ffffff99", paddingVertical: 20 }}>
|
|
47
|
+
{item.request.url}
|
|
48
|
+
</Text>
|
|
26
49
|
)}
|
|
27
50
|
<View>
|
|
28
|
-
<View style={{ flexDirection:
|
|
29
|
-
<Tab value=
|
|
30
|
-
{!!item.request.data &&
|
|
31
|
-
|
|
51
|
+
<View style={{ flexDirection: "row" }}>
|
|
52
|
+
<Tab value="response-body" label="Response Body" />
|
|
53
|
+
{!!item.request.data && (
|
|
54
|
+
<Tab value="request-body" label="Request Body" />
|
|
55
|
+
)}
|
|
56
|
+
<Tab value="request-header" label="Request Header" />
|
|
32
57
|
</View>
|
|
33
58
|
|
|
34
|
-
{tab ===
|
|
35
|
-
<Text style={{ color:
|
|
59
|
+
{tab === "response-body" && hasResponse && (
|
|
60
|
+
<Text style={{ color: "white" }}>
|
|
61
|
+
{JSON.stringify(item.response.data, undefined, 4)}
|
|
62
|
+
</Text>
|
|
36
63
|
)}
|
|
37
|
-
{tab ===
|
|
38
|
-
<Text style={{ color:
|
|
64
|
+
{tab === "request-body" && (
|
|
65
|
+
<Text style={{ color: "white" }}>
|
|
66
|
+
{JSON.stringify(item.request.data, undefined, 4)}
|
|
67
|
+
</Text>
|
|
39
68
|
)}
|
|
40
|
-
{tab ===
|
|
41
|
-
<Text style={{ color:
|
|
69
|
+
{tab === "request-header" && (
|
|
70
|
+
<Text style={{ color: "white" }}>
|
|
71
|
+
{JSON.stringify(item.request.headers, undefined, 4)}
|
|
72
|
+
</Text>
|
|
42
73
|
)}
|
|
43
74
|
</View>
|
|
44
75
|
</View>
|
|
@@ -46,48 +77,74 @@ const Row = ({ item }) => {
|
|
|
46
77
|
};
|
|
47
78
|
|
|
48
79
|
export default (props) => {
|
|
49
|
-
const [filter, setFilter] = useState(
|
|
80
|
+
const [filter, setFilter] = useState("");
|
|
50
81
|
const [errorOnly, setErrorOnly] = useState(false);
|
|
51
82
|
const [filterUrlOnly, setFilterUrlOnly] = useState(true);
|
|
52
83
|
const [expands, setExpands] = useState({});
|
|
53
|
-
const apis = props.apis.filter(
|
|
84
|
+
const apis = props.apis.filter(
|
|
85
|
+
(a) => !errorOnly || a.response?.status < 200 || a.response?.status >= 400
|
|
86
|
+
);
|
|
54
87
|
|
|
55
|
-
const hasError = apis.some(
|
|
88
|
+
const hasError = apis.some(
|
|
89
|
+
(a) => a.response?.status < 200 || a.response?.status >= 400
|
|
90
|
+
);
|
|
56
91
|
|
|
57
92
|
return (
|
|
58
93
|
<>
|
|
59
|
-
<View
|
|
94
|
+
<View
|
|
95
|
+
style={{
|
|
96
|
+
flexDirection: "row",
|
|
97
|
+
paddingLeft: 5,
|
|
98
|
+
alignItems: "center",
|
|
99
|
+
gap: 5,
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
60
102
|
{!!apis.length && !filter && (
|
|
61
103
|
<TouchableOpacity
|
|
62
|
-
style={{ padding: 5, backgroundColor:
|
|
104
|
+
style={{ padding: 5, backgroundColor: "white", borderRadius: 5 }}
|
|
63
105
|
onPress={() =>
|
|
64
|
-
Alert.alert(
|
|
65
|
-
{ text:
|
|
66
|
-
{ text:
|
|
106
|
+
Alert.alert("Are you sure", "You want to clear all logs", [
|
|
107
|
+
{ text: "Delete", onPress: props.clear, style: "cancel" },
|
|
108
|
+
{ text: "Cancel" },
|
|
67
109
|
])
|
|
68
110
|
}
|
|
69
111
|
>
|
|
70
|
-
<Text style={{ color:
|
|
112
|
+
<Text style={{ color: "black" }}>Clear {apis.length} APIs</Text>
|
|
71
113
|
</TouchableOpacity>
|
|
72
114
|
)}
|
|
73
115
|
{hasError && !filter && (
|
|
74
|
-
<TouchableOpacity
|
|
75
|
-
|
|
116
|
+
<TouchableOpacity
|
|
117
|
+
style={{ padding: 5 }}
|
|
118
|
+
onPress={() => setErrorOnly((v) => !v)}
|
|
119
|
+
>
|
|
120
|
+
<Text
|
|
121
|
+
style={{
|
|
122
|
+
color: "red",
|
|
123
|
+
textDecorationLine: errorOnly ? "line-through" : undefined,
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
Error Only
|
|
127
|
+
</Text>
|
|
76
128
|
</TouchableOpacity>
|
|
77
129
|
)}
|
|
78
|
-
<View style={{ flexDirection:
|
|
130
|
+
<View style={{ flexDirection: "row", flex: 1 }}>
|
|
79
131
|
<TextInput
|
|
80
132
|
value={filter}
|
|
81
|
-
placeholder=
|
|
82
|
-
clearButtonMode=
|
|
83
|
-
placeholderTextColor=
|
|
84
|
-
style={{ paddingHorizontal: 5, color:
|
|
133
|
+
placeholder="Filter..."
|
|
134
|
+
clearButtonMode="always"
|
|
135
|
+
placeholderTextColor="grey"
|
|
136
|
+
style={{ paddingHorizontal: 5, color: "white", flex: 1 }}
|
|
85
137
|
onChangeText={(t) => setFilter(t.toLowerCase())}
|
|
86
138
|
/>
|
|
87
139
|
</View>
|
|
88
140
|
{!!filter && (
|
|
89
|
-
<TouchableOpacity
|
|
90
|
-
|
|
141
|
+
<TouchableOpacity
|
|
142
|
+
style={{ padding: 5 }}
|
|
143
|
+
onPress={() => setFilterUrlOnly((v) => !v)}
|
|
144
|
+
>
|
|
145
|
+
<Text style={{ color: "#ffffff88" }}>
|
|
146
|
+
{filterUrlOnly ? "by URL" : "by URL & body"}
|
|
147
|
+
</Text>
|
|
91
148
|
</TouchableOpacity>
|
|
92
149
|
)}
|
|
93
150
|
</View>
|
|
@@ -99,22 +156,24 @@ export default (props) => {
|
|
|
99
156
|
.filter((a) =>
|
|
100
157
|
!filter || filterUrlOnly
|
|
101
158
|
? a.request.url.includes(filter)
|
|
102
|
-
: JSON.stringify(a).toLowerCase().includes(filter)
|
|
159
|
+
: JSON.stringify(a).toLowerCase().includes(filter)
|
|
103
160
|
)
|
|
104
161
|
.map((data) => ({ data: [data], id: data.id }))}
|
|
105
|
-
renderItem={(i) =>
|
|
162
|
+
renderItem={(i) =>
|
|
163
|
+
expands[i.item.id] ? <Row {...i} /> : <View style={{ height: 20 }} />
|
|
164
|
+
}
|
|
106
165
|
renderSectionHeader={({ section: { data } }) => {
|
|
107
166
|
const item = data[0];
|
|
108
167
|
const hasResponse = !!item.response;
|
|
109
168
|
|
|
110
169
|
let duration = 0;
|
|
111
170
|
if (item.response?.datetime) {
|
|
112
|
-
let [hr, min, sec] = item.request.datetime.split(
|
|
171
|
+
let [hr, min, sec] = item.request.datetime.split(" ")[0].split(":");
|
|
113
172
|
const start = new Date();
|
|
114
173
|
start.setHours(hr);
|
|
115
174
|
start.setMinutes(min);
|
|
116
175
|
start.setSeconds(sec);
|
|
117
|
-
[hr, min, sec] = item.response.datetime.split(
|
|
176
|
+
[hr, min, sec] = item.response.datetime.split(" ")[0].split(":");
|
|
118
177
|
const end = new Date();
|
|
119
178
|
end.setHours(hr);
|
|
120
179
|
end.setMinutes(min);
|
|
@@ -128,20 +187,24 @@ export default (props) => {
|
|
|
128
187
|
selectable
|
|
129
188
|
style={{
|
|
130
189
|
flex: 1,
|
|
131
|
-
color: hasResponse
|
|
190
|
+
color: hasResponse
|
|
191
|
+
? item.response.error
|
|
192
|
+
? "red"
|
|
193
|
+
: "white"
|
|
194
|
+
: "yellow",
|
|
132
195
|
marginVertical: 10,
|
|
133
196
|
}}
|
|
134
197
|
>
|
|
135
198
|
<Text style={{ opacity: 0.7 }}>
|
|
136
199
|
{item.request.method +
|
|
137
|
-
` (${item.response?.status ??
|
|
138
|
-
|
|
200
|
+
` (${item.response?.status ?? "no response"})` +
|
|
201
|
+
" - " +
|
|
139
202
|
item.request.datetime +
|
|
140
|
-
(hasResponse ?
|
|
141
|
-
|
|
203
|
+
(hasResponse ? " - " + duration + " second(s)" : "") +
|
|
204
|
+
"\n"}
|
|
142
205
|
</Text>
|
|
143
206
|
{item.request.url.slice(0, MAX_URL_LENGTH)}
|
|
144
|
-
{item.request.url.length > MAX_URL_LENGTH &&
|
|
207
|
+
{item.request.url.length > MAX_URL_LENGTH && "......."}
|
|
145
208
|
</Text>
|
|
146
209
|
<View style={{ gap: 4 }}>
|
|
147
210
|
<TouchableOpacity
|
|
@@ -155,18 +218,24 @@ export default (props) => {
|
|
|
155
218
|
}
|
|
156
219
|
style={styles.actionButton}
|
|
157
220
|
>
|
|
158
|
-
<Text style={{ color:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
onPress={() => {
|
|
162
|
-
const content = { ...item };
|
|
163
|
-
delete content.id;
|
|
164
|
-
Clipboard.setString(JSON.stringify(content, undefined, 4));
|
|
165
|
-
}}
|
|
166
|
-
style={styles.actionButton}
|
|
167
|
-
>
|
|
168
|
-
<Text style={{ color: 'black' }}>Copy</Text>
|
|
221
|
+
<Text style={{ color: "black" }}>
|
|
222
|
+
{isExpand ? "Hide" : "Show"}
|
|
223
|
+
</Text>
|
|
169
224
|
</TouchableOpacity>
|
|
225
|
+
{!!Clipboard && (
|
|
226
|
+
<TouchableOpacity
|
|
227
|
+
onPress={() => {
|
|
228
|
+
const content = { ...item };
|
|
229
|
+
delete content.id;
|
|
230
|
+
Clipboard.setString(
|
|
231
|
+
JSON.stringify(content, undefined, 4)
|
|
232
|
+
);
|
|
233
|
+
}}
|
|
234
|
+
style={styles.actionButton}
|
|
235
|
+
>
|
|
236
|
+
<Text style={{ color: "black" }}>Copy</Text>
|
|
237
|
+
</TouchableOpacity>
|
|
238
|
+
)}
|
|
170
239
|
</View>
|
|
171
240
|
</View>
|
|
172
241
|
);
|
|
@@ -179,18 +248,18 @@ export default (props) => {
|
|
|
179
248
|
const styles = StyleSheet.create({
|
|
180
249
|
details: {
|
|
181
250
|
padding: 5,
|
|
182
|
-
backgroundColor:
|
|
251
|
+
backgroundColor: "#171717",
|
|
183
252
|
paddingTop: 10,
|
|
184
253
|
paddingBottom: 40,
|
|
185
254
|
},
|
|
186
|
-
actionButton: { backgroundColor:
|
|
255
|
+
actionButton: { backgroundColor: "white", borderRadius: 5, padding: 4 },
|
|
187
256
|
rowHeader: {
|
|
188
|
-
flexDirection:
|
|
257
|
+
flexDirection: "row",
|
|
189
258
|
gap: 5,
|
|
190
|
-
backgroundColor:
|
|
259
|
+
backgroundColor: "black",
|
|
191
260
|
padding: 5,
|
|
192
261
|
paddingTop: 20,
|
|
193
|
-
shadowColor:
|
|
262
|
+
shadowColor: "black",
|
|
194
263
|
shadowOffset: {
|
|
195
264
|
width: 0,
|
|
196
265
|
height: 2,
|
|
@@ -202,7 +271,7 @@ const styles = StyleSheet.create({
|
|
|
202
271
|
zIndex: 99,
|
|
203
272
|
},
|
|
204
273
|
selectionTab: {
|
|
205
|
-
borderBottomColor:
|
|
274
|
+
borderBottomColor: "white",
|
|
206
275
|
borderBottomWidth: 2,
|
|
207
276
|
flex: 1,
|
|
208
277
|
borderTopEndRadius: 10,
|
package/README.md
CHANGED
|
@@ -1,80 +1,15 @@
|
|
|
1
1
|
# react-native-in-app-debugger
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
summary
|
|
4
4
|
|
|
5
|
-
Caution:
|
|
6
|
-
|
|
7
|
-
`renderItem` props return `item` and `index` parameters
|
|
8
|
-
`item` parameter returns a single element in `item` array. But if search text is not empty dan `highlightColor` props is set to any color, `item` parameter will return new structure of JSON object (in order to render highlighted text in jsx). This might break your logic. Therefore, if you want to access original structure of your data, it will be under `item.cleanData`. Remember, `item.cleanData` only exist if `highlightColor` props and search textfield is not empty (user is searching)
|
|
9
5
|
|
|
10
6
|
Usage :
|
|
11
7
|
|
|
12
8
|
```
|
|
13
|
-
import React
|
|
14
|
-
import { Text, SafeAreaView, TouchableOpacity } from 'react-native';
|
|
15
|
-
import CompleteFlatList from 'react-native-complete-flatlist';
|
|
16
|
-
|
|
17
|
-
const list = [
|
|
18
|
-
{ name: 'Fattah', status: 'Active', time: '8:10 PM', date: '1 Jan 2018' },
|
|
19
|
-
{ name: 'Syah', status: 'Active', time: '9:14 PM', date: '1 Dec 2018' },
|
|
20
|
-
{ name: 'Izzat', status: 'Active', time: '8:15 PM', date: '1 Jan 2018' },
|
|
21
|
-
{ name: 'Ali', status: 'Active', time: '8:10 PM', date: '1 Jan 2018' },
|
|
22
|
-
{ name: 'Abu', status: 'Active', time: '8:11 PM', date: '1 Jan 2018' },
|
|
23
|
-
{ name: 'Fitri', status: 'Active', time: '8:20 PM', date: '1 Jan 2018' },
|
|
24
|
-
{ name: 'Armi', status: 'Active', time: '8:33 PM', date: '1 Jan 2018' },
|
|
25
|
-
{ name: 'Eidit', status: 'Active', time: '9:10 PM', date: '1 Jan 2018' },
|
|
26
|
-
{ name: 'Hamdan', status: 'Active', time: '10:10 PM', date: '1 Jan 2018' },
|
|
27
|
-
{
|
|
28
|
-
name: 'Muhyiddeen',
|
|
29
|
-
status: 'Blocked',
|
|
30
|
-
time: '10:10 PM',
|
|
31
|
-
date: '9 Feb 2018',
|
|
32
|
-
},
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
const App = () => {
|
|
36
|
-
const ref = useRef();
|
|
37
|
-
const renderItem = ({item, index}) => {
|
|
38
|
-
const data = item.cleanData ? item.cleanData : item;
|
|
39
|
-
|
|
40
|
-
console.log('item (if search bar is not empty and prop highlightColor is not empty, item will contains extra data to enable highlight feature)', item);
|
|
41
|
-
console.log('cleanData (if search bar is not empty and prop highlightColor is not empty, cleanData will contain original data structure without extra data)', item.cleanData);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
console.log('this is index number : ' + index);
|
|
45
|
-
|
|
46
|
-
console.log(data + ' this is original data');
|
|
47
|
-
|
|
48
|
-
return <Text>{item.name}</Text>;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
return (
|
|
52
|
-
<SafeAreaView style={{ flex: 1 }}>
|
|
53
|
-
<CompleteFlatList
|
|
54
|
-
searchKey={['name', 'status', 'time', 'date']}
|
|
55
|
-
pullToRefreshCallback={() => console.log('refreshing')}
|
|
56
|
-
data={list}
|
|
57
|
-
// renderSeparator={null}
|
|
58
|
-
ref={ref}
|
|
59
|
-
highlightColor="yellow"
|
|
60
|
-
renderItem={renderItem}
|
|
61
|
-
/>
|
|
62
|
-
<TouchableOpacity onPress={() => ref.current.clearSearch()} style={{ padding: 5 }}>
|
|
63
|
-
<Text>Clear Search</Text>
|
|
64
|
-
</TouchableOpacity>
|
|
65
|
-
</SafeAreaView>
|
|
66
|
-
);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
export default App;
|
|
70
|
-
|
|
71
|
-
|
|
9
|
+
import React from 'react';
|
|
72
10
|
|
|
73
11
|
```
|
|
74
12
|
|
|
75
|
-
### Upgrading from V 1.x.x to V 2.x.x
|
|
76
|
-
|
|
77
|
-
Change from `renderItem={(data, index) => {} }` to `renderItem={({item, index, separators}) => {} }` (similar like the on in Original Flatlist)
|
|
78
13
|
|
|
79
14
|
### Properties
|
|
80
15
|
|
|
@@ -82,56 +17,5 @@ All FlatList props should work plus props mentioned below
|
|
|
82
17
|
|
|
83
18
|
| Prop | Type | Description | Default | Required |
|
|
84
19
|
| ----------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
|
85
|
-
| `
|
|
86
|
-
| `isJelly` | boolean | If `true`, when user scroll, the list will expand a lil bit, and when user stop drag, the list will back to original size (iMessage on iPhone style) | false | Optional |
|
|
87
|
-
| `slide` | string | Animation how every items come into the list. Can be "none", "left" or "right" | `none` | Optional |
|
|
88
|
-
| `data` | array of objects | Data to be rendered in the list | [] | Required (come on, ofcourse u need data for this) |
|
|
89
|
-
| `backgroundStyles` | style object | Style of the flatlist background | null | Optional |
|
|
90
|
-
| `searchBarBackgroundStyles` | style object | Style of the searchbar background | null | Optional |
|
|
91
|
-
| `pullToRefreshCallback` | function | Callback function when user pull to refresh | null | Optional (Pull to refresh will not be available if this is not supplied |
|
|
92
|
-
| `isLoading` | boolean | if true, the loading will be shown on top of the list. | false | Optional |
|
|
93
|
-
| `renderItem` | function that return a JSX element (Just like RN's ListView and FlatList) | Template of a row in the Flat List | null (open for PR if anyone wish to make default template for this) | Required (since I dont do default template yet) |
|
|
94
|
-
| `renderSeparator` | function that return a JSX element to be rendered between rows(Just like RN's ListView and FlatList) | Template of separator in the Flat List | `() => <View style={{ height: 1, width: "80%", alignSelf: "center", backgroundColor: "#f2f2f2" }} />` | Optional |
|
|
95
|
-
| `placeholder` | string | Placeholder of search field | "Search ..." | Optional |
|
|
96
|
-
| `searchTextInputStyle` | object (style for React Native's TextInput component) | style for search field | null | Optional |
|
|
97
|
-
| `highlightColor` | color | color of higlighted words background when match search keyword. Please read the pre caution if using this prop on top of the readme | yellow | Optional |
|
|
98
|
-
| `searchKey` | array of string | This should be name of keys available in data which will be use to search. If this prop is not supplied, search text input will not be rendered. `**Warning: nested key not yet supported` | [] | Optional (if not supplied, search field will not appear) |
|
|
99
|
-
| `elementBetweenSearchAndList` | JSX element | What to render between searchbar and the list | null | Optional |
|
|
100
|
-
| `refreshOnLoad` | boolean | If `true`, prop `pullToRefreshCallback` will be called if available | true | Optional |
|
|
101
|
-
| `onSearch` | function that will replace `pullToRefreshCallback` | If exist, `pullToRefreshCallback` will be overrided. This will not triggered on key press, but on return key pressed. This props is introduced if search trigger result from API. If you just want local search (search from existing array), this props is not needed. `onSearch` will automatic get `keyword` parameter | ()=>null | Optional |
|
|
102
|
-
|
|
103
|
-
### Methods
|
|
104
|
-
|
|
105
|
-
If you have `ref` to the component,
|
|
106
|
-
```
|
|
107
|
-
const completeFlatList = useRef();
|
|
108
|
-
...
|
|
109
|
-
<CompleteFlatList
|
|
110
|
-
...
|
|
111
|
-
ref={completeFlatList}
|
|
112
|
-
...
|
|
113
|
-
/>
|
|
114
|
-
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
or in component based
|
|
118
|
-
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
<CompleteFlatList
|
|
122
|
-
...
|
|
123
|
-
ref={c => this.completeFlatList = c}
|
|
124
|
-
...
|
|
125
|
-
/>
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
you can use any method(s) below:
|
|
129
|
-
```completeFlatList.current.methodName()```
|
|
130
|
-
|
|
131
|
-
or in component based
|
|
132
|
-
|
|
133
|
-
```this.completeFlatList.methodName()```
|
|
20
|
+
| `env` | string | Any text | | Optional |
|
|
134
21
|
|
|
135
|
-
| Method | Description |
|
|
136
|
-
| ----------- | ----------------------------------- |
|
|
137
|
-
| clearSearch | Clear search input programmatically |
|
package/index.jsx
CHANGED
|
@@ -1,38 +1,76 @@
|
|
|
1
1
|
/* eslint-disable react-hooks/rules-of-hooks */
|
|
2
2
|
// hooks are prevented to be called conditionally, but in this case, bundle id / package name will never changed in run time, so it is safe to call the hooks under that condition
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
import React, { useState } from "react";
|
|
5
|
+
import {
|
|
6
|
+
Animated,
|
|
7
|
+
Text,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
TouchableOpacity,
|
|
10
|
+
View,
|
|
11
|
+
SafeAreaView,
|
|
12
|
+
Dimensions,
|
|
13
|
+
} from "react-native";
|
|
14
|
+
// const DeviceInfo = React.lazy(() =>
|
|
15
|
+
// import("react-native-device-info").catch(() => ({ default: null }))
|
|
16
|
+
// );
|
|
11
17
|
|
|
12
|
-
|
|
18
|
+
let DeviceInfo;
|
|
19
|
+
try {
|
|
20
|
+
DeviceInfo = require("react-native-device-info");
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error("Error importing DeviceInfo:", error);
|
|
23
|
+
}
|
|
13
24
|
|
|
14
|
-
|
|
25
|
+
import useAnimation from "./useAnimation";
|
|
26
|
+
import Variables from "./Variables";
|
|
27
|
+
import Api from "./Api";
|
|
28
|
+
import useApiInterceptor from "./useApiInterceptor";
|
|
29
|
+
|
|
30
|
+
const dimension = Dimensions.get("window");
|
|
31
|
+
|
|
32
|
+
const version = DeviceInfo?.getReadableVersion() || "";
|
|
33
|
+
|
|
34
|
+
const Label = (props) => (
|
|
35
|
+
<Text
|
|
36
|
+
{...props}
|
|
37
|
+
numberOfLines={1}
|
|
38
|
+
ellipsizeMode="tail"
|
|
39
|
+
style={[styles.label, props.style]}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
15
42
|
|
|
16
43
|
export default ({ variables, env }) => {
|
|
17
44
|
const { apis, clear } = useApiInterceptor();
|
|
18
45
|
|
|
19
|
-
const [tab, setTab] = useState(
|
|
46
|
+
const [tab, setTab] = useState("api");
|
|
20
47
|
|
|
21
48
|
const errors = apis.filter((a) => a.response?.error).length;
|
|
22
49
|
const numPendingApiCalls = apis.filter((a) => !a.response).length;
|
|
23
|
-
let badgeHeight =
|
|
24
|
-
if (variables
|
|
25
|
-
if (variables
|
|
50
|
+
let badgeHeight = 20;
|
|
51
|
+
if (variables?.GIT_BRANCH) badgeHeight += 10;
|
|
52
|
+
if (variables?.BUILD_DATE_TIME) badgeHeight += 10;
|
|
53
|
+
const hasEnvOrVersion = !!env || !!version;
|
|
54
|
+
if (hasEnvOrVersion) badgeHeight += 10;
|
|
55
|
+
if (DeviceInfo) badgeHeight += 10;
|
|
26
56
|
|
|
27
|
-
const {
|
|
28
|
-
|
|
57
|
+
const {
|
|
58
|
+
translateX,
|
|
59
|
+
translateY,
|
|
60
|
+
borderRadius,
|
|
61
|
+
width,
|
|
62
|
+
height,
|
|
63
|
+
isOpen,
|
|
64
|
+
panResponder,
|
|
65
|
+
setIsOpen,
|
|
66
|
+
} = useAnimation(badgeHeight);
|
|
29
67
|
return (
|
|
30
68
|
<Animated.View
|
|
31
69
|
style={{
|
|
32
70
|
transform: [{ translateX }, { translateY }],
|
|
33
|
-
position:
|
|
71
|
+
position: "absolute",
|
|
34
72
|
borderRadius,
|
|
35
|
-
backgroundColor:
|
|
73
|
+
backgroundColor: "#000000" + (isOpen ? "dd" : "bb"),
|
|
36
74
|
height,
|
|
37
75
|
width,
|
|
38
76
|
}}
|
|
@@ -42,46 +80,69 @@ export default ({ variables, env }) => {
|
|
|
42
80
|
<TouchableOpacity onPress={() => setIsOpen(true)} style={styles.box}>
|
|
43
81
|
<View style={styles.badgeContainer}>
|
|
44
82
|
{!!numPendingApiCalls && (
|
|
45
|
-
<View style={[styles.badge, { backgroundColor:
|
|
46
|
-
<Text style={{ fontSize: 8, color:
|
|
83
|
+
<View style={[styles.badge, { backgroundColor: "orange" }]}>
|
|
84
|
+
<Text style={{ fontSize: 8, color: "white" }}>
|
|
85
|
+
{numPendingApiCalls}
|
|
86
|
+
</Text>
|
|
47
87
|
</View>
|
|
48
88
|
)}
|
|
49
89
|
{!!errors && (
|
|
50
|
-
<View style={[styles.badge, { backgroundColor:
|
|
51
|
-
<Text style={{ fontSize: 8, color:
|
|
90
|
+
<View style={[styles.badge, { backgroundColor: "red" }]}>
|
|
91
|
+
<Text style={{ fontSize: 8, color: "white" }}>{errors}</Text>
|
|
52
92
|
</View>
|
|
53
93
|
)}
|
|
54
94
|
</View>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
{
|
|
59
|
-
|
|
95
|
+
{(!!env || !!version) && (
|
|
96
|
+
<Label>{(env || "") + (env ? " " : "") + version}</Label>
|
|
97
|
+
)}
|
|
98
|
+
{!!DeviceInfo && (
|
|
99
|
+
<Label style={{ fontSize: 6 }}>
|
|
100
|
+
{DeviceInfo.getDeviceId() + " " + DeviceInfo.getSystemVersion()}
|
|
101
|
+
</Label>
|
|
102
|
+
)}
|
|
103
|
+
<Label style={{ fontSize: 6 }}>
|
|
104
|
+
{dimension.width + "x" + dimension.height}
|
|
105
|
+
</Label>
|
|
106
|
+
{variables?.GIT_BRANCH && (
|
|
107
|
+
<Label style={{ fontSize: 6 }}>{variables.GIT_BRANCH}</Label>
|
|
108
|
+
)}
|
|
109
|
+
{variables?.BUILD_DATE_TIME && (
|
|
110
|
+
<Label style={{ fontSize: 6 }}>{variables.BUILD_DATE_TIME}</Label>
|
|
111
|
+
)}
|
|
60
112
|
</TouchableOpacity>
|
|
61
113
|
) : (
|
|
62
114
|
<SafeAreaView style={{ flex: 1 }}>
|
|
63
|
-
<View style={{ flexDirection:
|
|
64
|
-
<View style={{ flex: 1, flexDirection:
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
115
|
+
<View style={{ flexDirection: "row", padding: 5 }}>
|
|
116
|
+
<View style={{ flex: 1, flexDirection: "row" }}>
|
|
117
|
+
{!!variables &&
|
|
118
|
+
["api", "variables"].map((t) => {
|
|
119
|
+
const isSelected = t === tab;
|
|
120
|
+
return (
|
|
121
|
+
<TouchableOpacity
|
|
122
|
+
onPress={() => setTab(t)}
|
|
123
|
+
activeOpacity={isSelected ? 1 : 0.7}
|
|
124
|
+
key={t}
|
|
125
|
+
style={{
|
|
126
|
+
flex: 1,
|
|
127
|
+
borderBottomWidth: +isSelected,
|
|
128
|
+
borderColor: "white",
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
<Text style={{ color: "white", textAlign: "center" }}>
|
|
132
|
+
{t.toUpperCase()}
|
|
133
|
+
</Text>
|
|
134
|
+
</TouchableOpacity>
|
|
135
|
+
);
|
|
136
|
+
})}
|
|
78
137
|
</View>
|
|
79
138
|
<TouchableOpacity onPress={() => setIsOpen(false)}>
|
|
80
139
|
<Text style={styles.close}>X</Text>
|
|
81
140
|
</TouchableOpacity>
|
|
82
141
|
</View>
|
|
83
|
-
{tab ===
|
|
84
|
-
|
|
142
|
+
{tab === "variables" && !!variables && (
|
|
143
|
+
<Variables variables={variables} />
|
|
144
|
+
)}
|
|
145
|
+
{tab === "api" && <Api apis={apis} clear={clear} />}
|
|
85
146
|
</SafeAreaView>
|
|
86
147
|
)}
|
|
87
148
|
</Animated.View>
|
|
@@ -90,22 +151,27 @@ export default ({ variables, env }) => {
|
|
|
90
151
|
|
|
91
152
|
const styles = StyleSheet.create({
|
|
92
153
|
box: {
|
|
93
|
-
justifyContent:
|
|
94
|
-
width:
|
|
95
|
-
height:
|
|
154
|
+
justifyContent: "center",
|
|
155
|
+
width: "100%",
|
|
156
|
+
height: "100%",
|
|
96
157
|
},
|
|
97
|
-
label: { color:
|
|
158
|
+
label: { color: "white", textAlign: "center", fontSize: 8 },
|
|
98
159
|
badgeContainer: {
|
|
99
160
|
gap: 3,
|
|
100
|
-
flexDirection:
|
|
161
|
+
flexDirection: "row",
|
|
101
162
|
top: -8,
|
|
102
163
|
right: -3,
|
|
103
|
-
position:
|
|
164
|
+
position: "absolute",
|
|
104
165
|
zIndex: 999,
|
|
105
166
|
},
|
|
106
167
|
badge: {
|
|
107
168
|
padding: 4,
|
|
108
169
|
borderRadius: 999,
|
|
109
170
|
},
|
|
110
|
-
close: {
|
|
171
|
+
close: {
|
|
172
|
+
color: "white",
|
|
173
|
+
fontWeight: "bold",
|
|
174
|
+
fontSize: 16,
|
|
175
|
+
paddingHorizontal: 10,
|
|
176
|
+
},
|
|
111
177
|
});
|
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-in-app-debugger",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.jsx",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
|
-
"
|
|
8
|
+
"deploy": "npm publish --access=public"
|
|
9
9
|
},
|
|
10
|
-
"
|
|
10
|
+
"devDependencies": {},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"debugger"
|
|
13
|
+
],
|
|
11
14
|
"author": "Fattah Muhyiddeen <fattahmuhyiddeen@gmail.com>",
|
|
12
15
|
"license": "MIT"
|
|
13
16
|
}
|