react-native-inapp-inspector 2.3.9 → 2.3.11

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.
Files changed (65) hide show
  1. package/dist/commonjs/components/Inspector/DebuggingTab.d.ts +3 -0
  2. package/dist/commonjs/components/Inspector/DebuggingTab.js +682 -0
  3. package/dist/commonjs/components/Inspector/MainScreen.js +2 -0
  4. package/dist/commonjs/components/Inspector/NetworkDetail.js +2 -1
  5. package/dist/commonjs/components/Inspector/NetworkFilterModal.d.ts +19 -0
  6. package/dist/commonjs/components/Inspector/NetworkFilterModal.js +648 -0
  7. package/dist/commonjs/components/Inspector/NetworkTab.js +89 -147
  8. package/dist/commonjs/components/Inspector/SettingsPanel.js +12 -0
  9. package/dist/commonjs/components/Inspector/TabBar.js +9 -0
  10. package/dist/commonjs/components/LogCard.js +5 -26
  11. package/dist/commonjs/components/NetworkIcons.d.ts +2 -0
  12. package/dist/commonjs/components/NetworkIcons.js +13 -1
  13. package/dist/commonjs/components/QRCodeView.d.ts +9 -0
  14. package/dist/commonjs/components/QRCodeView.js +79 -0
  15. package/dist/commonjs/constants/index.js +3 -0
  16. package/dist/commonjs/constants/version.d.ts +1 -1
  17. package/dist/commonjs/constants/version.js +1 -1
  18. package/dist/commonjs/helpers/qrGenerator.d.ts +2 -0
  19. package/dist/commonjs/helpers/qrGenerator.js +170 -0
  20. package/dist/commonjs/index.js +2 -0
  21. package/dist/commonjs/styles/index.d.ts +11 -0
  22. package/dist/commonjs/styles/index.js +20 -5
  23. package/dist/commonjs/types/enums.d.ts +5 -0
  24. package/dist/commonjs/types/enums.js +5 -0
  25. package/dist/esm/components/Inspector/DebuggingTab.d.ts +3 -0
  26. package/dist/esm/components/Inspector/DebuggingTab.js +642 -0
  27. package/dist/esm/components/Inspector/MainScreen.js +2 -0
  28. package/dist/esm/components/Inspector/NetworkDetail.js +2 -1
  29. package/dist/esm/components/Inspector/NetworkFilterModal.d.ts +19 -0
  30. package/dist/esm/components/Inspector/NetworkFilterModal.js +607 -0
  31. package/dist/esm/components/Inspector/NetworkTab.js +91 -149
  32. package/dist/esm/components/Inspector/SettingsPanel.js +13 -1
  33. package/dist/esm/components/Inspector/TabBar.js +10 -1
  34. package/dist/esm/components/LogCard.js +5 -26
  35. package/dist/esm/components/NetworkIcons.d.ts +2 -0
  36. package/dist/esm/components/NetworkIcons.js +10 -0
  37. package/dist/esm/components/QRCodeView.d.ts +9 -0
  38. package/dist/esm/components/QRCodeView.js +42 -0
  39. package/dist/esm/constants/index.js +3 -0
  40. package/dist/esm/constants/version.d.ts +1 -1
  41. package/dist/esm/constants/version.js +1 -1
  42. package/dist/esm/helpers/qrGenerator.d.ts +2 -0
  43. package/dist/esm/helpers/qrGenerator.js +167 -0
  44. package/dist/esm/index.js +2 -0
  45. package/dist/esm/styles/index.d.ts +11 -0
  46. package/dist/esm/styles/index.js +20 -5
  47. package/dist/esm/types/enums.d.ts +5 -0
  48. package/dist/esm/types/enums.js +5 -0
  49. package/package.json +2 -2
  50. package/src/components/Inspector/DebuggingTab.tsx +755 -0
  51. package/src/components/Inspector/MainScreen.tsx +2 -0
  52. package/src/components/Inspector/NetworkDetail.tsx +2 -1
  53. package/src/components/Inspector/NetworkFilterModal.tsx +710 -0
  54. package/src/components/Inspector/NetworkTab.tsx +127 -204
  55. package/src/components/Inspector/SettingsPanel.tsx +20 -0
  56. package/src/components/Inspector/TabBar.tsx +11 -0
  57. package/src/components/LogCard.tsx +5 -30
  58. package/src/components/NetworkIcons.tsx +66 -0
  59. package/src/components/QRCodeView.tsx +71 -0
  60. package/src/constants/index.ts +3 -0
  61. package/src/constants/version.ts +1 -1
  62. package/src/helpers/qrGenerator.ts +184 -0
  63. package/src/index.tsx +2 -0
  64. package/src/styles/index.ts +20 -5
  65. package/src/types/enums.ts +5 -0
@@ -9,13 +9,17 @@ import {
9
9
  View,
10
10
  } from 'react-native';
11
11
  import {animateNextLayout, useInspector} from './InspectorContext';
12
- import useAccordion from '../../customHooks/useAccordion';
13
12
  import TouchableScale from '../TouchableScale';
14
13
  import AnimatedEntrance from '../AnimatedEntrance';
15
14
  import DomainHeader from '../DomainHeader';
16
15
  import LogCard from '../LogCard';
17
16
  import EmptyState from '../EmptyState';
18
17
  import EndOfListFooter from '../EndOfListFooter';
18
+ import NetworkFilterModal, {
19
+ NetworkFilterState,
20
+ DEFAULT_NETWORK_FILTERS,
21
+ isNetworkFiltersDefault,
22
+ } from './NetworkFilterModal';
19
23
  import styles from '../../styles';
20
24
  import {AppColors} from '../../styles/AppColors';
21
25
  import {AppFonts} from '../../styles/AppFonts';
@@ -45,109 +49,7 @@ import {
45
49
  ShieldAlertIcon,
46
50
  } from '../NetworkIcons';
47
51
 
48
- const STATUS_META: Record<
49
- string,
50
- {color: string; Icon: (props: {color?: string; size?: number}) => React.JSX.Element}
51
- > = {
52
- ALL: {color: AppColors.grayText, Icon: LayersIcon},
53
- '2xx': {color: AppColors.greenColor, Icon: CircleCheckIcon},
54
- '200': {color: AppColors.greenColor, Icon: CircleCheckIcon},
55
- '3xx': {color: AppColors.warningIconGold, Icon: CircleAlertIcon},
56
- '300': {color: AppColors.warningIconGold, Icon: CircleAlertIcon},
57
- '4xx': {color: AppColors.darkOrange, Icon: CircleAlertIcon},
58
- '400': {color: AppColors.darkOrange, Icon: CircleAlertIcon},
59
- '404': {color: AppColors.darkOrange, Icon: CircleAlertIcon},
60
- '5xx': {color: AppColors.errorColor, Icon: CircleXIcon},
61
- '500': {color: AppColors.errorColor, Icon: CircleXIcon},
62
- Failed: {color: AppColors.errorColor, Icon: CircleXIcon},
63
- };
64
52
 
65
- const getStatusMeta = (filter: string) => {
66
- if (STATUS_META[filter]) return STATUS_META[filter];
67
- const num = parseInt(filter, 10);
68
- if (!isNaN(num)) {
69
- if (num >= 200 && num < 300) return {color: AppColors.greenColor, Icon: CircleCheckIcon};
70
- if (num >= 300 && num < 400) return {color: AppColors.warningIconGold, Icon: CircleAlertIcon};
71
- if (num >= 400 && num < 500) return {color: AppColors.darkOrange, Icon: CircleAlertIcon};
72
- if (num >= 500) return {color: AppColors.errorColor, Icon: CircleXIcon};
73
- }
74
- if (filter.startsWith('2')) return {color: AppColors.greenColor, Icon: CircleCheckIcon};
75
- if (filter.startsWith('3')) return {color: AppColors.warningIconGold, Icon: CircleAlertIcon};
76
- if (filter.startsWith('4')) return {color: AppColors.darkOrange, Icon: CircleAlertIcon};
77
- if (filter.startsWith('5')) return {color: AppColors.errorColor, Icon: CircleXIcon};
78
- return {color: AppColors.grayText, Icon: LayersIcon};
79
- };
80
-
81
- const FilterChip = React.memo(({
82
- label,
83
- color,
84
- Icon,
85
- badge,
86
- active,
87
- onPress,
88
- }: {
89
- label: string;
90
- color: string;
91
- Icon?: (props: {color?: string; size?: number}) => React.JSX.Element;
92
- badge?: string;
93
- active: boolean;
94
- onPress: () => void;
95
- }) => {
96
- const iconColor = active ? color : AppColors.grayTextWeak;
97
- return (
98
- <TouchableScale
99
- style={styles.statusFilterWrap}
100
- onPress={onPress}
101
- hitSlop={10}>
102
- <View
103
- style={[
104
- styles.statusFilterChip,
105
- {
106
- paddingHorizontal: 9,
107
- paddingVertical: 5,
108
- borderRadius: 7,
109
- borderWidth: 1,
110
- borderColor: active ? color : `${AppColors.grayBorderSecondary}`,
111
- backgroundColor: active ? `${color}14` : AppColors.grayBackground,
112
- },
113
- active && {
114
- shadowColor: color,
115
- shadowOffset: {width: 0, height: 1},
116
- shadowOpacity: 0.2,
117
- shadowRadius: 2,
118
- elevation: 1.5,
119
- },
120
- ]}>
121
- {badge != null ? (
122
- <View
123
- style={[
124
- styles.methodBadgeMini,
125
- {backgroundColor: badge},
126
- ]}
127
- />
128
- ) : (
129
- Icon && (
130
- <Icon
131
- color={iconColor}
132
- size={13}
133
- />
134
- )
135
- )}
136
- <Text
137
- numberOfLines={1}
138
- style={[
139
- styles.statusFilterText,
140
- {
141
- color: active ? color : AppColors.grayText,
142
- fontFamily: active ? AppFonts.interBold : AppFonts.interMedium,
143
- },
144
- ]}>
145
- {label}
146
- </Text>
147
- </View>
148
- </TouchableScale>
149
- );
150
- });
151
53
 
152
54
  const NetworkTab = React.memo(() => {
153
55
  const {
@@ -185,11 +87,52 @@ const NetworkTab = React.memo(() => {
185
87
  } = useInspector();
186
88
 
187
89
  const {t} = useTranslation();
188
- const filtersAccordion = useAccordion(false, 300, 260);
189
90
  const apisListRef = useRef<FlatList<any>>(null);
190
91
 
92
+ const [isFilterModalOpen, setIsFilterModalOpen] = React.useState(false);
191
93
  const [isSearchFocused, setIsSearchFocused] = React.useState(false);
192
94
 
95
+ const modalFilterState: NetworkFilterState = useMemo(
96
+ () => ({
97
+ statusCodes:
98
+ statusFilters.size > 0 ? new Set(statusFilters) : new Set(['all']),
99
+ methods:
100
+ methodFilters.size > 0 ? new Set(methodFilters) : new Set(['all']),
101
+ latency: 'all',
102
+ protocol: 'all',
103
+ sortBy: sortOrder === 'newest' ? 'time_desc' : 'time_asc',
104
+ }),
105
+ [statusFilters, methodFilters, sortOrder],
106
+ );
107
+
108
+ const handleApplyNetworkFilters = useCallback(
109
+ (newFilters: NetworkFilterState) => {
110
+ if (
111
+ newFilters.statusCodes.has('all') ||
112
+ newFilters.statusCodes.size === 0
113
+ ) {
114
+ setStatusFilters(new Set());
115
+ } else {
116
+ setStatusFilters(new Set(newFilters.statusCodes));
117
+ }
118
+
119
+ if (newFilters.methods.has('all') || newFilters.methods.size === 0) {
120
+ setMethodFilters(new Set());
121
+ } else {
122
+ setMethodFilters(
123
+ new Set([...newFilters.methods].map(m => m as Method)),
124
+ );
125
+ }
126
+
127
+ if (newFilters.sortBy === 'time_asc') {
128
+ setSortOrder('oldest');
129
+ } else {
130
+ setSortOrder('newest');
131
+ }
132
+ },
133
+ [setStatusFilters, setMethodFilters, setSortOrder],
134
+ );
135
+
193
136
  const quickCounts = useMemo(() => {
194
137
  let errorCount = 0;
195
138
  let successCount = 0;
@@ -755,14 +698,16 @@ const NetworkTab = React.memo(() => {
755
698
  <TouchableScale
756
699
  style={[
757
700
  styles.toolbarBtn,
758
- filtersAccordion.isOpen &&
759
- styles.toolbarBtnActive,
701
+ (statusFilters.size > 0 || methodFilters.size > 0) && {
702
+ borderColor: AppColors.purple,
703
+ backgroundColor: `${AppColors.purple}15`,
704
+ },
760
705
  ]}
761
- onPress={filtersAccordion.toggleOpen}
706
+ onPress={() => setIsFilterModalOpen(true)}
762
707
  hitSlop={6}>
763
708
  <FilterIcon
764
709
  color={
765
- filtersAccordion.isOpen
710
+ statusFilters.size > 0 || methodFilters.size > 0
766
711
  ? AppColors.purple
767
712
  : AppColors.grayTextStrong
768
713
  }
@@ -959,6 +904,66 @@ const NetworkTab = React.memo(() => {
959
904
  </TouchableScale>
960
905
  );
961
906
  })}
907
+
908
+ <TouchableScale
909
+ onPress={() => setIsFilterModalOpen(true)}>
910
+ <View
911
+ style={{
912
+ flexDirection: 'row',
913
+ alignItems: 'center',
914
+ paddingHorizontal: 10,
915
+ paddingVertical: 4.5,
916
+ borderRadius: 8,
917
+ backgroundColor:
918
+ statusFilters.size > 0 || methodFilters.size > 0
919
+ ? `${AppColors.purple}20`
920
+ : AppColors.grayBackground,
921
+ borderWidth: 1,
922
+ borderColor:
923
+ statusFilters.size > 0 || methodFilters.size > 0
924
+ ? AppColors.purple
925
+ : AppColors.grayBorderSecondary,
926
+ gap: 5,
927
+ }}>
928
+ <FilterIcon
929
+ size={11}
930
+ color={
931
+ statusFilters.size > 0 || methodFilters.size > 0
932
+ ? AppColors.purple
933
+ : AppColors.grayText
934
+ }
935
+ />
936
+ <Text
937
+ style={{
938
+ fontFamily: AppFonts.interBold,
939
+ fontSize: 10.5,
940
+ color:
941
+ statusFilters.size > 0 || methodFilters.size > 0
942
+ ? AppColors.purple
943
+ : AppColors.grayText,
944
+ }}>
945
+ More Filters
946
+ </Text>
947
+ {(statusFilters.size > 0 || methodFilters.size > 0) && (
948
+ <View
949
+ style={{
950
+ backgroundColor: AppColors.purple,
951
+ paddingHorizontal: 4.5,
952
+ paddingVertical: 1,
953
+ borderRadius: 6,
954
+ }}>
955
+ <Text
956
+ style={{
957
+ fontFamily: AppFonts.interBold,
958
+ fontSize: 8.5,
959
+ color: AppColors.white,
960
+ }}>
961
+ {statusFilters.size + methodFilters.size}
962
+ </Text>
963
+ </View>
964
+ )}
965
+ </View>
966
+ </TouchableScale>
962
967
  </ScrollView>
963
968
  </View>
964
969
 
@@ -1141,103 +1146,10 @@ const NetworkTab = React.memo(() => {
1141
1146
  </View>
1142
1147
  )}
1143
1148
 
1144
- <Animated.View
1145
- style={[
1146
- filtersAccordion.bodyStyle,
1147
- {overflow: 'hidden'},
1148
- ]}>
1149
- <View style={styles.filtersContainer}>
1150
- <Text style={styles.filtersHeading}>
1151
- STATUS
1152
- </Text>
1153
- <ScrollView
1154
- horizontal
1155
- showsHorizontalScrollIndicator={false}
1156
- contentContainerStyle={
1157
- styles.statusRowContent
1158
- }>
1159
- {STATUS_FILTERS.map(filter => {
1160
- const isAll = filter === 'ALL';
1161
- const active = isAll
1162
- ? statusFilters.size === 0
1163
- : statusFilters.has(filter);
1164
- const meta = getStatusMeta(filter);
1165
- return (
1166
- <FilterChip
1167
- key={filter}
1168
- label={filter}
1169
- color={meta.color}
1170
- Icon={meta.Icon}
1171
- active={active}
1172
- onPress={() => {
1173
- if (isAll) {
1174
- setStatusFilters(new Set());
1175
- } else {
1176
- setStatusFilters(prev => {
1177
- const next = new Set(prev);
1178
- next.has(filter)
1179
- ? next.delete(filter)
1180
- : next.add(filter);
1181
- return next;
1182
- });
1183
- }
1184
- }}
1185
- />
1186
- );
1187
- })}
1188
- </ScrollView>
1189
-
1190
- <Text
1191
- style={[
1192
- styles.filtersHeading,
1193
- {marginTop: 16},
1194
- ]}>
1195
- METHOD
1196
- </Text>
1197
- <ScrollView
1198
- horizontal
1199
- showsHorizontalScrollIndicator={false}
1200
- contentContainerStyle={
1201
- styles.statusRowContent
1202
- }>
1203
- {availableMethods.map(filter => {
1204
- const isAll = filter === 'ALL';
1205
- const active = isAll
1206
- ? methodFilters.size === 0
1207
- : methodFilters.has(filter as Method);
1208
- const methodColor =
1209
- METHOD_COLORS[filter as Method] ??
1210
- METHOD_COLORS.ALL;
1211
- return (
1212
- <FilterChip
1213
- key={filter}
1214
- label={filter}
1215
- color={methodColor}
1216
- badge={methodColor}
1217
- active={active}
1218
- onPress={() => {
1219
- if (isAll) {
1220
- setMethodFilters(new Set());
1221
- } else {
1222
- setMethodFilters(prev => {
1223
- const next = new Set(prev);
1224
- next.has(filter as Method)
1225
- ? next.delete(filter as Method)
1226
- : next.add(filter as Method);
1227
- return next;
1228
- });
1229
- }
1230
- }}
1231
- />
1232
- );
1233
- })}
1234
- </ScrollView>
1235
- </View>
1236
- </Animated.View>
1237
-
1238
1149
  {(search ||
1239
1150
  statusFilters.size > 0 ||
1240
- methodFilters.size > 0) && (
1151
+ methodFilters.size > 0 ||
1152
+ quickFilter !== 'all') && (
1241
1153
  <Text style={styles.resultCount}>
1242
1154
  {filteredLogs.length === logs.length
1243
1155
  ? `${logs.length} requests`
@@ -1251,7 +1163,8 @@ const NetworkTab = React.memo(() => {
1251
1163
  isSearch={
1252
1164
  search.length > 0 ||
1253
1165
  statusFilters.size > 0 ||
1254
- methodFilters.size > 0
1166
+ methodFilters.size > 0 ||
1167
+ quickFilter !== 'all'
1255
1168
  }
1256
1169
  searchQuery={search}
1257
1170
  customTitle={
@@ -1263,6 +1176,7 @@ const NetworkTab = React.memo(() => {
1263
1176
  setSearch('');
1264
1177
  setStatusFilters(new Set());
1265
1178
  setMethodFilters(new Set());
1179
+ setQuickFilter('all');
1266
1180
  setSearchScope('all');
1267
1181
  }}
1268
1182
  />
@@ -1301,6 +1215,15 @@ const NetworkTab = React.memo(() => {
1301
1215
  <ChevronIcon color={AppColors.white} size={18} />
1302
1216
  </View>
1303
1217
  </TouchableScale>
1218
+
1219
+ {/* Modern Network Filter Modal */}
1220
+ <NetworkFilterModal
1221
+ visible={isFilterModalOpen}
1222
+ onClose={() => setIsFilterModalOpen(false)}
1223
+ filters={modalFilterState}
1224
+ onApply={handleApplyNetworkFilters}
1225
+ searchQuery={search}
1226
+ />
1304
1227
  </View>
1305
1228
  );
1306
1229
  });
@@ -56,6 +56,7 @@ import {
56
56
  BrainIcon,
57
57
  SmartphoneIcon,
58
58
  DatabaseIcon,
59
+ QrCodeIcon,
59
60
  } from '../NetworkIcons';
60
61
  import {LIB_VERSION} from '../../constants';
61
62
  import {copyToClipboard} from '../../helpers';
@@ -194,6 +195,13 @@ const SettingsPanel = () => {
194
195
  icon: 'storage',
195
196
  desc: 'AsyncStorage & MMKV key-value store viewer with full CRUD support',
196
197
  },
198
+ {
199
+ key: 'debugging',
200
+ label: 'Multi-Device Debugging',
201
+ category: 'diagnostic',
202
+ icon: 'debugging',
203
+ desc: 'QR Code bridge for direct Debug APK download & Metro live-reload sync',
204
+ },
197
205
  ] as const;
198
206
 
199
207
  // Staged selection state for checkboxes before clicking "Save Changes"
@@ -209,6 +217,7 @@ const SettingsPanel = () => {
209
217
  crash: Boolean(tabVisibility?.crash),
210
218
  device: Boolean(tabVisibility?.device),
211
219
  storage: Boolean(tabVisibility?.storage),
220
+ debugging: Boolean(tabVisibility?.debugging ?? true),
212
221
  }));
213
222
 
214
223
  // Synchronize staged state with tabVisibility when tabVisibility updates
@@ -223,6 +232,7 @@ const SettingsPanel = () => {
223
232
  crash: Boolean(tabVisibility?.crash),
224
233
  device: Boolean(tabVisibility?.device),
225
234
  storage: Boolean(tabVisibility?.storage),
235
+ debugging: Boolean(tabVisibility?.debugging ?? true),
226
236
  });
227
237
  }, [tabVisibility]);
228
238
 
@@ -823,6 +833,16 @@ const SettingsPanel = () => {
823
833
  size={16}
824
834
  />
825
835
  )}
836
+ {moduleItem.icon === 'debugging' && (
837
+ <QrCodeIcon
838
+ color={
839
+ isChecked
840
+ ? AppColors.purple
841
+ : AppColors.grayTextWeak
842
+ }
843
+ size={16}
844
+ />
845
+ )}
826
846
  </View>
827
847
 
828
848
  {/* Titles & Status Pill */}
@@ -14,6 +14,7 @@ import {
14
14
  CrashIcon,
15
15
  SmartphoneIcon,
16
16
  DatabaseIcon,
17
+ QrCodeIcon,
17
18
  } from '../NetworkIcons';
18
19
 
19
20
  import {isReduxConnected} from '../../customHooks/reduxLogger';
@@ -100,9 +101,16 @@ const TabBar = React.memo(() => {
100
101
  count: 0,
101
102
  icon: 'storage',
102
103
  },
104
+ {
105
+ key: 'debugging',
106
+ label: 'Debugging',
107
+ count: 0,
108
+ icon: 'debugging',
109
+ },
103
110
  ] as const
104
111
  )
105
112
  .filter(tab => {
113
+ if (tab.key === 'debugging') return true;
106
114
  if (!tabVisibility?.[tab.key]) return false;
107
115
  if (tab.key === 'redux' && !isReduxAvail) return false;
108
116
  if (tab.key === 'analytics' && !isAnalyticsAvail) return false;
@@ -169,6 +177,9 @@ const TabBar = React.memo(() => {
169
177
  {tab.icon === 'storage' && (
170
178
  <DatabaseIcon color={iconColor} size={14} />
171
179
  )}
180
+ {tab.icon === 'debugging' && (
181
+ <QrCodeIcon color={iconColor} size={14} />
182
+ )}
172
183
  <Text
173
184
  numberOfLines={1}
174
185
  ellipsizeMode="tail"
@@ -275,33 +275,6 @@ const LogCard = React.memo(function LogCard({
275
275
  </Text>
276
276
  </View>
277
277
  )}
278
-
279
- {/* In-Line Duration / Latency Pill */}
280
- {item.duration != null && !isFailed && (
281
- <View
282
- style={[
283
- styles.chip,
284
- {
285
- backgroundColor: `${durationColor}12`,
286
- borderColor: `${durationColor}2E`,
287
- paddingHorizontal: 4.5,
288
- paddingVertical: 1,
289
- borderRadius: 4,
290
- },
291
- ]}>
292
- <Text
293
- style={[
294
- styles.chipText,
295
- {
296
- fontFamily: AppFonts.interBold,
297
- fontSize: 8.5,
298
- color: durationColor,
299
- },
300
- ]}>
301
- {item.duration}ms
302
- </Text>
303
- </View>
304
- )}
305
278
  </View>
306
279
 
307
280
  <View
@@ -321,6 +294,7 @@ const LogCard = React.memo(function LogCard({
321
294
  : isLoading
322
295
  ? `${AppColors.darkOrange}30`
323
296
  : `${statusColor}30`,
297
+ flexShrink: 0,
324
298
  },
325
299
  ]}>
326
300
  {renderStatusIcon()}
@@ -359,6 +333,7 @@ const LogCard = React.memo(function LogCard({
359
333
  alignItems: 'center',
360
334
  justifyContent: 'center',
361
335
  marginRight: 4,
336
+ flexShrink: 0,
362
337
  }}>
363
338
  <GlobeIcon color={AppColors.skyBlue} size={11} />
364
339
  </View>
@@ -411,8 +386,8 @@ const LogCard = React.memo(function LogCard({
411
386
 
412
387
  {/* Row 3: Footer (Timestamp & Origin on Left, Response Size & Duration on Right) */}
413
388
  <View style={styles.cardFooterRow}>
414
- <View style={{flexDirection: 'row', alignItems: 'center', gap: 6, flexShrink: 1, minWidth: 0}}>
415
- <View style={styles.cardDateRow}>
389
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 6, flex: 1, minWidth: 0, marginRight: 8}}>
390
+ <View style={[styles.cardDateRow, {flexShrink: 0}]}>
416
391
  <CalendarIcon color={AppColors.grayTextWeak} size={10} />
417
392
  <Text style={styles.cardDateText}>{triggeredAt}</Text>
418
393
  </View>
@@ -431,7 +406,7 @@ const LogCard = React.memo(function LogCard({
431
406
  paddingVertical: 1,
432
407
  borderRadius: 4,
433
408
  flexShrink: 1,
434
- maxWidth: 160,
409
+ minWidth: 0,
435
410
  },
436
411
  ]}>
437
412
  <PinIcon color={AppColors.skyBlue} size={8.5} />
@@ -2219,3 +2219,69 @@ export const PlusIcon = ({
2219
2219
  </Svg>
2220
2220
  );
2221
2221
 
2222
+ export const LockIcon = ({
2223
+ color = AppColors.grayTextWeak,
2224
+ size = 14,
2225
+ }: IconProps) => (
2226
+ <Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
2227
+ <Rect
2228
+ x="3"
2229
+ y="11"
2230
+ width="18"
2231
+ height="11"
2232
+ rx="2"
2233
+ ry="2"
2234
+ stroke={color}
2235
+ strokeWidth="2"
2236
+ strokeLinecap="round"
2237
+ strokeLinejoin="round"
2238
+ />
2239
+ <Path
2240
+ d="M7 11V7a5 5 0 0 1 10 0v4"
2241
+ stroke={color}
2242
+ strokeWidth="2"
2243
+ strokeLinecap="round"
2244
+ strokeLinejoin="round"
2245
+ />
2246
+ </Svg>
2247
+ );
2248
+
2249
+ export const QrCodeIcon = ({
2250
+ color = AppColors.grayTextWeak,
2251
+ size = 14,
2252
+ }: IconProps) => (
2253
+ <Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
2254
+ <Rect
2255
+ x="3"
2256
+ y="3"
2257
+ width="7"
2258
+ height="7"
2259
+ rx="1.5"
2260
+ stroke={color}
2261
+ strokeWidth="2"
2262
+ />
2263
+ <Rect
2264
+ x="14"
2265
+ y="3"
2266
+ width="7"
2267
+ height="7"
2268
+ rx="1.5"
2269
+ stroke={color}
2270
+ strokeWidth="2"
2271
+ />
2272
+ <Rect
2273
+ x="3"
2274
+ y="14"
2275
+ width="7"
2276
+ height="7"
2277
+ rx="1.5"
2278
+ stroke={color}
2279
+ strokeWidth="2"
2280
+ />
2281
+ <Path
2282
+ d="M14 14h3v3h-3zM18 18h3v3h-3zM14 19h3v2h-3zM19 14h2v3h-2z"
2283
+ fill={color}
2284
+ />
2285
+ </Svg>
2286
+ );
2287
+