react-native-vconsole 0.3.1 → 0.5.0

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/src/VConsole.tsx CHANGED
@@ -4,10 +4,12 @@ import {
4
4
  Clipboard,
5
5
  Dimensions,
6
6
  FlatList,
7
+ Keyboard,
7
8
  NativeModules,
8
9
  PanResponder,
9
10
  Platform,
10
11
  Pressable,
12
+ TextInput,
11
13
  StatusBar,
12
14
  StyleSheet,
13
15
  Text,
@@ -42,9 +44,11 @@ import type {
42
44
  const BUTTON_WIDTH = 88;
43
45
  const BUTTON_HEIGHT = 36;
44
46
  const PANEL_HEIGHT_RATIO = 7 / 9;
45
- const EMPTY_FILTER: string[] = [];
47
+ const EMPTY_EXCLUDE: VConsoleExclude = {};
46
48
  const LOG_SUB_TABS: LogFilterTab[] = ['All', 'log', 'info', 'warn', 'error'];
47
49
  const ROOT_TABS: VConsoleTab[] = ['Log', 'Network', 'System', 'App'];
50
+ const NETWORK_DURATION_WARN_THRESHOLD_MS = 1000;
51
+ const NETWORK_DURATION_SEVERE_THRESHOLD_MS = 3000;
48
52
 
49
53
  const LOG_THEME = {
50
54
  log: { backgroundColor: '#FFFFFF', color: '#111111' },
@@ -62,7 +66,12 @@ type NativeModuleShape = {
62
66
 
63
67
  export type VConsoleProps = {
64
68
  enable?: boolean;
65
- exclude?: string[];
69
+ exclude?: VConsoleExclude;
70
+ };
71
+
72
+ type VConsoleExclude = {
73
+ domains?: string[];
74
+ ip?: boolean;
66
75
  };
67
76
 
68
77
  function clamp(value: number, min: number, max: number): number {
@@ -102,6 +111,18 @@ function formatMemorySize(bytes: unknown): string {
102
111
  return `${mb.toFixed(2)} MB`;
103
112
  }
104
113
 
114
+ function formatLogTime(timestamp: number): string {
115
+ const date = new Date(timestamp);
116
+ if (Number.isNaN(date.getTime())) {
117
+ return '--:--:--.---';
118
+ }
119
+ const hh = String(date.getHours()).padStart(2, '0');
120
+ const mm = String(date.getMinutes()).padStart(2, '0');
121
+ const ss = String(date.getSeconds()).padStart(2, '0');
122
+ const ms = String(date.getMilliseconds()).padStart(3, '0');
123
+ return `${hh}:${mm}:${ss}.${ms}`;
124
+ }
125
+
105
126
  function prettyText(value: unknown): string {
106
127
  if (value === undefined) {
107
128
  return '';
@@ -116,19 +137,123 @@ function prettyText(value: unknown): string {
116
137
  }
117
138
  }
118
139
 
140
+ function isNetworkErrorEntry(item: NetworkEntry): boolean {
141
+ return item.isError === true;
142
+ }
143
+
144
+ function getNetworkItemBackgroundColor(item: NetworkEntry): string | undefined {
145
+ if (isNetworkErrorEntry(item)) {
146
+ return LOG_THEME.error.backgroundColor;
147
+ }
148
+
149
+ if (typeof item.durationMs !== 'number') {
150
+ return undefined;
151
+ }
152
+
153
+ if (item.durationMs >= NETWORK_DURATION_SEVERE_THRESHOLD_MS) {
154
+ return LOG_THEME.error.backgroundColor;
155
+ }
156
+
157
+ if (item.durationMs >= NETWORK_DURATION_WARN_THRESHOLD_MS) {
158
+ return LOG_THEME.warn.backgroundColor;
159
+ }
160
+
161
+ return undefined;
162
+ }
163
+
119
164
  function buildNetworkCopyText(item: NetworkEntry): string {
120
165
  const status = item.status ?? '-';
121
166
  const duration =
122
167
  typeof item.durationMs === 'number' ? `${item.durationMs}ms` : '-';
168
+ const isError = isNetworkErrorEntry(item);
123
169
 
124
- return [
170
+ const segments = [
125
171
  `${item.method} ${item.url}`,
126
172
  `status ${status} duration ${duration}`,
127
173
  `request headers\n${prettyText(item.requestHeaders)}`,
128
174
  `request body\n${prettyText(item.requestBody)}`,
129
- `response headers\n${prettyText(item.responseHeaders)}`,
130
- `response data\n${prettyText(item.responseData)}`,
131
- ].join('\n');
175
+ ];
176
+
177
+ if (isError) {
178
+ segments.push(
179
+ `error reason\n${item.errorReason ?? 'Network request failed'}`
180
+ );
181
+ } else {
182
+ segments.push(`response headers\n${prettyText(item.responseHeaders)}`);
183
+ segments.push(`response data\n${prettyText(item.responseData)}`);
184
+ }
185
+
186
+ return segments.join('\n');
187
+ }
188
+
189
+ const FORBIDDEN_RETRY_HEADERS = new Set([
190
+ 'host',
191
+ 'content-length',
192
+ 'accept-encoding',
193
+ 'connection',
194
+ 'origin',
195
+ 'referer',
196
+ ]);
197
+
198
+ function normalizeRetryUrl(rawUrl: string): string {
199
+ if (!rawUrl) {
200
+ return '';
201
+ }
202
+ if (/^\/\//.test(rawUrl)) {
203
+ return `https:${rawUrl}`;
204
+ }
205
+ return rawUrl;
206
+ }
207
+
208
+ function buildRetryHeaders(
209
+ headers: Record<string, string> | undefined
210
+ ): Record<string, string> {
211
+ const nextHeaders: Record<string, string> = {};
212
+ if (!headers) {
213
+ return nextHeaders;
214
+ }
215
+
216
+ Object.entries(headers).forEach(([key, value]) => {
217
+ if (!FORBIDDEN_RETRY_HEADERS.has(key.toLowerCase())) {
218
+ nextHeaders[key] = value;
219
+ }
220
+ });
221
+ return nextHeaders;
222
+ }
223
+
224
+ function buildRetryBody(payload: unknown, method: string): unknown | undefined {
225
+ if (method === 'GET' || method === 'HEAD' || payload == null) {
226
+ return undefined;
227
+ }
228
+ if (typeof payload === 'string') {
229
+ return payload;
230
+ }
231
+ if (typeof payload === 'number' || typeof payload === 'boolean') {
232
+ return String(payload);
233
+ }
234
+ if (typeof FormData !== 'undefined' && payload instanceof FormData) {
235
+ return payload;
236
+ }
237
+ if (
238
+ typeof URLSearchParams !== 'undefined' &&
239
+ payload instanceof URLSearchParams
240
+ ) {
241
+ return payload;
242
+ }
243
+ if (typeof Blob !== 'undefined' && payload instanceof Blob) {
244
+ return payload;
245
+ }
246
+ if (typeof ArrayBuffer !== 'undefined' && payload instanceof ArrayBuffer) {
247
+ return payload;
248
+ }
249
+ if (ArrayBuffer.isView(payload)) {
250
+ return payload;
251
+ }
252
+ try {
253
+ return JSON.stringify(payload);
254
+ } catch {
255
+ return String(payload);
256
+ }
132
257
  }
133
258
 
134
259
  type ObjectTreeProps = {
@@ -225,7 +350,7 @@ function useFlatListRefs() {
225
350
 
226
351
  export function VConsole({
227
352
  enable = true,
228
- exclude = EMPTY_FILTER,
353
+ exclude = EMPTY_EXCLUDE,
229
354
  }: VConsoleProps) {
230
355
  const nativeModule = NativeModules.Vconsole as NativeModuleShape | undefined;
231
356
  const { width, height } = Dimensions.get('window');
@@ -258,6 +383,11 @@ export function VConsole({
258
383
  const [logSubTab, setLogSubTab] = useState<LogFilterTab>('All');
259
384
  const [logEntries, setLogEntries] = useState<LogEntry[]>([]);
260
385
  const [networkEntries, setNetworkEntries] = useState<NetworkEntry[]>([]);
386
+ const [logFilterInput, setLogFilterInput] = useState('');
387
+ const [networkFilterInput, setNetworkFilterInput] = useState('');
388
+ const [debouncedLogFilter, setDebouncedLogFilter] = useState('');
389
+ const [debouncedNetworkFilter, setDebouncedNetworkFilter] = useState('');
390
+ const [keyboardHeight, setKeyboardHeight] = useState(0);
261
391
  const [expandedMap, setExpandedMap] = useState<ExpandedMap>({});
262
392
  const [systemInfo, setSystemInfo] = useState<SystemInfo | null>(null);
263
393
  const [appInfo, setAppInfo] = useState<AppInfo | null>(null);
@@ -266,10 +396,14 @@ export function VConsole({
266
396
  const panelTranslateY = useRef(new Animated.Value(panelHeight)).current;
267
397
  const logListRefs = useFlatListRefs();
268
398
  const networkListRef = useRef<FlatList<NetworkEntry>>(null);
269
- const normalizedExclude = useMemo(
270
- () => exclude.map((item) => item.trim().toLowerCase()).filter(Boolean),
271
- [exclude]
399
+ const normalizedExcludeDomains = useMemo(
400
+ () =>
401
+ (exclude.domains ?? [])
402
+ .map((item) => item.trim().toLowerCase())
403
+ .filter(Boolean),
404
+ [exclude.domains]
272
405
  );
406
+ const shouldExcludeIp = exclude.ip === true;
273
407
 
274
408
  useEffect(() => {
275
409
  if (!enable) {
@@ -278,7 +412,10 @@ export function VConsole({
278
412
  }
279
413
 
280
414
  installConsoleProxy();
281
- installXhrProxy({ excludeHosts: normalizedExclude });
415
+ installXhrProxy({
416
+ excludeHosts: normalizedExcludeDomains,
417
+ excludeIp: shouldExcludeIp,
418
+ });
282
419
 
283
420
  const unsubscribeLog = subscribeLogEntries(setLogEntries);
284
421
  const unsubscribeNetwork = subscribeNetworkEntries(setNetworkEntries);
@@ -291,7 +428,7 @@ export function VConsole({
291
428
  uninstallConsoleProxy();
292
429
  uninstallXhrProxy();
293
430
  };
294
- }, [enable, normalizedExclude]);
431
+ }, [enable, normalizedExcludeDomains, shouldExcludeIp]);
295
432
 
296
433
  useEffect(() => {
297
434
  dragPosition.stopAnimation((value) => {
@@ -317,6 +454,39 @@ export function VConsole({
317
454
  }
318
455
  }, [activeTab, appInfo, nativeModule, panelVisible, systemInfo]);
319
456
 
457
+ useEffect(() => {
458
+ const showEvent =
459
+ Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
460
+ const hideEvent =
461
+ Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
462
+
463
+ const showSubscription = Keyboard.addListener(showEvent, (event) => {
464
+ setKeyboardHeight(event.endCoordinates?.height ?? 0);
465
+ });
466
+ const hideSubscription = Keyboard.addListener(hideEvent, () => {
467
+ setKeyboardHeight(0);
468
+ });
469
+
470
+ return () => {
471
+ showSubscription.remove();
472
+ hideSubscription.remove();
473
+ };
474
+ }, []);
475
+
476
+ useEffect(() => {
477
+ const timer = setTimeout(() => {
478
+ setDebouncedLogFilter(logFilterInput);
479
+ }, 1000);
480
+ return () => clearTimeout(timer);
481
+ }, [logFilterInput]);
482
+
483
+ useEffect(() => {
484
+ const timer = setTimeout(() => {
485
+ setDebouncedNetworkFilter(networkFilterInput);
486
+ }, 1000);
487
+ return () => clearTimeout(timer);
488
+ }, [networkFilterInput]);
489
+
320
490
  const panResponder = useMemo(
321
491
  () =>
322
492
  PanResponder.create({
@@ -370,15 +540,36 @@ export function VConsole({
370
540
  });
371
541
  };
372
542
 
543
+ const normalizedLogFilter = debouncedLogFilter.trim().toLowerCase();
544
+ const normalizedNetworkFilter = debouncedNetworkFilter.trim().toLowerCase();
545
+
546
+ const filteredLogEntries = useMemo(() => {
547
+ if (!normalizedLogFilter) {
548
+ return logEntries;
549
+ }
550
+ return logEntries.filter((item) =>
551
+ item.text.toLowerCase().includes(normalizedLogFilter)
552
+ );
553
+ }, [logEntries, normalizedLogFilter]);
554
+
555
+ const filteredNetworkEntries = useMemo(() => {
556
+ if (!normalizedNetworkFilter) {
557
+ return networkEntries;
558
+ }
559
+ return networkEntries.filter((item) =>
560
+ item.url.toLowerCase().includes(normalizedNetworkFilter)
561
+ );
562
+ }, [networkEntries, normalizedNetworkFilter]);
563
+
373
564
  const logDataByTab = useMemo(
374
565
  () => ({
375
- All: logEntries,
376
- log: logEntries.filter((item) => item.level === 'log'),
377
- info: logEntries.filter((item) => item.level === 'info'),
378
- warn: logEntries.filter((item) => item.level === 'warn'),
379
- error: logEntries.filter((item) => item.level === 'error'),
566
+ All: filteredLogEntries,
567
+ log: filteredLogEntries.filter((item) => item.level === 'log'),
568
+ info: filteredLogEntries.filter((item) => item.level === 'info'),
569
+ warn: filteredLogEntries.filter((item) => item.level === 'warn'),
570
+ error: filteredLogEntries.filter((item) => item.level === 'error'),
380
571
  }),
381
- [logEntries]
572
+ [filteredLogEntries]
382
573
  );
383
574
 
384
575
  const onToggleNode = (key: string) => {
@@ -407,6 +598,39 @@ export function VConsole({
407
598
  networkListRef.current?.scrollToEnd({ animated: true });
408
599
  };
409
600
 
601
+ const retryNetworkRequest = (item: NetworkEntry) => {
602
+ const method = (item.method || 'GET').toUpperCase();
603
+ const url = normalizeRetryUrl(item.url);
604
+ if (!url) {
605
+ console.error('[vConsole] Retry failed: empty request URL');
606
+ return;
607
+ }
608
+
609
+ const headers = buildRetryHeaders(item.requestHeaders);
610
+ const body = buildRetryBody(item.requestBody, method);
611
+ const hasContentType = Object.keys(headers).some(
612
+ (key) => key.toLowerCase() === 'content-type'
613
+ );
614
+
615
+ if (
616
+ body &&
617
+ typeof body === 'string' &&
618
+ typeof item.requestBody === 'object' &&
619
+ item.requestBody !== null &&
620
+ !hasContentType
621
+ ) {
622
+ headers['Content-Type'] = 'application/json';
623
+ }
624
+
625
+ fetch(url, {
626
+ method,
627
+ headers,
628
+ body: body as never,
629
+ }).catch((error: unknown) => {
630
+ console.error('[vConsole] Retry request failed', error);
631
+ });
632
+ };
633
+
410
634
  const renderRootTab = (tab: VConsoleTab) => (
411
635
  <Pressable
412
636
  key={tab}
@@ -445,6 +669,10 @@ export function VConsole({
445
669
  <View style={styles.listItemMain}>
446
670
  <Text style={[styles.logLevelText, { color: levelTheme.color }]}>
447
671
  [{item.level.toUpperCase()}]
672
+ <Text style={styles.logTimeText}>
673
+ {' '}
674
+ {formatLogTime(item.timestamp)}
675
+ </Text>
448
676
  </Text>
449
677
  <View style={styles.logPayload}>
450
678
  {item.args.map((arg, index) => (
@@ -471,18 +699,29 @@ export function VConsole({
471
699
  const renderNetworkItem: FlatListProps<NetworkEntry>['renderItem'] = ({
472
700
  item,
473
701
  }) => {
702
+ const isError = isNetworkErrorEntry(item);
703
+ const backgroundColor = getNetworkItemBackgroundColor(item);
704
+ const startedTime = formatLogTime(item.startedAt);
705
+ const finishedTime =
706
+ typeof item.finishedAt === 'number'
707
+ ? formatLogTime(item.finishedAt)
708
+ : '-';
474
709
  return (
475
- <View style={styles.listItem}>
710
+ <View
711
+ style={[styles.listItem, backgroundColor ? { backgroundColor } : null]}
712
+ >
476
713
  <View style={styles.listItemMain}>
477
714
  <Text style={styles.networkTitle}>
478
715
  {item.method} {item.url}
479
716
  </Text>
480
717
  <Text style={styles.networkLabel}>
481
- Status: {item.status ?? '-'}
718
+ Time: {startedTime}
719
+ {finishedTime !== '-' ? ` ~ ${finishedTime}` : ''}
482
720
  {' '}
483
721
  Duration:{' '}
484
722
  {typeof item.durationMs === 'number' ? `${item.durationMs}ms` : '-'}
485
723
  </Text>
724
+ <Text style={styles.networkLabel}>Status: {item.status ?? '-'}</Text>
486
725
  <View style={styles.networkBlock}>
487
726
  <Text style={styles.networkLabel}>Request Headers</Text>
488
727
  <ObjectTree
@@ -501,26 +740,39 @@ export function VConsole({
501
740
  onToggle={onToggleNode}
502
741
  />
503
742
  </View>
504
- <View style={styles.networkBlock}>
505
- <Text style={styles.networkLabel}>Response Headers</Text>
506
- <ObjectTree
507
- value={item.responseHeaders}
508
- nodeKey={`${item.id}.responseHeaders`}
509
- expandedMap={expandedMap}
510
- onToggle={onToggleNode}
511
- />
512
- </View>
513
- <View style={styles.networkBlock}>
514
- <Text style={styles.networkLabel}>Response Data</Text>
515
- <ScrollView horizontal={true}>
516
- <ObjectTree
517
- value={item.responseData ?? ''}
518
- nodeKey={`${item.id}.responseData`}
519
- expandedMap={expandedMap}
520
- onToggle={onToggleNode}
521
- />
522
- </ScrollView>
523
- </View>
743
+ {isError ? (
744
+ <View style={styles.networkBlock}>
745
+ <Text style={[styles.networkLabel, styles.networkErrorLabel]}>
746
+ Error Reason
747
+ </Text>
748
+ <Text style={styles.networkErrorText}>
749
+ {item.errorReason ?? 'Network request failed'}
750
+ </Text>
751
+ </View>
752
+ ) : (
753
+ <>
754
+ <View style={styles.networkBlock}>
755
+ <Text style={styles.networkLabel}>Response Headers</Text>
756
+ <ObjectTree
757
+ value={item.responseHeaders}
758
+ nodeKey={`${item.id}.responseHeaders`}
759
+ expandedMap={expandedMap}
760
+ onToggle={onToggleNode}
761
+ />
762
+ </View>
763
+ <View style={styles.networkBlock}>
764
+ <Text style={styles.networkLabel}>Response Data</Text>
765
+ <ScrollView horizontal={true}>
766
+ <ObjectTree
767
+ value={item.responseData ?? ''}
768
+ nodeKey={`${item.id}.responseData`}
769
+ expandedMap={expandedMap}
770
+ onToggle={onToggleNode}
771
+ />
772
+ </ScrollView>
773
+ </View>
774
+ </>
775
+ )}
524
776
  </View>
525
777
  <Pressable
526
778
  style={styles.copyButton}
@@ -530,12 +782,18 @@ export function VConsole({
530
782
  >
531
783
  <Text style={styles.copyButtonText}>Copy</Text>
532
784
  </Pressable>
785
+ <Pressable
786
+ style={styles.retryButton}
787
+ onPress={() => retryNetworkRequest(item)}
788
+ >
789
+ <Text style={styles.retryButtonText}>Retry</Text>
790
+ </Pressable>
533
791
  </View>
534
792
  );
535
793
  };
536
794
 
537
- const renderLogPanel = () => (
538
- <View style={styles.contentArea}>
795
+ const renderLogPanel = (visible: boolean) => (
796
+ <View style={[styles.contentArea, visible ? {} : styles.hidden]}>
539
797
  <View style={styles.subTabRow}>
540
798
  {LOG_SUB_TABS.map((tab) => (
541
799
  <Pressable
@@ -573,6 +831,16 @@ export function VConsole({
573
831
  </View>
574
832
  ))}
575
833
  </View>
834
+ <View style={styles.filterInputWrap}>
835
+ <TextInput
836
+ style={styles.filterInput}
837
+ textAlignVertical="center"
838
+ value={logFilterInput}
839
+ onChangeText={setLogFilterInput}
840
+ placeholder="filter..."
841
+ placeholderTextColor="#999999"
842
+ />
843
+ </View>
576
844
  <View style={styles.actionsRow}>
577
845
  {renderActionButton('Clear', () => {
578
846
  clearLogEntries();
@@ -585,15 +853,24 @@ export function VConsole({
585
853
  </View>
586
854
  );
587
855
 
588
- const renderNetworkPanel = () => (
589
- <View style={styles.contentArea}>
856
+ const renderNetworkPanel = (visible: boolean) => (
857
+ <View style={[styles.contentArea, visible ? {} : styles.hidden]}>
590
858
  <FlatList
591
859
  ref={networkListRef}
592
- data={networkEntries}
860
+ data={filteredNetworkEntries}
593
861
  keyExtractor={(item) => `network-${item.id}`}
594
862
  renderItem={renderNetworkItem}
595
863
  ItemSeparatorComponent={ListSeparator}
596
864
  />
865
+ <View style={styles.filterInputWrap}>
866
+ <TextInput
867
+ style={styles.filterInput}
868
+ value={networkFilterInput}
869
+ onChangeText={setNetworkFilterInput}
870
+ placeholder="filter"
871
+ placeholderTextColor="#999999"
872
+ />
873
+ </View>
597
874
  <View style={styles.actionsRow}>
598
875
  {renderActionButton('Clear', () => {
599
876
  clearNetworkEntries();
@@ -606,8 +883,8 @@ export function VConsole({
606
883
  </View>
607
884
  );
608
885
 
609
- const renderSystemPanel = () => (
610
- <View style={styles.contentArea}>
886
+ const renderSystemPanel = (visible: boolean) => (
887
+ <View style={[styles.contentArea, visible ? {} : styles.hidden]}>
611
888
  <View style={styles.infoCard}>
612
889
  <Text style={styles.infoText}>
613
890
  Brand: {systemInfo?.manufacturer ?? '-'}
@@ -642,8 +919,8 @@ export function VConsole({
642
919
  </View>
643
920
  );
644
921
 
645
- const renderAppPanel = () => (
646
- <View style={styles.contentArea}>
922
+ const renderAppPanel = (visible: boolean) => (
923
+ <View style={[styles.contentArea, visible ? {} : styles.hidden]}>
647
924
  <View style={styles.infoCard}>
648
925
  <Text style={styles.infoText}>
649
926
  App Version: {appInfo?.appVersion ?? '-'}
@@ -686,15 +963,16 @@ export function VConsole({
686
963
  styles.panel,
687
964
  {
688
965
  height: panelHeight,
966
+ marginBottom: keyboardHeight,
689
967
  transform: [{ translateY: panelTranslateY }],
690
968
  },
691
969
  ]}
692
970
  >
693
971
  <View style={styles.topTabRow}>{ROOT_TABS.map(renderRootTab)}</View>
694
- {activeTab === 'Log' ? renderLogPanel() : null}
695
- {activeTab === 'Network' ? renderNetworkPanel() : null}
696
- {activeTab === 'System' ? renderSystemPanel() : null}
697
- {activeTab === 'App' ? renderAppPanel() : null}
972
+ {renderLogPanel(activeTab === 'Log')}
973
+ {renderNetworkPanel(activeTab === 'Network')}
974
+ {renderSystemPanel(activeTab === 'System')}
975
+ {renderAppPanel(activeTab === 'App')}
698
976
  </Animated.View>
699
977
  </View>
700
978
  ) : null}
@@ -813,6 +1091,11 @@ const styles = StyleSheet.create({
813
1091
  fontWeight: '700',
814
1092
  marginBottom: 4,
815
1093
  },
1094
+ logTimeText: {
1095
+ fontSize: 11,
1096
+ fontWeight: '400',
1097
+ color: '#888888',
1098
+ },
816
1099
  logPayload: {
817
1100
  flex: 1,
818
1101
  },
@@ -830,6 +1113,20 @@ const styles = StyleSheet.create({
830
1113
  fontSize: 11,
831
1114
  color: '#333333',
832
1115
  },
1116
+ retryButton: {
1117
+ position: 'absolute',
1118
+ right: 8,
1119
+ top: 40,
1120
+ borderWidth: StyleSheet.hairlineWidth,
1121
+ borderColor: '#D0D0D0',
1122
+ borderRadius: 6,
1123
+ paddingVertical: 4,
1124
+ paddingHorizontal: 8,
1125
+ },
1126
+ retryButtonText: {
1127
+ fontSize: 11,
1128
+ color: '#333333',
1129
+ },
833
1130
  valuePrimitive: {
834
1131
  color: '#222222',
835
1132
  fontSize: 12,
@@ -892,6 +1189,32 @@ const styles = StyleSheet.create({
892
1189
  color: '#444444',
893
1190
  marginBottom: 2,
894
1191
  },
1192
+ networkErrorLabel: {
1193
+ color: LOG_THEME.error.color,
1194
+ fontWeight: '600',
1195
+ },
1196
+ networkErrorText: {
1197
+ color: LOG_THEME.error.color,
1198
+ fontSize: 12,
1199
+ },
1200
+ filterInputWrap: {
1201
+ borderTopWidth: StyleSheet.hairlineWidth,
1202
+ borderTopColor: '#E1E1E1',
1203
+ paddingHorizontal: 8,
1204
+ paddingTop: 8,
1205
+ paddingBottom: 6,
1206
+ },
1207
+ filterInput: {
1208
+ height: 34,
1209
+ borderWidth: StyleSheet.hairlineWidth,
1210
+ borderColor: '#D0D0D0',
1211
+ borderRadius: 8,
1212
+ paddingHorizontal: 10,
1213
+ fontSize: 12,
1214
+ color: '#222222',
1215
+ backgroundColor: '#FFFFFF',
1216
+ paddingVertical: 0,
1217
+ },
895
1218
  actionsRow: {
896
1219
  borderTopWidth: StyleSheet.hairlineWidth,
897
1220
  borderTopColor: '#E1E1E1',