react-native-inapp-inspector 2.2.2 → 2.2.4

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 (159) hide show
  1. package/dist/commonjs/components/AnalyticsDetail.js +5 -1
  2. package/dist/commonjs/components/AnalyticsEventCard.js +2 -1
  3. package/dist/commonjs/components/DiffViewer.js +1 -1
  4. package/dist/commonjs/components/Inspector/BundleTab.js +22 -0
  5. package/dist/commonjs/components/Inspector/DeviceInfoTab.d.ts +3 -0
  6. package/dist/commonjs/components/Inspector/DeviceInfoTab.js +835 -0
  7. package/dist/commonjs/components/Inspector/InspectorHeader.js +12 -14
  8. package/dist/commonjs/components/Inspector/LogDetail.js +6 -3
  9. package/dist/commonjs/components/Inspector/MainScreen.js +7 -3
  10. package/dist/commonjs/components/Inspector/NetworkTab.js +92 -15
  11. package/dist/commonjs/components/Inspector/PerformanceTab.js +38 -12
  12. package/dist/commonjs/components/Inspector/SettingsPanel.js +440 -229
  13. package/dist/commonjs/components/Inspector/StorageTab.d.ts +3 -0
  14. package/dist/commonjs/components/Inspector/StorageTab.js +892 -0
  15. package/dist/commonjs/components/Inspector/TabBar.js +14 -0
  16. package/dist/commonjs/components/Inspector/TelemetryConsentModal.js +11 -2
  17. package/dist/commonjs/components/Inspector/UpdateAvailableModal.js +4 -1
  18. package/dist/commonjs/components/NetworkIcons.d.ts +6 -0
  19. package/dist/commonjs/components/NetworkIcons.js +30 -1
  20. package/dist/commonjs/constants/version.d.ts +1 -1
  21. package/dist/commonjs/constants/version.js +1 -1
  22. package/dist/commonjs/customHooks/bundleAnalyzer.js +44 -22
  23. package/dist/commonjs/customHooks/storageInspector.d.ts +67 -0
  24. package/dist/commonjs/customHooks/storageInspector.js +486 -0
  25. package/dist/commonjs/i18n/locales/en.json +8 -2
  26. package/dist/commonjs/index.d.ts +1 -0
  27. package/dist/commonjs/index.js +16 -1
  28. package/dist/commonjs/storage.d.ts +5 -0
  29. package/dist/commonjs/storage.js +18 -0
  30. package/dist/commonjs/types/enums.d.ts +5 -0
  31. package/dist/commonjs/types/enums.js +5 -0
  32. package/dist/esm/components/AnalyticsDetail.js +6 -2
  33. package/dist/esm/components/AnalyticsEventCard.js +2 -1
  34. package/dist/esm/components/DiffViewer.js +1 -1
  35. package/dist/esm/components/Inspector/BundleTab.js +22 -0
  36. package/dist/esm/components/Inspector/DeviceInfoTab.d.ts +3 -0
  37. package/dist/esm/components/Inspector/DeviceInfoTab.js +796 -0
  38. package/dist/esm/components/Inspector/InspectorHeader.js +13 -15
  39. package/dist/esm/components/Inspector/LogDetail.js +7 -4
  40. package/dist/esm/components/Inspector/MainScreen.js +7 -3
  41. package/dist/esm/components/Inspector/NetworkTab.js +93 -16
  42. package/dist/esm/components/Inspector/PerformanceTab.js +39 -13
  43. package/dist/esm/components/Inspector/SettingsPanel.js +441 -230
  44. package/dist/esm/components/Inspector/StorageTab.d.ts +3 -0
  45. package/dist/esm/components/Inspector/StorageTab.js +853 -0
  46. package/dist/esm/components/Inspector/TabBar.js +15 -1
  47. package/dist/esm/components/Inspector/TelemetryConsentModal.js +11 -2
  48. package/dist/esm/components/Inspector/UpdateAvailableModal.js +4 -1
  49. package/dist/esm/components/NetworkIcons.d.ts +6 -0
  50. package/dist/esm/components/NetworkIcons.js +23 -0
  51. package/dist/esm/constants/version.d.ts +1 -1
  52. package/dist/esm/constants/version.js +1 -1
  53. package/dist/esm/customHooks/bundleAnalyzer.js +44 -22
  54. package/dist/esm/customHooks/storageInspector.d.ts +67 -0
  55. package/dist/esm/customHooks/storageInspector.js +469 -0
  56. package/dist/esm/i18n/locales/en.json +8 -2
  57. package/dist/esm/index.d.ts +1 -0
  58. package/dist/esm/index.js +5 -0
  59. package/dist/esm/storage.d.ts +5 -0
  60. package/dist/esm/storage.js +5 -0
  61. package/dist/esm/types/enums.d.ts +5 -0
  62. package/dist/esm/types/enums.js +5 -0
  63. package/package.json +9 -1
  64. package/src/analytics.ts +35 -0
  65. package/src/bundle.ts +25 -0
  66. package/src/components/AnalyticsDetail.tsx +870 -0
  67. package/src/components/AnalyticsEventCard.tsx +442 -0
  68. package/src/components/AnalyticsGraph.tsx +583 -0
  69. package/src/components/AnimatedEntrance.tsx +64 -0
  70. package/src/components/AppHeaderLogo.tsx +109 -0
  71. package/src/components/BrandCircleIcon.tsx +144 -0
  72. package/src/components/BrandSquareIcon.tsx +144 -0
  73. package/src/components/CodeSnippet.tsx +725 -0
  74. package/src/components/ConsoleLogCard.tsx +628 -0
  75. package/src/components/CopyButton.tsx +87 -0
  76. package/src/components/DiffViewer.tsx +82 -0
  77. package/src/components/DomainHeader.tsx +237 -0
  78. package/src/components/EmptyState.tsx +73 -0
  79. package/src/components/EndOfListFooter.tsx +100 -0
  80. package/src/components/ErrorBoundary.tsx +688 -0
  81. package/src/components/HeadersSection.tsx +192 -0
  82. package/src/components/HighlightText.tsx +100 -0
  83. package/src/components/Inspector/AnalyticsFilterModal.tsx +1250 -0
  84. package/src/components/Inspector/AnalyticsTab.tsx +1336 -0
  85. package/src/components/Inspector/BundleTab.tsx +4760 -0
  86. package/src/components/Inspector/ConsoleTab.tsx +702 -0
  87. package/src/components/Inspector/CrashDetail.tsx +941 -0
  88. package/src/components/Inspector/CrashFilterModal.tsx +721 -0
  89. package/src/components/Inspector/CrashTab.tsx +871 -0
  90. package/src/components/Inspector/DeviceInfoTab.tsx +1204 -0
  91. package/src/components/Inspector/FabLauncher.tsx +80 -0
  92. package/src/components/Inspector/InspectorContext.tsx +28 -0
  93. package/src/components/Inspector/InspectorHeader.tsx +972 -0
  94. package/src/components/Inspector/LogDetail.tsx +1431 -0
  95. package/src/components/Inspector/MainScreen.tsx +262 -0
  96. package/src/components/Inspector/NavigationTracker.tsx +13 -0
  97. package/src/components/Inspector/NetworkDetail.tsx +794 -0
  98. package/src/components/Inspector/NetworkTab.tsx +1183 -0
  99. package/src/components/Inspector/NpmUpdateToast.tsx +392 -0
  100. package/src/components/Inspector/PerformanceTab.tsx +1928 -0
  101. package/src/components/Inspector/ReduxDetail.tsx +1651 -0
  102. package/src/components/Inspector/ReduxTab.tsx +954 -0
  103. package/src/components/Inspector/SettingsPanel.tsx +3447 -0
  104. package/src/components/Inspector/StorageTab.tsx +1048 -0
  105. package/src/components/Inspector/TabBar.tsx +207 -0
  106. package/src/components/Inspector/TelemetryConsentModal.tsx +416 -0
  107. package/src/components/Inspector/UpdateAvailableModal.tsx +557 -0
  108. package/src/components/JsonViewer.tsx +486 -0
  109. package/src/components/LogCard.tsx +491 -0
  110. package/src/components/LogSyntaxHighlighter.tsx +178 -0
  111. package/src/components/MetaAccordion.tsx +340 -0
  112. package/src/components/MiniBarChart.tsx +42 -0
  113. package/src/components/MiniLineChart.tsx +33 -0
  114. package/src/components/NetworkIcons.tsx +2221 -0
  115. package/src/components/SectionHeader.tsx +113 -0
  116. package/src/components/SegmentedTabs.tsx +83 -0
  117. package/src/components/Slider.tsx +300 -0
  118. package/src/components/SourcePageCard.tsx +148 -0
  119. package/src/components/Toast.tsx +131 -0
  120. package/src/components/TouchableScale.tsx +91 -0
  121. package/src/components/TreeNode.tsx +186 -0
  122. package/src/console.ts +24 -0
  123. package/src/constants/index.ts +38 -0
  124. package/src/constants/version.ts +3 -0
  125. package/src/crash.ts +32 -0
  126. package/src/customHooks/analyticsLogger.ts +336 -0
  127. package/src/customHooks/bundleAnalyzer.ts +1256 -0
  128. package/src/customHooks/consoleLogger.ts +497 -0
  129. package/src/customHooks/crashHandler.ts +944 -0
  130. package/src/customHooks/logFilters.ts +32 -0
  131. package/src/customHooks/networkLogger.ts +419 -0
  132. package/src/customHooks/performanceTracker.ts +1014 -0
  133. package/src/customHooks/reduxLogger.ts +406 -0
  134. package/src/customHooks/storageInspector.ts +509 -0
  135. package/src/customHooks/useAccordion.tsx +60 -0
  136. package/src/decorators/index.ts +184 -0
  137. package/src/helpers/gaAnalyticsRegistry.ts +204 -0
  138. package/src/helpers/index.ts +860 -0
  139. package/src/helpers/memoryManager.ts +138 -0
  140. package/src/helpers/searchQueryParser.ts +283 -0
  141. package/src/helpers/settingsStore.ts +171 -0
  142. package/src/helpers/telemetry.ts +505 -0
  143. package/src/helpers/toast.ts +17 -0
  144. package/src/i18n/index.ts +69 -0
  145. package/src/i18n/locales/en.json +1058 -0
  146. package/src/index.tsx +2356 -0
  147. package/src/native/NativeInspector.ts +397 -0
  148. package/src/native/NativeNetworkInspector.ts +24 -0
  149. package/src/network.ts +22 -0
  150. package/src/performance.ts +38 -0
  151. package/src/redux.ts +23 -0
  152. package/src/storage.ts +18 -0
  153. package/src/styles/AppColors.ts +408 -0
  154. package/src/styles/AppFonts.ts +8 -0
  155. package/src/styles/common.ts +209 -0
  156. package/src/styles/index.ts +1570 -0
  157. package/src/types/enums.ts +199 -0
  158. package/src/types/index.ts +34 -0
  159. package/src/types/interfaces.ts +515 -0
@@ -0,0 +1,1048 @@
1
+ import React, {useState, useEffect, useMemo, useCallback} from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ ScrollView,
6
+ TextInput,
7
+ Modal,
8
+ Alert,
9
+ StyleSheet,
10
+ ActivityIndicator,
11
+ Platform,
12
+ } from 'react-native';
13
+ import TouchableScale from '../TouchableScale';
14
+ import CopyButton from '../CopyButton';
15
+ import {AppColors} from '../../styles/AppColors';
16
+ import {AppFonts} from '../../styles/AppFonts';
17
+ import {useTranslation} from '../../i18n';
18
+ import {
19
+ DatabaseIcon,
20
+ SearchIcon,
21
+ ClearIcon,
22
+ PlusIcon,
23
+ PencilIcon,
24
+ TrashIcon,
25
+ CopyIcon,
26
+ CheckIcon,
27
+ CircleAlertIcon,
28
+ LayersIcon,
29
+ ResetIcon,
30
+ } from '../NetworkIcons';
31
+ import {
32
+ fetchStorageEntries,
33
+ setStorageEntry,
34
+ removeStorageEntry,
35
+ clearStorageDriver,
36
+ isAsyncStorageConnected,
37
+ isMMKVConnected,
38
+ getRegisteredMMKVInstanceIds,
39
+ subscribeToStorageChanges,
40
+ StorageDriver,
41
+ StorageEntry,
42
+ } from '../../customHooks/storageInspector';
43
+ import {copyToClipboard} from '../../helpers';
44
+ import {showToast} from '../../helpers/toast';
45
+
46
+ export const StorageTab = React.memo(() => {
47
+ const {t} = useTranslation();
48
+ const [activeDriver, setActiveDriver] = useState<StorageDriver>('asyncStorage');
49
+ const [activeMMKVId, setActiveMMKVId] = useState<string>('default');
50
+ const [entries, setEntries] = useState<StorageEntry[]>([]);
51
+ const [isLoading, setIsLoading] = useState<boolean>(true);
52
+ const [search, setSearch] = useState<string>('');
53
+ const [expandedKeys, setExpandedKeys] = useState<Record<string, boolean>>({});
54
+
55
+ // Modal State for Create / Edit
56
+ const [modalVisible, setModalVisible] = useState<boolean>(false);
57
+ const [modalMode, setModalMode] = useState<'create' | 'edit'>('create');
58
+ const [editKeyName, setEditKeyName] = useState<string>('');
59
+ const [editValue, setEditValue] = useState<string>('');
60
+ const [editType, setEditType] = useState<'string' | 'json' | 'number' | 'boolean'>('string');
61
+ const [jsonError, setJsonError] = useState<string | null>(null);
62
+
63
+ const mmkvInstanceIds = useMemo(() => getRegisteredMMKVInstanceIds(), []);
64
+
65
+ // Fetch entries
66
+ const loadEntries = useCallback(async () => {
67
+ setIsLoading(true);
68
+ try {
69
+ const data = await fetchStorageEntries(
70
+ activeDriver,
71
+ activeDriver === 'mmkv' ? activeMMKVId : undefined,
72
+ );
73
+ setEntries(data);
74
+ } catch {
75
+ setEntries([]);
76
+ } finally {
77
+ setIsLoading(false);
78
+ }
79
+ }, [activeDriver, activeMMKVId]);
80
+
81
+ useEffect(() => {
82
+ loadEntries();
83
+ const unsubscribe = subscribeToStorageChanges(loadEntries);
84
+ return unsubscribe;
85
+ }, [loadEntries]);
86
+
87
+ // Toggle JSON accordion
88
+ const toggleExpand = (key: string) => {
89
+ setExpandedKeys(prev => ({...prev, [key]: !prev[key]}));
90
+ };
91
+
92
+ // Open Create Modal
93
+ const handleOpenCreate = () => {
94
+ setModalMode('create');
95
+ setEditKeyName('');
96
+ setEditValue('');
97
+ setEditType('string');
98
+ setJsonError(null);
99
+ setModalVisible(true);
100
+ };
101
+
102
+ // Open Edit Modal
103
+ const handleOpenEdit = (entry: StorageEntry) => {
104
+ setModalMode('edit');
105
+ setEditKeyName(entry.key);
106
+ setEditValue(entry.value);
107
+ setEditType(entry.type === 'json' ? 'json' : entry.type === 'number' ? 'number' : entry.type === 'boolean' ? 'boolean' : 'string');
108
+ setJsonError(null);
109
+ setModalVisible(true);
110
+ };
111
+
112
+ // Delete single key
113
+ const handleDeleteKey = (key: string) => {
114
+ Alert.alert(
115
+ 'Delete Storage Key',
116
+ `Are you sure you want to permanently delete "${key}" from ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'}?`,
117
+ [
118
+ {text: 'Cancel', style: 'cancel'},
119
+ {
120
+ text: 'Delete',
121
+ style: 'destructive',
122
+ onPress: async () => {
123
+ const ok = await removeStorageEntry(
124
+ activeDriver,
125
+ key,
126
+ activeDriver === 'mmkv' ? activeMMKVId : undefined,
127
+ );
128
+ if (ok) {
129
+ showToast(`Deleted "${key}"`);
130
+ loadEntries();
131
+ } else {
132
+ showToast(`Failed to delete "${key}"`);
133
+ }
134
+ },
135
+ },
136
+ ],
137
+ );
138
+ };
139
+
140
+ // Clear all keys
141
+ const handleClearAll = () => {
142
+ Alert.alert(
143
+ 'Wipe All Storage Keys',
144
+ `Are you sure you want to clear all ${entries.length} keys in ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'}? This cannot be undone.`,
145
+ [
146
+ {text: 'Cancel', style: 'cancel'},
147
+ {
148
+ text: 'Wipe All',
149
+ style: 'destructive',
150
+ onPress: async () => {
151
+ const ok = await clearStorageDriver(
152
+ activeDriver,
153
+ activeDriver === 'mmkv' ? activeMMKVId : undefined,
154
+ );
155
+ if (ok) {
156
+ showToast('All storage keys wiped');
157
+ loadEntries();
158
+ }
159
+ },
160
+ },
161
+ ],
162
+ );
163
+ };
164
+
165
+ // Save Modal (Create / Update)
166
+ const handleSaveModal = async () => {
167
+ if (!editKeyName.trim()) {
168
+ showToast('Key name cannot be empty');
169
+ return;
170
+ }
171
+
172
+ let finalVal = editValue;
173
+
174
+ if (editType === 'json') {
175
+ try {
176
+ // Validate JSON
177
+ const parsed = JSON.parse(editValue);
178
+ finalVal = JSON.stringify(parsed);
179
+ } catch (err: any) {
180
+ setJsonError('Invalid JSON: ' + err.message);
181
+ return;
182
+ }
183
+ } else if (editType === 'number') {
184
+ if (isNaN(Number(editValue))) {
185
+ setJsonError('Value must be a valid number');
186
+ return;
187
+ }
188
+ }
189
+
190
+ const success = await setStorageEntry(
191
+ activeDriver,
192
+ editKeyName.trim(),
193
+ finalVal,
194
+ activeDriver === 'mmkv' ? activeMMKVId : undefined,
195
+ );
196
+
197
+ if (success) {
198
+ showToast(modalMode === 'create' ? `Added "${editKeyName.trim()}"` : `Updated "${editKeyName.trim()}"`);
199
+ setModalVisible(false);
200
+ loadEntries();
201
+ } else {
202
+ showToast('Error saving storage key');
203
+ }
204
+ };
205
+
206
+ // Format / Beautify JSON in modal
207
+ const handleBeautifyJson = () => {
208
+ try {
209
+ const parsed = JSON.parse(editValue);
210
+ setEditValue(JSON.stringify(parsed, null, 2));
211
+ setJsonError(null);
212
+ setEditType('json');
213
+ showToast('Formatted JSON');
214
+ } catch {
215
+ setJsonError('Cannot format: invalid JSON syntax');
216
+ }
217
+ };
218
+
219
+ // Filter entries
220
+ const filteredEntries = useMemo(() => {
221
+ if (!search.trim()) return entries;
222
+ const q = search.toLowerCase().trim();
223
+ return entries.filter(
224
+ e => e.key.toLowerCase().includes(q) || e.value.toLowerCase().includes(q),
225
+ );
226
+ }, [entries, search]);
227
+
228
+ const totalBytes = useMemo(() => {
229
+ return entries.reduce((sum, e) => sum + e.byteSize, 0);
230
+ }, [entries]);
231
+
232
+ const formattedTotalSize = useMemo(() => {
233
+ if (totalBytes < 1024) return `${totalBytes} B`;
234
+ if (totalBytes < 1024 * 1024) return `${(totalBytes / 1024).toFixed(1)} KB`;
235
+ return `${(totalBytes / (1024 * 1024)).toFixed(2)} MB`;
236
+ }, [totalBytes]);
237
+
238
+ const getTypeBadge = (type: StorageEntry['type']) => {
239
+ switch (type) {
240
+ case 'json':
241
+ return {label: 'JSON', color: AppColors.purple, bg: `${AppColors.purple}16`};
242
+ case 'number':
243
+ return {label: 'NUM', color: AppColors.warningIconGold, bg: `${AppColors.warningIconGold}16`};
244
+ case 'boolean':
245
+ return {label: 'BOOL', color: AppColors.emerald500, bg: `${AppColors.emerald500}16`};
246
+ case 'null':
247
+ return {label: 'NULL', color: AppColors.grayTextWeak, bg: `${AppColors.grayTextWeak}16`};
248
+ default:
249
+ return {label: 'STR', color: AppColors.blue500, bg: `${AppColors.blue500}16`};
250
+ }
251
+ };
252
+
253
+ const isConnected =
254
+ activeDriver === 'asyncStorage'
255
+ ? isAsyncStorageConnected()
256
+ : isMMKVConnected();
257
+
258
+ return (
259
+ <View style={styles.container}>
260
+ {/* ── Driver Sub-Tabs ── */}
261
+ <View style={styles.driverTabsWrapper}>
262
+ <ScrollView
263
+ horizontal
264
+ showsHorizontalScrollIndicator={false}
265
+ contentContainerStyle={styles.driverTabsContainer}>
266
+ <TouchableScale
267
+ onPress={() => setActiveDriver('asyncStorage')}
268
+ style={[
269
+ styles.driverTabPill,
270
+ activeDriver === 'asyncStorage' && styles.driverTabPillActive,
271
+ ]}>
272
+ <DatabaseIcon
273
+ size={13}
274
+ color={
275
+ activeDriver === 'asyncStorage'
276
+ ? AppColors.white
277
+ : AppColors.grayText
278
+ }
279
+ />
280
+ <Text
281
+ style={[
282
+ styles.driverTabText,
283
+ activeDriver === 'asyncStorage' && styles.driverTabTextActive,
284
+ ]}>
285
+ AsyncStorage
286
+ </Text>
287
+ {activeDriver === 'asyncStorage' && entries.length > 0 && (
288
+ <View style={styles.driverCountBadge}>
289
+ <Text style={styles.driverCountText}>{entries.length}</Text>
290
+ </View>
291
+ )}
292
+ </TouchableScale>
293
+
294
+ <TouchableScale
295
+ onPress={() => setActiveDriver('mmkv')}
296
+ style={[
297
+ styles.driverTabPill,
298
+ activeDriver === 'mmkv' && styles.driverTabPillActive,
299
+ ]}>
300
+ <LayersIcon
301
+ size={13}
302
+ color={
303
+ activeDriver === 'mmkv'
304
+ ? AppColors.white
305
+ : AppColors.grayText
306
+ }
307
+ />
308
+ <Text
309
+ style={[
310
+ styles.driverTabText,
311
+ activeDriver === 'mmkv' && styles.driverTabTextActive,
312
+ ]}>
313
+ MMKV
314
+ </Text>
315
+ {activeDriver === 'mmkv' && entries.length > 0 && (
316
+ <View style={styles.driverCountBadge}>
317
+ <Text style={styles.driverCountText}>{entries.length}</Text>
318
+ </View>
319
+ )}
320
+ </TouchableScale>
321
+
322
+ {/* MMKV Named Instance Dropdown/Pills */}
323
+ {activeDriver === 'mmkv' && mmkvInstanceIds.length > 1 && (
324
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 4, marginLeft: 6}}>
325
+ {mmkvInstanceIds.map(id => (
326
+ <TouchableScale
327
+ key={id}
328
+ onPress={() => setActiveMMKVId(id)}
329
+ style={[
330
+ styles.instancePill,
331
+ activeMMKVId === id && styles.instancePillActive,
332
+ ]}>
333
+ <Text
334
+ style={[
335
+ styles.instancePillText,
336
+ activeMMKVId === id && styles.instancePillTextActive,
337
+ ]}>
338
+ {id}
339
+ </Text>
340
+ </TouchableScale>
341
+ ))}
342
+ </View>
343
+ )}
344
+ </ScrollView>
345
+ </View>
346
+
347
+ {/* ── Search & Actions Bar ── */}
348
+ <View style={styles.actionBar}>
349
+ <View style={styles.searchBar}>
350
+ <SearchIcon size={14} color={AppColors.grayTextWeak} />
351
+ <TextInput
352
+ value={search}
353
+ onChangeText={setSearch}
354
+ placeholder={`Search ${entries.length} keys & values...`}
355
+ placeholderTextColor={AppColors.grayTextWeak}
356
+ style={styles.searchInput}
357
+ clearButtonMode="while-editing"
358
+ />
359
+ {search.length > 0 && (
360
+ <TouchableScale onPress={() => setSearch('')} hitSlop={8}>
361
+ <ClearIcon size={13} color={AppColors.grayTextWeak} />
362
+ </TouchableScale>
363
+ )}
364
+ </View>
365
+
366
+ {/* Action Buttons: Add Key & Refresh */}
367
+ <TouchableScale
368
+ onPress={handleOpenCreate}
369
+ style={styles.addKeyBtn}
370
+ hitSlop={6}>
371
+ <PlusIcon size={13} color={AppColors.white} />
372
+ <Text style={styles.addKeyBtnText}>Add</Text>
373
+ </TouchableScale>
374
+
375
+ <TouchableScale
376
+ onPress={loadEntries}
377
+ style={styles.actionIconBtn}
378
+ hitSlop={6}>
379
+ <ResetIcon size={13} color={AppColors.grayText} />
380
+ </TouchableScale>
381
+
382
+ {entries.length > 0 && (
383
+ <TouchableScale
384
+ onPress={handleClearAll}
385
+ style={[styles.actionIconBtn, {borderColor: `${AppColors.errorColor}33`, backgroundColor: `${AppColors.errorColor}12`}]}
386
+ hitSlop={6}>
387
+ <TrashIcon size={13} color={AppColors.errorColor} />
388
+ </TouchableScale>
389
+ )}
390
+ </View>
391
+
392
+ {/* ── Stats Strip ── */}
393
+ <View style={styles.statsStrip}>
394
+ <Text style={styles.statsText}>
395
+ {filteredEntries.length} of {entries.length} Keys • {formattedTotalSize}
396
+ </Text>
397
+ <Text style={styles.statsSubtext}>
398
+ {activeDriver === 'asyncStorage' ? 'SQLite Key-Value' : `MMKV (${activeMMKVId})`}
399
+ </Text>
400
+ </View>
401
+
402
+ {/* ── Storage Entries List ── */}
403
+ {isLoading ? (
404
+ <View style={styles.loadingContainer}>
405
+ <ActivityIndicator size="small" color={AppColors.purple} />
406
+ <Text style={styles.loadingText}>Loading storage entries...</Text>
407
+ </View>
408
+ ) : filteredEntries.length === 0 ? (
409
+ <ScrollView
410
+ contentContainerStyle={styles.emptyContainer}
411
+ showsVerticalScrollIndicator={false}>
412
+ <View style={styles.emptyIconWrap}>
413
+ <DatabaseIcon size={28} color={AppColors.grayTextWeak} />
414
+ </View>
415
+ <Text style={styles.emptyTitle}>
416
+ {search ? 'No matching keys found' : `No ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'} Keys`}
417
+ </Text>
418
+ <Text style={styles.emptySubtitle}>
419
+ {search
420
+ ? 'Try modifying your search query'
421
+ : `Storage is auto-detected. Tap "+ Add" above to create your first ${activeDriver === 'asyncStorage' ? 'AsyncStorage' : 'MMKV'} key!`}
422
+ </Text>
423
+ </ScrollView>
424
+ ) : (
425
+ <ScrollView
426
+ style={styles.scrollArea}
427
+ contentContainerStyle={styles.scrollContent}
428
+ showsVerticalScrollIndicator={false}>
429
+ {filteredEntries.map(entry => {
430
+ const isExpanded = Boolean(expandedKeys[entry.key]);
431
+ const badge = getTypeBadge(entry.type);
432
+ const formattedBytes =
433
+ entry.byteSize < 1024
434
+ ? `${entry.byteSize} B`
435
+ : `${(entry.byteSize / 1024).toFixed(1)} KB`;
436
+
437
+ return (
438
+ <View key={entry.key} style={styles.entryCard}>
439
+ {/* Entry Header: Key name, Type Badge, Size, Actions */}
440
+ <View style={styles.entryHeader}>
441
+ <View style={{flex: 1, marginRight: 8}}>
442
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap'}}>
443
+ <Text style={styles.entryKey} numberOfLines={1} selectable>
444
+ {entry.key}
445
+ </Text>
446
+ <View
447
+ style={[
448
+ styles.typeBadge,
449
+ {backgroundColor: badge.bg, borderColor: `${badge.color}33`},
450
+ ]}>
451
+ <Text style={[styles.typeBadgeText, {color: badge.color}]}>
452
+ {badge.label}
453
+ </Text>
454
+ </View>
455
+ <Text style={styles.sizeText}>{formattedBytes}</Text>
456
+ </View>
457
+ </View>
458
+
459
+ {/* Actions: Copy, Edit, Delete */}
460
+ <View style={styles.entryActions}>
461
+ <TouchableScale
462
+ hitSlop={6}
463
+ onPress={() => {
464
+ copyToClipboard(entry.value, entry.key);
465
+ showToast(`Copied value of "${entry.key}"`);
466
+ }}
467
+ style={styles.entryActionBtn}>
468
+ <CopyIcon size={12} color={AppColors.grayText} />
469
+ </TouchableScale>
470
+
471
+ <TouchableScale
472
+ hitSlop={6}
473
+ onPress={() => handleOpenEdit(entry)}
474
+ style={styles.entryActionBtn}>
475
+ <PencilIcon size={12} color={AppColors.purple} />
476
+ </TouchableScale>
477
+
478
+ <TouchableScale
479
+ hitSlop={6}
480
+ onPress={() => handleDeleteKey(entry.key)}
481
+ style={[styles.entryActionBtn, {backgroundColor: `${AppColors.errorColor}12`}]}>
482
+ <TrashIcon size={12} color={AppColors.errorColor} />
483
+ </TouchableScale>
484
+ </View>
485
+ </View>
486
+
487
+ {/* Entry Value Preview / Viewer */}
488
+ <TouchableScale
489
+ onPress={() => toggleExpand(entry.key)}
490
+ style={styles.valuePreviewBox}>
491
+ <Text
492
+ style={styles.valuePreviewText}
493
+ numberOfLines={isExpanded ? undefined : 3}
494
+ selectable>
495
+ {entry.type === 'json' && entry.parsedValue
496
+ ? isExpanded
497
+ ? JSON.stringify(entry.parsedValue, null, 2)
498
+ : entry.value
499
+ : entry.value}
500
+ </Text>
501
+ {entry.value.length > 80 && (
502
+ <Text style={styles.expandHint}>
503
+ {isExpanded ? '▲ Collapse' : '▼ Expand'}
504
+ </Text>
505
+ )}
506
+ </TouchableScale>
507
+ </View>
508
+ );
509
+ })}
510
+ <View style={{height: 60}} />
511
+ </ScrollView>
512
+ )}
513
+
514
+ {/* ── Create / Edit Key Modal ── */}
515
+ <Modal
516
+ visible={modalVisible}
517
+ transparent
518
+ animationType="fade"
519
+ onRequestClose={() => setModalVisible(false)}>
520
+ <View style={styles.modalOverlay}>
521
+ <View style={styles.modalCard}>
522
+ <View style={styles.modalHeader}>
523
+ <Text style={styles.modalTitle}>
524
+ {modalMode === 'create' ? 'Add Storage Key' : `Edit: ${editKeyName}`}
525
+ </Text>
526
+ <TouchableScale onPress={() => setModalVisible(false)} hitSlop={8}>
527
+ <ClearIcon size={16} color={AppColors.grayText} />
528
+ </TouchableScale>
529
+ </View>
530
+
531
+ {/* Key Name Input */}
532
+ <View style={styles.inputGroup}>
533
+ <Text style={styles.inputLabel}>KEY NAME</Text>
534
+ <TextInput
535
+ value={editKeyName}
536
+ onChangeText={setEditKeyName}
537
+ placeholder="e.g. user_session, auth_token"
538
+ placeholderTextColor={AppColors.grayTextWeak}
539
+ editable={modalMode === 'create'}
540
+ style={[
541
+ styles.textInput,
542
+ modalMode === 'edit' && styles.textInputDisabled,
543
+ ]}
544
+ autoCapitalize="none"
545
+ />
546
+ </View>
547
+
548
+ {/* Type Selector Strip */}
549
+ <View style={styles.inputGroup}>
550
+ <View style={{flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4}}>
551
+ <Text style={styles.inputLabel}>VALUE TYPE</Text>
552
+ {editType === 'json' && (
553
+ <TouchableScale onPress={handleBeautifyJson} hitSlop={6}>
554
+ <Text style={styles.formatJsonBtn}>Beautify JSON</Text>
555
+ </TouchableScale>
556
+ )}
557
+ </View>
558
+ <View style={styles.typeSelectorStrip}>
559
+ {(['string', 'json', 'number', 'boolean'] as const).map(t => {
560
+ const isSel = editType === t;
561
+ return (
562
+ <TouchableScale
563
+ key={t}
564
+ onPress={() => {
565
+ setEditType(t);
566
+ setJsonError(null);
567
+ }}
568
+ style={[
569
+ styles.typePill,
570
+ isSel && styles.typePillActive,
571
+ ]}>
572
+ <Text
573
+ style={[
574
+ styles.typePillText,
575
+ isSel && styles.typePillTextActive,
576
+ ]}>
577
+ {t.toUpperCase()}
578
+ </Text>
579
+ </TouchableScale>
580
+ );
581
+ })}
582
+ </View>
583
+ </View>
584
+
585
+ {/* Value Input */}
586
+ <View style={styles.inputGroup}>
587
+ <Text style={styles.inputLabel}>VALUE CONTENT</Text>
588
+ <TextInput
589
+ value={editValue}
590
+ onChangeText={v => {
591
+ setEditValue(v);
592
+ if (jsonError) setJsonError(null);
593
+ }}
594
+ placeholder={
595
+ editType === 'json'
596
+ ? '{"userId": 123, "isLoggedIn": true}'
597
+ : editType === 'number'
598
+ ? '42'
599
+ : editType === 'boolean'
600
+ ? 'true'
601
+ : 'String value...'
602
+ }
603
+ placeholderTextColor={AppColors.grayTextWeak}
604
+ style={[styles.textInput, styles.textAreaInput]}
605
+ multiline
606
+ textAlignVertical="top"
607
+ autoCapitalize="none"
608
+ />
609
+ {jsonError && (
610
+ <Text style={styles.errorText}>⚠️ {jsonError}</Text>
611
+ )}
612
+ </View>
613
+
614
+ {/* Modal Actions */}
615
+ <View style={styles.modalActions}>
616
+ <TouchableScale
617
+ onPress={() => setModalVisible(false)}
618
+ style={styles.cancelBtn}>
619
+ <Text style={styles.cancelBtnText}>Cancel</Text>
620
+ </TouchableScale>
621
+ <TouchableScale
622
+ onPress={handleSaveModal}
623
+ style={styles.saveBtn}>
624
+ <CheckIcon size={14} color={AppColors.white} />
625
+ <Text style={styles.saveBtnText}>Save Key</Text>
626
+ </TouchableScale>
627
+ </View>
628
+ </View>
629
+ </View>
630
+ </Modal>
631
+ </View>
632
+ );
633
+ });
634
+
635
+ const styles = StyleSheet.create({
636
+ container: {
637
+ flex: 1,
638
+ backgroundColor: AppColors.grayBackground,
639
+ },
640
+ driverTabsWrapper: {
641
+ backgroundColor: AppColors.primaryLight,
642
+ borderBottomWidth: 1,
643
+ borderBottomColor: AppColors.dividerColor,
644
+ paddingVertical: 8,
645
+ },
646
+ driverTabsContainer: {
647
+ paddingHorizontal: 12,
648
+ flexDirection: 'row',
649
+ alignItems: 'center',
650
+ gap: 6,
651
+ },
652
+ driverTabPill: {
653
+ flexDirection: 'row',
654
+ alignItems: 'center',
655
+ gap: 6,
656
+ paddingHorizontal: 11,
657
+ paddingVertical: 6,
658
+ borderRadius: 8,
659
+ backgroundColor: AppColors.grayBackground,
660
+ borderWidth: 1,
661
+ borderColor: AppColors.grayBorderSecondary,
662
+ },
663
+ driverTabPillActive: {
664
+ backgroundColor: AppColors.purple,
665
+ borderColor: AppColors.purple,
666
+ },
667
+ driverTabText: {
668
+ fontFamily: AppFonts.interBold,
669
+ fontSize: 11.5,
670
+ color: AppColors.grayText,
671
+ },
672
+ driverTabTextActive: {
673
+ color: AppColors.white,
674
+ },
675
+ driverCountBadge: {
676
+ backgroundColor: 'rgba(255, 255, 255, 0.25)',
677
+ paddingHorizontal: 5,
678
+ paddingVertical: 1,
679
+ borderRadius: 6,
680
+ },
681
+ driverCountText: {
682
+ fontFamily: AppFonts.interBold,
683
+ fontSize: 9,
684
+ color: AppColors.white,
685
+ },
686
+ instancePill: {
687
+ paddingHorizontal: 7,
688
+ paddingVertical: 3,
689
+ borderRadius: 6,
690
+ backgroundColor: `${AppColors.purple}14`,
691
+ borderWidth: 1,
692
+ borderColor: `${AppColors.purple}30`,
693
+ },
694
+ instancePillActive: {
695
+ backgroundColor: AppColors.purple,
696
+ borderColor: AppColors.purple,
697
+ },
698
+ instancePillText: {
699
+ fontFamily: AppFonts.interBold,
700
+ fontSize: 10,
701
+ color: AppColors.purple,
702
+ },
703
+ instancePillTextActive: {
704
+ color: AppColors.white,
705
+ },
706
+ actionBar: {
707
+ flexDirection: 'row',
708
+ alignItems: 'center',
709
+ paddingHorizontal: 12,
710
+ paddingVertical: 8,
711
+ backgroundColor: AppColors.primaryLight,
712
+ borderBottomWidth: 1,
713
+ borderBottomColor: AppColors.dividerColor,
714
+ gap: 6,
715
+ },
716
+ searchBar: {
717
+ flex: 1,
718
+ flexDirection: 'row',
719
+ alignItems: 'center',
720
+ backgroundColor: AppColors.grayBackground,
721
+ borderRadius: 8,
722
+ paddingHorizontal: 9,
723
+ paddingVertical: Platform.OS === 'ios' ? 6 : 2,
724
+ borderWidth: 1,
725
+ borderColor: AppColors.grayBorderSecondary,
726
+ gap: 6,
727
+ },
728
+ searchInput: {
729
+ flex: 1,
730
+ fontFamily: AppFonts.interMedium,
731
+ fontSize: 12,
732
+ color: AppColors.primaryBlack,
733
+ padding: 0,
734
+ },
735
+ addKeyBtn: {
736
+ flexDirection: 'row',
737
+ alignItems: 'center',
738
+ gap: 4,
739
+ paddingHorizontal: 10,
740
+ paddingVertical: 6,
741
+ borderRadius: 8,
742
+ backgroundColor: AppColors.purple,
743
+ },
744
+ addKeyBtnText: {
745
+ fontFamily: AppFonts.interBold,
746
+ fontSize: 11,
747
+ color: AppColors.white,
748
+ },
749
+ actionIconBtn: {
750
+ width: 30,
751
+ height: 30,
752
+ borderRadius: 8,
753
+ alignItems: 'center',
754
+ justifyContent: 'center',
755
+ backgroundColor: AppColors.grayBackground,
756
+ borderWidth: 1,
757
+ borderColor: AppColors.grayBorderSecondary,
758
+ },
759
+ statsStrip: {
760
+ flexDirection: 'row',
761
+ alignItems: 'center',
762
+ justifyContent: 'space-between',
763
+ paddingHorizontal: 12,
764
+ paddingVertical: 5,
765
+ backgroundColor: `${AppColors.grayBorderSecondary}40`,
766
+ },
767
+ statsText: {
768
+ fontFamily: AppFonts.interMedium,
769
+ fontSize: 10,
770
+ color: AppColors.grayText,
771
+ },
772
+ statsSubtext: {
773
+ fontFamily: AppFonts.interBold,
774
+ fontSize: 9.5,
775
+ color: AppColors.grayTextWeak,
776
+ },
777
+ scrollArea: {
778
+ flex: 1,
779
+ },
780
+ scrollContent: {
781
+ padding: 12,
782
+ gap: 10,
783
+ paddingBottom: 100,
784
+ },
785
+ entryCard: {
786
+ backgroundColor: AppColors.primaryLight,
787
+ borderRadius: 10,
788
+ borderWidth: 1,
789
+ borderColor: AppColors.grayBorderSecondary,
790
+ padding: 10,
791
+ gap: 8,
792
+ shadowColor: AppColors.black,
793
+ shadowOpacity: 0.02,
794
+ shadowRadius: 3,
795
+ shadowOffset: {width: 0, height: 1},
796
+ elevation: 1,
797
+ },
798
+ entryHeader: {
799
+ flexDirection: 'row',
800
+ alignItems: 'center',
801
+ justifyContent: 'space-between',
802
+ },
803
+ entryKey: {
804
+ fontFamily: AppFonts.interBold,
805
+ fontSize: 12.5,
806
+ color: AppColors.primaryBlack,
807
+ },
808
+ typeBadge: {
809
+ paddingHorizontal: 5,
810
+ paddingVertical: 1,
811
+ borderRadius: 4,
812
+ borderWidth: 1,
813
+ },
814
+ typeBadgeText: {
815
+ fontFamily: AppFonts.interBold,
816
+ fontSize: 8.5,
817
+ },
818
+ sizeText: {
819
+ fontFamily: AppFonts.interRegular,
820
+ fontSize: 10,
821
+ color: AppColors.grayTextWeak,
822
+ },
823
+ entryActions: {
824
+ flexDirection: 'row',
825
+ alignItems: 'center',
826
+ gap: 5,
827
+ },
828
+ entryActionBtn: {
829
+ padding: 5,
830
+ borderRadius: 5,
831
+ backgroundColor: AppColors.grayBackground,
832
+ },
833
+ valuePreviewBox: {
834
+ backgroundColor: AppColors.grayBackground,
835
+ borderRadius: 6,
836
+ padding: 8,
837
+ borderWidth: 1,
838
+ borderColor: `${AppColors.grayBorderSecondary}80`,
839
+ },
840
+ valuePreviewText: {
841
+ fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
842
+ fontSize: 11,
843
+ color: AppColors.primaryBlack,
844
+ lineHeight: 16,
845
+ },
846
+ expandHint: {
847
+ fontFamily: AppFonts.interBold,
848
+ fontSize: 9.5,
849
+ color: AppColors.purple,
850
+ marginTop: 4,
851
+ },
852
+ loadingContainer: {
853
+ flex: 1,
854
+ alignItems: 'center',
855
+ justifyContent: 'center',
856
+ gap: 8,
857
+ },
858
+ loadingText: {
859
+ fontFamily: AppFonts.interMedium,
860
+ fontSize: 12,
861
+ color: AppColors.grayText,
862
+ },
863
+ emptyContainer: {
864
+ padding: 24,
865
+ alignItems: 'center',
866
+ justifyContent: 'center',
867
+ },
868
+ emptyIconWrap: {
869
+ width: 56,
870
+ height: 56,
871
+ borderRadius: 28,
872
+ backgroundColor: `${AppColors.grayBorderSecondary}40`,
873
+ alignItems: 'center',
874
+ justifyContent: 'center',
875
+ marginBottom: 12,
876
+ },
877
+ emptyTitle: {
878
+ fontFamily: AppFonts.interBold,
879
+ fontSize: 14,
880
+ color: AppColors.primaryBlack,
881
+ marginBottom: 4,
882
+ },
883
+ emptySubtitle: {
884
+ fontFamily: AppFonts.interRegular,
885
+ fontSize: 11.5,
886
+ color: AppColors.grayText,
887
+ textAlign: 'center',
888
+ maxWidth: 280,
889
+ },
890
+ connectGuideCard: {
891
+ marginTop: 20,
892
+ width: '100%',
893
+ backgroundColor: AppColors.primaryLight,
894
+ borderRadius: 10,
895
+ borderWidth: 1,
896
+ borderColor: AppColors.grayBorderSecondary,
897
+ padding: 12,
898
+ gap: 6,
899
+ },
900
+ connectGuideTitle: {
901
+ fontFamily: AppFonts.interBold,
902
+ fontSize: 11,
903
+ color: AppColors.purple,
904
+ },
905
+ connectGuideCode: {
906
+ fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
907
+ fontSize: 10,
908
+ color: AppColors.grayText,
909
+ lineHeight: 15,
910
+ },
911
+ modalOverlay: {
912
+ flex: 1,
913
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
914
+ justifyContent: 'center',
915
+ alignItems: 'center',
916
+ padding: 16,
917
+ },
918
+ modalCard: {
919
+ width: '100%',
920
+ maxWidth: 400,
921
+ backgroundColor: AppColors.primaryLight,
922
+ borderRadius: 14,
923
+ padding: 16,
924
+ gap: 12,
925
+ shadowColor: AppColors.black,
926
+ shadowOpacity: 0.2,
927
+ shadowRadius: 10,
928
+ shadowOffset: {width: 0, height: 4},
929
+ elevation: 5,
930
+ },
931
+ modalHeader: {
932
+ flexDirection: 'row',
933
+ alignItems: 'center',
934
+ justifyContent: 'space-between',
935
+ borderBottomWidth: 1,
936
+ borderBottomColor: AppColors.dividerColor,
937
+ paddingBottom: 10,
938
+ },
939
+ modalTitle: {
940
+ fontFamily: AppFonts.interBold,
941
+ fontSize: 14,
942
+ color: AppColors.primaryBlack,
943
+ },
944
+ inputGroup: {
945
+ gap: 4,
946
+ },
947
+ inputLabel: {
948
+ fontFamily: AppFonts.interBold,
949
+ fontSize: 9.5,
950
+ color: AppColors.grayTextWeak,
951
+ letterSpacing: 0.5,
952
+ },
953
+ textInput: {
954
+ backgroundColor: AppColors.grayBackground,
955
+ borderRadius: 8,
956
+ paddingHorizontal: 10,
957
+ paddingVertical: 8,
958
+ fontFamily: AppFonts.interMedium,
959
+ fontSize: 12,
960
+ color: AppColors.primaryBlack,
961
+ borderWidth: 1,
962
+ borderColor: AppColors.grayBorderSecondary,
963
+ },
964
+ textInputDisabled: {
965
+ backgroundColor: `${AppColors.grayBorderSecondary}40`,
966
+ color: AppColors.grayText,
967
+ },
968
+ textAreaInput: {
969
+ minHeight: 90,
970
+ maxHeight: 160,
971
+ fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
972
+ fontSize: 11,
973
+ },
974
+ typeSelectorStrip: {
975
+ flexDirection: 'row',
976
+ alignItems: 'center',
977
+ gap: 6,
978
+ },
979
+ typePill: {
980
+ flex: 1,
981
+ alignItems: 'center',
982
+ paddingVertical: 6,
983
+ borderRadius: 6,
984
+ backgroundColor: AppColors.grayBackground,
985
+ borderWidth: 1,
986
+ borderColor: AppColors.grayBorderSecondary,
987
+ },
988
+ typePillActive: {
989
+ backgroundColor: AppColors.purple,
990
+ borderColor: AppColors.purple,
991
+ },
992
+ typePillText: {
993
+ fontFamily: AppFonts.interBold,
994
+ fontSize: 10,
995
+ color: AppColors.grayText,
996
+ },
997
+ typePillTextActive: {
998
+ color: AppColors.white,
999
+ },
1000
+ formatJsonBtn: {
1001
+ fontFamily: AppFonts.interBold,
1002
+ fontSize: 10,
1003
+ color: AppColors.purple,
1004
+ },
1005
+ errorText: {
1006
+ fontFamily: AppFonts.interMedium,
1007
+ fontSize: 10,
1008
+ color: AppColors.errorColor,
1009
+ marginTop: 2,
1010
+ },
1011
+ modalActions: {
1012
+ flexDirection: 'row',
1013
+ alignItems: 'center',
1014
+ gap: 10,
1015
+ marginTop: 4,
1016
+ },
1017
+ cancelBtn: {
1018
+ flex: 1,
1019
+ alignItems: 'center',
1020
+ paddingVertical: 10,
1021
+ borderRadius: 8,
1022
+ backgroundColor: AppColors.grayBackground,
1023
+ borderWidth: 1,
1024
+ borderColor: AppColors.grayBorderSecondary,
1025
+ },
1026
+ cancelBtnText: {
1027
+ fontFamily: AppFonts.interBold,
1028
+ fontSize: 12,
1029
+ color: AppColors.grayText,
1030
+ },
1031
+ saveBtn: {
1032
+ flex: 1.5,
1033
+ flexDirection: 'row',
1034
+ alignItems: 'center',
1035
+ justifyContent: 'center',
1036
+ gap: 5,
1037
+ paddingVertical: 10,
1038
+ borderRadius: 8,
1039
+ backgroundColor: AppColors.purple,
1040
+ },
1041
+ saveBtnText: {
1042
+ fontFamily: AppFonts.interBold,
1043
+ fontSize: 12,
1044
+ color: AppColors.white,
1045
+ },
1046
+ });
1047
+
1048
+ export default StorageTab;