react-native-inapp-inspector 1.1.11 → 1.1.13
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/dist/commonjs/components/AnalyticsDetail.js +93 -479
- package/dist/commonjs/components/AnalyticsEventCard.js +25 -25
- package/dist/commonjs/components/ConsoleLogCard.js +3 -1
- package/dist/commonjs/components/HighlightText.js +1 -1
- package/dist/commonjs/components/JsonViewer.d.ts +3 -1
- package/dist/commonjs/components/JsonViewer.js +327 -9
- package/dist/commonjs/components/MetaAccordion.d.ts +10 -1
- package/dist/commonjs/components/MetaAccordion.js +121 -24
- package/dist/commonjs/components/NetworkIcons.d.ts +1 -0
- package/dist/commonjs/components/NetworkIcons.js +8 -1
- package/dist/commonjs/components/ReduxTreeView.d.ts +12 -17
- package/dist/commonjs/components/ReduxTreeView.js +1045 -632
- package/dist/commonjs/components/SectionHeader.d.ts +1 -1
- package/dist/commonjs/components/SectionHeader.js +7 -1
- package/dist/commonjs/components/TouchableScale.js +8 -0
- package/dist/commonjs/constants/version.d.ts +1 -1
- package/dist/commonjs/constants/version.js +1 -1
- package/dist/commonjs/customHooks/reduxLogger.d.ts +2 -0
- package/dist/commonjs/customHooks/reduxLogger.js +2 -0
- package/dist/commonjs/helpers/index.d.ts +3 -0
- package/dist/commonjs/helpers/index.js +103 -1
- package/dist/commonjs/helpers/settingsStore.d.ts +1 -0
- package/dist/commonjs/helpers/settingsStore.js +4 -0
- package/dist/commonjs/index.js +385 -339
- package/dist/commonjs/types/index.d.ts +3 -0
- package/dist/esm/components/AnalyticsDetail.js +94 -480
- package/dist/esm/components/AnalyticsEventCard.js +25 -25
- package/dist/esm/components/ConsoleLogCard.js +3 -1
- package/dist/esm/components/HighlightText.js +3 -3
- package/dist/esm/components/JsonViewer.d.ts +3 -1
- package/dist/esm/components/JsonViewer.js +295 -10
- package/dist/esm/components/MetaAccordion.d.ts +10 -1
- package/dist/esm/components/MetaAccordion.js +90 -26
- package/dist/esm/components/NetworkIcons.d.ts +1 -0
- package/dist/esm/components/NetworkIcons.js +6 -0
- package/dist/esm/components/ReduxTreeView.d.ts +12 -17
- package/dist/esm/components/ReduxTreeView.js +1047 -629
- package/dist/esm/components/SectionHeader.d.ts +1 -1
- package/dist/esm/components/SectionHeader.js +8 -2
- package/dist/esm/components/TouchableScale.js +9 -1
- package/dist/esm/constants/version.d.ts +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/customHooks/reduxLogger.d.ts +2 -0
- package/dist/esm/customHooks/reduxLogger.js +2 -0
- package/dist/esm/helpers/index.d.ts +3 -0
- package/dist/esm/helpers/index.js +100 -1
- package/dist/esm/helpers/settingsStore.d.ts +1 -0
- package/dist/esm/helpers/settingsStore.js +3 -0
- package/dist/esm/index.js +389 -343
- package/dist/esm/types/index.d.ts +3 -0
- package/example/package-lock.json +33 -74
- package/example/package.json +1 -1
- package/package.json +1 -1
|
@@ -1,759 +1,1177 @@
|
|
|
1
|
-
import React, { useEffect,
|
|
2
|
-
import {
|
|
1
|
+
import React, { useEffect, useState, useMemo } from 'react';
|
|
2
|
+
import { LayoutAnimation, Platform, Pressable, StyleSheet, Text, TextInput, UIManager, View, ScrollView, ToastAndroid, Alert, } from 'react-native';
|
|
3
3
|
import { AppColors } from '../styles/AppColors';
|
|
4
4
|
import { AppFonts } from '../styles/AppFonts';
|
|
5
|
-
import { ChevronIcon, CopyIcon,
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
width: 26,
|
|
18
|
-
height: 26,
|
|
19
|
-
borderRadius: 7,
|
|
20
|
-
alignItems: 'center',
|
|
21
|
-
justifyContent: 'center',
|
|
22
|
-
backgroundColor: copied
|
|
23
|
-
? `${AppColors.greenColor}1A`
|
|
24
|
-
: AppColors.grayBackground,
|
|
25
|
-
borderWidth: 1,
|
|
26
|
-
borderColor: AppColors.dividerColor,
|
|
27
|
-
}}>
|
|
28
|
-
{copied ? (<CheckIcon color={AppColors.greenColor} size={13}/>) : (<CopyIcon color={AppColors.grayTextWeak} size={13}/>)}
|
|
29
|
-
</Pressable>);
|
|
5
|
+
import { ChevronIcon, FolderIcon, TrashIcon, HeaderPauseIcon, CopyIcon, SearchIcon, ClearIcon, ClockIcon, ExpandCollapseIcon, SignalIcon, LayersIcon, TerminalIcon, RequestIcon, DiffIcon, SortIcon, CalendarIcon, } from './NetworkIcons';
|
|
6
|
+
import JsonViewer from './JsonViewer';
|
|
7
|
+
import DiffViewer from './DiffViewer';
|
|
8
|
+
import TouchableScale from './TouchableScale';
|
|
9
|
+
import { copyToClipboard, getSize } from '../helpers';
|
|
10
|
+
import { getCustomStorage } from '../helpers/settingsStore';
|
|
11
|
+
// Enable LayoutAnimation for Android
|
|
12
|
+
if (Platform.OS === 'android') {
|
|
13
|
+
UIManager.setLayoutAnimationEnabledExperimental?.(true);
|
|
14
|
+
}
|
|
15
|
+
const animateLayout = () => {
|
|
16
|
+
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
|
30
17
|
};
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
18
|
+
// Chevron pointing left (for back buttons)
|
|
19
|
+
const BackChevronIcon = ({ color = AppColors.purple, size = 12 }) => (<View style={{ transform: [{ rotate: '180deg' }], marginRight: 4 }}>
|
|
20
|
+
<ChevronIcon color={color} size={size}/>
|
|
21
|
+
</View>);
|
|
22
|
+
// Chevron pointing right (for list items)
|
|
23
|
+
const ForwardChevronIcon = ({ color = AppColors.grayTextWeak, size = 12 }) => (<View style={{ transform: [{ rotate: '0deg' }], marginLeft: 'auto' }}>
|
|
24
|
+
<ChevronIcon color={color} size={size}/>
|
|
25
|
+
</View>);
|
|
26
|
+
// Helper: check if a value recursively contains a string term
|
|
27
|
+
const valueContainsTerm = (val, term) => {
|
|
28
|
+
if (!term)
|
|
29
|
+
return true;
|
|
30
|
+
const t = term.toLowerCase();
|
|
31
|
+
if (val === null || val === undefined)
|
|
32
|
+
return false;
|
|
33
|
+
if (typeof val !== 'object') {
|
|
34
|
+
return String(val).toLowerCase().includes(t);
|
|
39
35
|
}
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
36
|
+
if (Array.isArray(val)) {
|
|
37
|
+
return val.some(item => valueContainsTerm(item, term));
|
|
43
38
|
}
|
|
44
|
-
return
|
|
45
|
-
};
|
|
46
|
-
// Custom icons
|
|
47
|
-
const DatabaseIcon = ({ color = AppColors.grayTextWeak, size = 12 }) => (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
48
|
-
<Path d="M12 2C6.5 2 2 4.2 2 7v10c0 2.8 4.5 5 10 5s10-2.2 10-5V7c0-2.8-4.5-5-10-5z" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
49
|
-
<Path d="M2 12c0 2.8 4.5 5 10 5s10-2.2 10-5" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
50
|
-
<Path d="M2 7c0 2.8 4.5 5 10 5s10-2.2 10-5" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
51
|
-
</Svg>);
|
|
52
|
-
const BoltIcon = ({ color = AppColors.grayTextWeak, size = 12 }) => (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
53
|
-
<Path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" fill={color} stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
|
54
|
-
</Svg>);
|
|
55
|
-
const FolderIcon = ({ color = AppColors.grayTextWeak, size = 12 }) => (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
56
|
-
<Path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
57
|
-
</Svg>);
|
|
58
|
-
// Exported icons so the Redux tab's section headers and sub-tab buttons can
|
|
59
|
-
// render real vector icons instead of emoji (which fail to render on some
|
|
60
|
-
// Android/font setups).
|
|
61
|
-
export const ReduxStoreIcon = ({ color = AppColors.grayTextWeak, size = 12, }) => (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
62
|
-
<Path d="M3 9l1.5-5h15L21 9M3 9v10a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V9M3 9h18M7 9v3a2 2 0 0 0 4 0V9m2 0v3a2 2 0 0 0 4 0V9" stroke={color} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
|
|
63
|
-
</Svg>);
|
|
64
|
-
export const ReduxBoltIcon = ({ color = AppColors.grayTextWeak, size = 12, }) => (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
65
|
-
<Path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" fill={color} stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
|
66
|
-
</Svg>);
|
|
67
|
-
// Action Timeline — a history/clock icon reads as "sequence over time".
|
|
68
|
-
export const ReduxTimelineIcon = ({ color = AppColors.grayTextWeak, size = 12, }) => (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
69
|
-
<Path d="M3 3v5h5" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
70
|
-
<Path d="M3.05 13A9 9 0 1 0 6 5.3L3 8" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
71
|
-
<Path d="M12 7v5l3 2" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
72
|
-
</Svg>);
|
|
73
|
-
// Store Tree — a node hierarchy icon reads as "nested tree structure".
|
|
74
|
-
export const ReduxTreeIcon = ({ color = AppColors.grayTextWeak, size = 12, }) => (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
75
|
-
<Path d="M9 4h6v4H9zM3 16h6v4H3zm12 0h6v4h-6z" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
76
|
-
<Path d="M12 8v4M6 16v-2h12v2" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
77
|
-
</Svg>);
|
|
78
|
-
const animateTreeLayout = () => {
|
|
79
|
-
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
|
39
|
+
return Object.keys(val).some(key => key.toLowerCase().includes(t) || valueContainsTerm(val[key], term));
|
|
80
40
|
};
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return (<Animated.View style={[style, { transform: [{ rotate }] }]}>
|
|
95
|
-
<ChevronIcon color={color} size={size}/>
|
|
96
|
-
</Animated.View>);
|
|
97
|
-
};
|
|
98
|
-
const ReduxValueNode = ({ name, value, level, search }) => {
|
|
99
|
-
const [expanded, setExpanded] = useState(level < 1);
|
|
100
|
-
const isObject = typeof value === 'object' && value !== null;
|
|
101
|
-
const isArray = Array.isArray(value);
|
|
102
|
-
const nameStr = String(name);
|
|
103
|
-
// Filter check if search query matches key or value
|
|
104
|
-
const matchesSearch = (k, val) => {
|
|
105
|
-
if (!search)
|
|
106
|
-
return true;
|
|
107
|
-
const s = search.toLowerCase();
|
|
108
|
-
if (k.toLowerCase().includes(s))
|
|
109
|
-
return true;
|
|
110
|
-
if (typeof val !== 'object' && String(val).toLowerCase().includes(s))
|
|
111
|
-
return true;
|
|
112
|
-
if (typeof val === 'object' && val !== null) {
|
|
113
|
-
return Object.keys(val).some(key => matchesSearch(key, val[key]));
|
|
114
|
-
}
|
|
115
|
-
return false;
|
|
41
|
+
export const ReduxTreeView = ({ state, actionHistory = [], lastActionMap = {}, search = '', onSearchChange, autoRefresh = true, onToggleAutoRefresh, onClearHistory, onDetailOpenChange, selectedSliceKey = null, onSelectSliceKey, selectedActionId = null, onSelectActionId, }) => {
|
|
42
|
+
const [activeTab, setActiveTab] = useState('state');
|
|
43
|
+
const [actionSubTab, setActionSubTab] = useState('payload');
|
|
44
|
+
// local fallbacks if props are not provided
|
|
45
|
+
const [localSliceKey, setLocalSliceKey] = useState(null);
|
|
46
|
+
const [localActionId, setLocalActionId] = useState(null);
|
|
47
|
+
const activeSliceKey = onSelectSliceKey ? selectedSliceKey : localSliceKey;
|
|
48
|
+
const activeActionId = onSelectActionId ? selectedActionId : localActionId;
|
|
49
|
+
const setActiveSliceKey = (key) => {
|
|
50
|
+
if (onSelectSliceKey)
|
|
51
|
+
onSelectSliceKey(key);
|
|
52
|
+
else
|
|
53
|
+
setLocalSliceKey(key);
|
|
116
54
|
};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
55
|
+
const setActiveActionId = (id) => {
|
|
56
|
+
if (onSelectActionId)
|
|
57
|
+
onSelectActionId(id);
|
|
58
|
+
else
|
|
59
|
+
setLocalActionId(id);
|
|
60
|
+
};
|
|
61
|
+
// Expand / collapse all states for detail views
|
|
62
|
+
const [sliceForceOpen, setSliceForceOpen] = useState(undefined);
|
|
63
|
+
const [actionForceOpen, setActionForceOpen] = useState(undefined);
|
|
64
|
+
const [searchModeType, setSearchModeType] = useState(true);
|
|
65
|
+
const [searchModeData, setSearchModeData] = useState(true);
|
|
66
|
+
// Sorting directions
|
|
67
|
+
const [stateSortAsc, setStateSortAsc] = useState(true);
|
|
68
|
+
const [actionSortAsc, setActionSortAsc] = useState(false);
|
|
69
|
+
// Persistence-related states for State Slice details
|
|
70
|
+
const [sliceDetailTab, setSliceDetailTab] = useState('live');
|
|
71
|
+
const [isPersisted, setIsPersisted] = useState(false);
|
|
72
|
+
const [persistedData, setPersistedData] = useState(null);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (activeSliceKey === null) {
|
|
75
|
+
setIsPersisted(false);
|
|
76
|
+
setPersistedData(null);
|
|
77
|
+
setSliceDetailTab('live');
|
|
78
|
+
return;
|
|
133
79
|
}
|
|
134
|
-
|
|
135
|
-
|
|
80
|
+
async function checkPersistence() {
|
|
81
|
+
try {
|
|
82
|
+
const storage = getCustomStorage();
|
|
83
|
+
if (!storage) {
|
|
84
|
+
setIsPersisted(false);
|
|
85
|
+
setPersistedData(null);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Check direct key: persist:sliceKey
|
|
89
|
+
let raw = await storage.getItem(`persist:${activeSliceKey}`);
|
|
90
|
+
if (raw) {
|
|
91
|
+
try {
|
|
92
|
+
setPersistedData(JSON.parse(raw));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
setPersistedData(raw);
|
|
96
|
+
}
|
|
97
|
+
setIsPersisted(true);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Check root key: persist:root
|
|
101
|
+
let rootRaw = await storage.getItem('persist:root');
|
|
102
|
+
if (rootRaw) {
|
|
103
|
+
const parsedRoot = JSON.parse(rootRaw);
|
|
104
|
+
if (parsedRoot && parsedRoot[activeSliceKey] !== undefined) {
|
|
105
|
+
const nested = parsedRoot[activeSliceKey];
|
|
106
|
+
try {
|
|
107
|
+
setPersistedData(typeof nested === 'string' ? JSON.parse(nested) : nested);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
setPersistedData(nested);
|
|
111
|
+
}
|
|
112
|
+
setIsPersisted(true);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
console.log('Error checking Redux persistence:', e);
|
|
119
|
+
}
|
|
120
|
+
setIsPersisted(false);
|
|
121
|
+
setPersistedData(null);
|
|
136
122
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
123
|
+
checkPersistence();
|
|
124
|
+
}, [activeSliceKey]);
|
|
125
|
+
const handleClearPersistence = () => {
|
|
126
|
+
Alert.alert('Clear Persisted State', `Are you sure you want to delete the persisted storage item for the slice "${activeSliceKey}"? This will clear the value from device storage, but the current active in-memory Redux state will remain unchanged until the next app reload.`, [
|
|
127
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
128
|
+
{
|
|
129
|
+
text: 'Delete',
|
|
130
|
+
style: 'destructive',
|
|
131
|
+
onPress: async () => {
|
|
132
|
+
try {
|
|
133
|
+
const storage = getCustomStorage();
|
|
134
|
+
if (!storage)
|
|
135
|
+
return;
|
|
136
|
+
// Delete direct key
|
|
137
|
+
if (typeof storage.removeItem === 'function') {
|
|
138
|
+
await storage.removeItem(`persist:${activeSliceKey}`);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
await storage.setItem(`persist:${activeSliceKey}`, '');
|
|
142
|
+
}
|
|
143
|
+
// Delete from root key
|
|
144
|
+
let rootRaw = await storage.getItem('persist:root');
|
|
145
|
+
if (rootRaw) {
|
|
146
|
+
const parsed = JSON.parse(rootRaw);
|
|
147
|
+
if (parsed && parsed[activeSliceKey] !== undefined) {
|
|
148
|
+
delete parsed[activeSliceKey];
|
|
149
|
+
await storage.setItem('persist:root', JSON.stringify(parsed));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
setIsPersisted(false);
|
|
153
|
+
setPersistedData(null);
|
|
154
|
+
setSliceDetailTab('live');
|
|
155
|
+
if (Platform.OS === 'android') {
|
|
156
|
+
ToastAndroid.show('Persisted state cleared', ToastAndroid.SHORT);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
Alert.alert('Cleared', 'Persisted state has been cleared from device storage.');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
Alert.alert('Error', 'Failed to clear persisted state.');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
]);
|
|
168
|
+
};
|
|
169
|
+
const isSearching = search.trim().length > 0;
|
|
173
170
|
useEffect(() => {
|
|
174
|
-
|
|
175
|
-
|
|
171
|
+
onDetailOpenChange?.(activeSliceKey !== null || activeActionId !== null);
|
|
172
|
+
return () => {
|
|
173
|
+
onDetailOpenChange?.(false);
|
|
174
|
+
};
|
|
175
|
+
}, [activeSliceKey, activeActionId, onDetailOpenChange]);
|
|
176
|
+
// Reset details view when tab changes
|
|
177
|
+
const switchTab = (tab) => {
|
|
178
|
+
animateLayout();
|
|
179
|
+
setActiveTab(tab);
|
|
180
|
+
setActiveSliceKey(null);
|
|
181
|
+
setActiveActionId(null);
|
|
182
|
+
setSliceForceOpen(undefined);
|
|
183
|
+
setActionForceOpen(undefined);
|
|
184
|
+
};
|
|
185
|
+
const handleGoBackSlice = () => {
|
|
186
|
+
animateLayout();
|
|
187
|
+
setActiveSliceKey(null);
|
|
188
|
+
setSliceForceOpen(undefined);
|
|
189
|
+
};
|
|
190
|
+
const handleGoBackAction = () => {
|
|
191
|
+
animateLayout();
|
|
192
|
+
setActiveActionId(null);
|
|
193
|
+
setActionForceOpen(undefined);
|
|
194
|
+
};
|
|
195
|
+
// Filtered state keys based on search query
|
|
196
|
+
const filteredStateKeys = useMemo(() => {
|
|
197
|
+
if (!state || typeof state !== 'object')
|
|
198
|
+
return [];
|
|
199
|
+
const keys = Object.keys(state);
|
|
200
|
+
let result = keys;
|
|
201
|
+
if (isSearching) {
|
|
202
|
+
result = keys.filter(key => {
|
|
203
|
+
const typeMatch = searchModeType && key.toLowerCase().includes(search.toLowerCase());
|
|
204
|
+
const dataMatch = searchModeData && valueContainsTerm(state[key], search);
|
|
205
|
+
return typeMatch || dataMatch;
|
|
206
|
+
});
|
|
176
207
|
}
|
|
177
|
-
|
|
208
|
+
return [...result].sort((a, b) => {
|
|
209
|
+
const comparison = a.localeCompare(b);
|
|
210
|
+
return stateSortAsc ? comparison : -comparison;
|
|
211
|
+
});
|
|
212
|
+
}, [state, search, isSearching, searchModeType, searchModeData, stateSortAsc]);
|
|
213
|
+
// Filtered action history based on search query
|
|
214
|
+
const filteredActionHistory = useMemo(() => {
|
|
215
|
+
if (!actionHistory)
|
|
216
|
+
return [];
|
|
217
|
+
let result = actionHistory;
|
|
218
|
+
if (isSearching) {
|
|
219
|
+
result = actionHistory.filter(action => {
|
|
220
|
+
const typeMatch = searchModeType && action.type.toLowerCase().includes(search.toLowerCase());
|
|
221
|
+
const payloadMatch = searchModeData && action.payload && valueContainsTerm(action.payload, search);
|
|
222
|
+
return typeMatch || payloadMatch;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return [...result].sort((a, b) => {
|
|
226
|
+
const comparison = a.id - b.id;
|
|
227
|
+
return actionSortAsc ? comparison : -comparison;
|
|
228
|
+
});
|
|
229
|
+
}, [actionHistory, search, isSearching, searchModeType, searchModeData, actionSortAsc]);
|
|
230
|
+
// Find currently selected action object
|
|
231
|
+
const selectedAction = useMemo(() => {
|
|
232
|
+
if (activeActionId === null)
|
|
233
|
+
return null;
|
|
234
|
+
return actionHistory.find(a => a.id === activeActionId) || null;
|
|
235
|
+
}, [activeActionId, actionHistory]);
|
|
178
236
|
if (!state || typeof state !== 'object') {
|
|
179
|
-
return (<
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
color: AppColors.grayTextWeak,
|
|
183
|
-
padding: 12,
|
|
184
|
-
}}>
|
|
185
|
-
No state object to display.
|
|
186
|
-
</Text>);
|
|
237
|
+
return (<View style={styles.errorContainer}>
|
|
238
|
+
<Text style={styles.errorText}>No connected Redux store state found.</Text>
|
|
239
|
+
</View>);
|
|
187
240
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
241
|
+
// ── Render Detail View: State Slice ──
|
|
242
|
+
if (activeTab === 'state' && activeSliceKey !== null) {
|
|
243
|
+
const sliceData = state[activeSliceKey];
|
|
244
|
+
const fieldsCount = typeof sliceData === 'object' && sliceData !== null
|
|
245
|
+
? Object.keys(sliceData).length
|
|
246
|
+
: 1;
|
|
247
|
+
return (<View style={{ flex: 1 }}>
|
|
248
|
+
{/* Non-scrollable header */}
|
|
249
|
+
<View style={{ paddingHorizontal: 6, paddingTop: 4 }}>
|
|
250
|
+
<View style={styles.apiLikeInfoBar}>
|
|
251
|
+
<View style={styles.apiBadgeRow}>
|
|
252
|
+
<View style={[styles.apiMethodBadge, { backgroundColor: AppColors.purple }]}>
|
|
253
|
+
<Text style={styles.apiMethodBadgeText}>REDUX</Text>
|
|
254
|
+
</View>
|
|
255
|
+
<View style={[styles.apiMethodBadge, { backgroundColor: AppColors.purpleShade50, borderWidth: 1, borderColor: AppColors.purple + '30' }]}>
|
|
256
|
+
<Text style={[styles.apiMethodBadgeText, { color: AppColors.purple }]}>SLICE</Text>
|
|
257
|
+
</View>
|
|
258
|
+
{isPersisted && (<View style={[styles.apiMethodBadge, { backgroundColor: '#10B981' }]}>
|
|
259
|
+
<Text style={styles.apiMethodBadgeText}>PERSISTED</Text>
|
|
260
|
+
</View>)}
|
|
261
|
+
<View style={styles.apiChip}>
|
|
262
|
+
<Text style={styles.apiChipText}>{getSize(sliceData)}</Text>
|
|
263
|
+
</View>
|
|
264
|
+
<View style={styles.apiChip}>
|
|
265
|
+
<Text style={styles.apiChipText}>{fieldsCount} {fieldsCount === 1 ? 'field' : 'fields'}</Text>
|
|
266
|
+
</View>
|
|
267
|
+
</View>
|
|
268
|
+
|
|
269
|
+
<Text style={styles.apiTitleText} selectable={true}>{activeSliceKey}</Text>
|
|
270
|
+
|
|
271
|
+
<View style={styles.apiMetaRow}>
|
|
272
|
+
<ClockIcon color={AppColors.grayTextWeak} size={10}/>
|
|
273
|
+
<Text style={styles.apiMetaText}>
|
|
274
|
+
Last updated: {lastActionMap[activeSliceKey] ? lastActionMap[activeSliceKey].timestamp : 'Initial'}
|
|
275
|
+
</Text>
|
|
276
|
+
</View>
|
|
277
|
+
</View>
|
|
206
278
|
</View>
|
|
207
|
-
|
|
208
|
-
|
|
279
|
+
|
|
280
|
+
{/* Segment Control */}
|
|
281
|
+
<View style={{
|
|
282
|
+
flexDirection: 'row',
|
|
283
|
+
backgroundColor: AppColors.grayBackground,
|
|
284
|
+
borderRadius: 10,
|
|
285
|
+
padding: 3,
|
|
286
|
+
marginHorizontal: 6,
|
|
287
|
+
marginBottom: 10,
|
|
288
|
+
marginTop: 6,
|
|
289
|
+
borderWidth: 1,
|
|
290
|
+
borderColor: AppColors.grayBorderSecondary,
|
|
291
|
+
}}>
|
|
292
|
+
{(isPersisted ? ['live', 'persisted'] : ['live']).map(tab => {
|
|
293
|
+
const isActive = sliceDetailTab === tab;
|
|
294
|
+
const getLabel = () => {
|
|
295
|
+
if (tab === 'live')
|
|
296
|
+
return 'Live State';
|
|
297
|
+
return `Persisted (${getSize(persistedData)})`;
|
|
298
|
+
};
|
|
299
|
+
const getIcon = () => {
|
|
300
|
+
const iconColor = isActive ? '#FFFFFF' : AppColors.grayText;
|
|
301
|
+
if (tab === 'live')
|
|
302
|
+
return <LayersIcon color={iconColor} size={11}/>;
|
|
303
|
+
return <CalendarIcon color={iconColor} size={11}/>;
|
|
304
|
+
};
|
|
305
|
+
return (<Pressable key={tab} onPress={() => {
|
|
306
|
+
animateLayout();
|
|
307
|
+
setSliceDetailTab(tab);
|
|
308
|
+
}} style={{
|
|
309
|
+
flex: 1,
|
|
310
|
+
paddingVertical: 6,
|
|
311
|
+
flexDirection: 'row',
|
|
312
|
+
alignItems: 'center',
|
|
313
|
+
justifyContent: 'center',
|
|
314
|
+
borderRadius: 8,
|
|
315
|
+
backgroundColor: isActive ? AppColors.purple : 'transparent',
|
|
316
|
+
gap: 4,
|
|
317
|
+
}}>
|
|
318
|
+
{getIcon()}
|
|
319
|
+
<Text style={{
|
|
320
|
+
fontFamily: AppFonts.interBold,
|
|
321
|
+
fontSize: 10,
|
|
322
|
+
color: isActive ? '#FFFFFF' : AppColors.grayText,
|
|
323
|
+
}}>
|
|
324
|
+
{getLabel()}
|
|
325
|
+
</Text>
|
|
326
|
+
</Pressable>);
|
|
327
|
+
})}
|
|
209
328
|
</View>
|
|
210
|
-
</Pressable>
|
|
211
329
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
<View style={[
|
|
221
|
-
styles.reducerVerticalLine,
|
|
222
|
-
isLastReducer && { bottom: '50%' },
|
|
223
|
-
]}/>
|
|
330
|
+
{/* Scrollable Tab Content */}
|
|
331
|
+
<View style={{ flex: 1, paddingHorizontal: 6 }}>
|
|
332
|
+
{/* Back button + controls */}
|
|
333
|
+
<View style={styles.contentDetailSubHeader}>
|
|
334
|
+
<TouchableScale onPress={handleGoBackSlice} style={styles.backBtn} hitSlop={12}>
|
|
335
|
+
<BackChevronIcon color={AppColors.purple} size={11}/>
|
|
336
|
+
<Text style={styles.backBtnText}>State Slices</Text>
|
|
337
|
+
</TouchableScale>
|
|
224
338
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
<
|
|
228
|
-
|
|
229
|
-
<View style={styles.iconWrap}>
|
|
230
|
-
<FolderIcon color={AppColors.purple} size={11}/>
|
|
231
|
-
</View>
|
|
232
|
-
<Text style={styles.reducerText}>{reducerKey}</Text>
|
|
233
|
-
</Pressable>
|
|
339
|
+
<View style={styles.detailHeaderActions}>
|
|
340
|
+
{isPersisted && sliceDetailTab === 'persisted' && (<TouchableScale onPress={handleClearPersistence} style={[styles.detailHeaderBtn, { marginRight: 8 }]} hitSlop={8}>
|
|
341
|
+
<TrashIcon color={AppColors.errorColor} size={14}/>
|
|
342
|
+
</TouchableScale>)}
|
|
234
343
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
344
|
+
<TouchableScale onPress={() => {
|
|
345
|
+
animateLayout();
|
|
346
|
+
setSliceForceOpen(prev => (prev === true ? false : prev === false ? undefined : true));
|
|
347
|
+
}} style={styles.detailHeaderBtn} hitSlop={8}>
|
|
348
|
+
<ExpandCollapseIcon expanded={sliceForceOpen !== false} color={AppColors.purple} size={14}/>
|
|
349
|
+
</TouchableScale>
|
|
238
350
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
flexDirection: 'row',
|
|
248
|
-
alignItems: 'center',
|
|
249
|
-
flexWrap: 'wrap',
|
|
250
|
-
gap: 6,
|
|
251
|
-
}}>
|
|
252
|
-
<Text style={styles.childLabel}>Last Action:</Text>
|
|
253
|
-
{lastAction ? (<View style={styles.actionTypeBadge}>
|
|
254
|
-
<Text style={styles.actionTypeText}>
|
|
255
|
-
{lastAction.type}
|
|
256
|
-
</Text>
|
|
257
|
-
</View>) : (<Text style={styles.noActionText}>
|
|
258
|
-
None dispatched
|
|
259
|
-
</Text>)}
|
|
260
|
-
</View>
|
|
261
|
-
{lastAction && (<Text style={styles.timestampText}>
|
|
262
|
-
Dispatched: {lastAction.timestamp}
|
|
263
|
-
</Text>)}
|
|
264
|
-
{lastAction && lastAction.payload !== null && (<View style={{ marginTop: 6 }}>
|
|
265
|
-
<ReduxValueNode name="payload" value={lastAction.payload} level={0} search={search}/>
|
|
266
|
-
</View>)}
|
|
267
|
-
</View>
|
|
268
|
-
</View>
|
|
351
|
+
<TouchableScale onPress={() => {
|
|
352
|
+
const dataToCopy = sliceDetailTab === 'live' ? sliceData : persistedData;
|
|
353
|
+
copyToClipboard(dataToCopy, `Redux Slice '${activeSliceKey}' (${sliceDetailTab})`);
|
|
354
|
+
}} style={styles.detailHeaderBtn} hitSlop={8}>
|
|
355
|
+
<CopyIcon color={AppColors.purple} size={14}/>
|
|
356
|
+
</TouchableScale>
|
|
357
|
+
</View>
|
|
358
|
+
</View>
|
|
269
359
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
</View>
|
|
276
|
-
<View style={{ flex: 1 }}>
|
|
277
|
-
<Text style={styles.childLabel}>State Slice Data</Text>
|
|
278
|
-
<View style={{ marginTop: 6 }}>
|
|
279
|
-
<ReduxValueNode name="state" value={sliceData} level={0} search={search}/>
|
|
280
|
-
</View>
|
|
281
|
-
</View>
|
|
282
|
-
</View>
|
|
283
|
-
</View>)}
|
|
284
|
-
</View>);
|
|
285
|
-
})}
|
|
286
|
-
</View>)}
|
|
287
|
-
</View>);
|
|
288
|
-
};
|
|
289
|
-
export const ReduxActionTimeline = ({ history, onClear, search, }) => {
|
|
290
|
-
const [expandedActionId, setExpandedActionId] = useState(null);
|
|
291
|
-
const toggleExpand = (id) => {
|
|
292
|
-
animateTreeLayout();
|
|
293
|
-
setExpandedActionId(prev => (prev === id ? null : id));
|
|
294
|
-
};
|
|
295
|
-
const filteredHistory = history.filter(action => {
|
|
296
|
-
if (!search)
|
|
297
|
-
return true;
|
|
298
|
-
const s = search.toLowerCase();
|
|
299
|
-
if (action.type.toLowerCase().includes(s))
|
|
300
|
-
return true;
|
|
301
|
-
if (action.affectedSlices.some(slice => slice.toLowerCase().includes(s)))
|
|
302
|
-
return true;
|
|
303
|
-
if (action.payload && typeof action.payload === 'object') {
|
|
304
|
-
return JSON.stringify(action.payload).toLowerCase().includes(s);
|
|
305
|
-
}
|
|
306
|
-
return false;
|
|
307
|
-
});
|
|
308
|
-
return (<View style={timelineStyles.container}>
|
|
309
|
-
<View style={timelineStyles.headerRow}>
|
|
310
|
-
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
311
|
-
<ReduxBoltIcon color={AppColors.purple} size={15}/>
|
|
312
|
-
<Text style={timelineStyles.headerTitle}>
|
|
313
|
-
Dispatched Actions ({filteredHistory.length})
|
|
314
|
-
</Text>
|
|
360
|
+
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ paddingBottom: 24 }} showsVerticalScrollIndicator={true}>
|
|
361
|
+
<View style={{ padding: 12 }}>
|
|
362
|
+
<JsonViewer data={sliceDetailTab === 'live' ? sliceData : (persistedData ?? 'No persisted data')} search={search} forceOpen={sliceForceOpen !== undefined ? sliceForceOpen : (isSearching ? true : undefined)} fullHeight={true}/>
|
|
363
|
+
</View>
|
|
364
|
+
</ScrollView>
|
|
315
365
|
</View>
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
366
|
+
</View>);
|
|
367
|
+
}
|
|
368
|
+
// ── Render Detail View: Timeline Action ──
|
|
369
|
+
if (activeTab === 'timeline' && selectedAction !== null) {
|
|
370
|
+
const affectedSlices = selectedAction.affectedSlices || [];
|
|
371
|
+
return (<View style={{ flex: 1 }}>
|
|
372
|
+
{/* Non-scrollable header */}
|
|
373
|
+
<View style={{ paddingHorizontal: 6, paddingTop: 4 }}>
|
|
374
|
+
<View style={styles.apiLikeInfoBar}>
|
|
375
|
+
<View style={styles.apiBadgeRow}>
|
|
376
|
+
<View style={[styles.apiMethodBadge, { backgroundColor: AppColors.purple }]}>
|
|
377
|
+
<Text style={styles.apiMethodBadgeText}>REDUX</Text>
|
|
378
|
+
</View>
|
|
379
|
+
<View style={[styles.apiMethodBadge, { backgroundColor: '#F59E0B' }]}>
|
|
380
|
+
<Text style={styles.apiMethodBadgeText}>ACTION</Text>
|
|
381
|
+
</View>
|
|
382
|
+
<View style={styles.apiChip}>
|
|
383
|
+
<Text style={styles.apiChipText}>#{selectedAction.id}</Text>
|
|
384
|
+
</View>
|
|
385
|
+
<View style={styles.apiChip}>
|
|
386
|
+
<Text style={styles.apiChipText}>{getSize(selectedAction.payload)}</Text>
|
|
387
|
+
</View>
|
|
388
|
+
</View>
|
|
320
389
|
|
|
321
|
-
|
|
322
|
-
<Text style={timelineStyles.emptyText}>
|
|
323
|
-
{history.length === 0
|
|
324
|
-
? 'No actions dispatched yet.\nDispatch actions in your application to see the timeline.'
|
|
325
|
-
: 'No matching actions found.'}
|
|
326
|
-
</Text>
|
|
327
|
-
</View>) : (<View style={timelineStyles.listContainer}>
|
|
328
|
-
{filteredHistory.map((item, index) => {
|
|
329
|
-
const isLast = index === filteredHistory.length - 1;
|
|
330
|
-
const isExpanded = expandedActionId === item.id;
|
|
331
|
-
return (<AnimatedEntrance key={item.id} index={index} distance={8} style={timelineStyles.timelineItem}>
|
|
332
|
-
{/* Visual Line */}
|
|
333
|
-
<View style={[
|
|
334
|
-
timelineStyles.verticalLine,
|
|
335
|
-
isLast && { bottom: '50%' },
|
|
336
|
-
]}/>
|
|
337
|
-
<View style={timelineStyles.circleIndicator}>
|
|
338
|
-
<View style={timelineStyles.circleInner}/>
|
|
339
|
-
</View>
|
|
390
|
+
<Text style={styles.apiTitleText} selectable={true}>{selectedAction.type}</Text>
|
|
340
391
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
392
|
+
<View style={styles.apiMetaRow}>
|
|
393
|
+
<ClockIcon color={AppColors.grayTextWeak} size={10}/>
|
|
394
|
+
<Text style={styles.apiMetaText}>Dispatched: {selectedAction.timestamp}</Text>
|
|
395
|
+
</View>
|
|
396
|
+
</View>
|
|
397
|
+
</View>
|
|
398
|
+
|
|
399
|
+
{/* Affected Slices Tags list */}
|
|
400
|
+
{affectedSlices.length > 0 && (<View style={styles.detailAffectedTags}>
|
|
401
|
+
<Text style={styles.affectedTagsLabel}>AFFECTED SLICES:</Text>
|
|
402
|
+
<View style={styles.detailTagsList}>
|
|
403
|
+
{affectedSlices.map(slice => (<View key={slice} style={styles.sliceTag}>
|
|
404
|
+
<Text style={styles.sliceTagText}>{slice}</Text>
|
|
405
|
+
</View>))}
|
|
406
|
+
</View>
|
|
407
|
+
</View>)}
|
|
408
|
+
|
|
409
|
+
{/* Segment Control */}
|
|
410
|
+
<View style={{
|
|
411
|
+
flexDirection: 'row',
|
|
412
|
+
backgroundColor: AppColors.grayBackground,
|
|
413
|
+
borderRadius: 10,
|
|
414
|
+
padding: 3,
|
|
415
|
+
marginHorizontal: 6,
|
|
416
|
+
marginBottom: 10,
|
|
417
|
+
marginTop: 6,
|
|
418
|
+
borderWidth: 1,
|
|
419
|
+
borderColor: AppColors.grayBorderSecondary,
|
|
420
|
+
}}>
|
|
421
|
+
{['payload', 'diff', 'state'].map(tab => {
|
|
422
|
+
const isActive = actionSubTab === tab;
|
|
423
|
+
const getLabel = () => {
|
|
424
|
+
if (tab === 'payload')
|
|
425
|
+
return 'Payload';
|
|
426
|
+
if (tab === 'diff')
|
|
427
|
+
return 'Diff Changes';
|
|
428
|
+
return 'Snapshot';
|
|
429
|
+
};
|
|
430
|
+
const getIcon = () => {
|
|
431
|
+
const iconColor = isActive ? '#FFFFFF' : AppColors.grayText;
|
|
432
|
+
if (tab === 'payload')
|
|
433
|
+
return <RequestIcon color={iconColor} size={11}/>;
|
|
434
|
+
if (tab === 'diff')
|
|
435
|
+
return <DiffIcon color={iconColor} size={11}/>;
|
|
436
|
+
return <LayersIcon color={iconColor} size={11}/>;
|
|
437
|
+
};
|
|
438
|
+
return (<Pressable key={tab} onPress={() => {
|
|
439
|
+
animateLayout();
|
|
440
|
+
setActionSubTab(tab);
|
|
441
|
+
}} style={{
|
|
354
442
|
flex: 1,
|
|
355
|
-
|
|
356
|
-
{(() => {
|
|
357
|
-
const moduleName = getActionModule(item.type, item.affectedSlices);
|
|
358
|
-
return moduleName ? (<View style={{
|
|
359
|
-
paddingHorizontal: 7,
|
|
360
|
-
paddingVertical: 2,
|
|
361
|
-
borderRadius: 6,
|
|
362
|
-
backgroundColor: `${AppColors.purple}14`,
|
|
363
|
-
borderWidth: 1,
|
|
364
|
-
borderColor: `${AppColors.purple}33`,
|
|
365
|
-
}}>
|
|
366
|
-
<Text style={{
|
|
367
|
-
fontFamily: AppFonts.interBold,
|
|
368
|
-
fontSize: 9.5,
|
|
369
|
-
color: AppColors.purple,
|
|
370
|
-
letterSpacing: 0.2,
|
|
371
|
-
}}>
|
|
372
|
-
{moduleName}
|
|
373
|
-
</Text>
|
|
374
|
-
</View>) : null;
|
|
375
|
-
})()}
|
|
376
|
-
<View style={timelineStyles.typeBadge}>
|
|
377
|
-
<Text style={timelineStyles.typeText}>{item.type}</Text>
|
|
378
|
-
</View>
|
|
379
|
-
</View>
|
|
380
|
-
<View style={{
|
|
443
|
+
paddingVertical: 6,
|
|
381
444
|
flexDirection: 'row',
|
|
382
445
|
alignItems: 'center',
|
|
383
|
-
|
|
446
|
+
justifyContent: 'center',
|
|
447
|
+
borderRadius: 8,
|
|
448
|
+
backgroundColor: isActive ? AppColors.purple : 'transparent',
|
|
449
|
+
gap: 4,
|
|
384
450
|
}}>
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
451
|
+
{getIcon()}
|
|
452
|
+
<Text style={{
|
|
453
|
+
fontFamily: AppFonts.interBold,
|
|
454
|
+
fontSize: 10,
|
|
455
|
+
color: isActive ? '#FFFFFF' : AppColors.grayText,
|
|
456
|
+
}}>
|
|
457
|
+
{getLabel()}
|
|
458
|
+
</Text>
|
|
459
|
+
</Pressable>);
|
|
460
|
+
})}
|
|
461
|
+
</View>
|
|
394
462
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
463
|
+
{/* Scrollable Tab Content */}
|
|
464
|
+
<View style={{ flex: 1, paddingHorizontal: 6 }}>
|
|
465
|
+
{/* Back button + controls */}
|
|
466
|
+
<View style={styles.contentDetailSubHeader}>
|
|
467
|
+
<TouchableScale onPress={handleGoBackAction} style={styles.backBtn} hitSlop={12}>
|
|
468
|
+
<BackChevronIcon color={AppColors.purple} size={11}/>
|
|
469
|
+
<Text style={styles.backBtnText}>Actions</Text>
|
|
470
|
+
</TouchableScale>
|
|
401
471
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
472
|
+
{(actionSubTab === 'payload' || actionSubTab === 'state') && (<View style={styles.detailHeaderActions}>
|
|
473
|
+
<TouchableScale onPress={() => {
|
|
474
|
+
animateLayout();
|
|
475
|
+
setActionForceOpen(prev => (prev === true ? false : prev === false ? undefined : true));
|
|
476
|
+
}} style={styles.detailHeaderBtn} hitSlop={8}>
|
|
477
|
+
<ExpandCollapseIcon expanded={actionForceOpen !== false} color={AppColors.purple} size={14}/>
|
|
478
|
+
</TouchableScale>
|
|
479
|
+
|
|
480
|
+
<TouchableScale onPress={() => {
|
|
481
|
+
const dataToCopy = actionSubTab === 'payload' ? selectedAction.payload : selectedAction.nextState;
|
|
482
|
+
copyToClipboard(dataToCopy, actionSubTab === 'payload' ? 'Action payload' : 'State snapshot');
|
|
483
|
+
}} style={styles.detailHeaderBtn} hitSlop={8}>
|
|
484
|
+
<CopyIcon color={AppColors.purple} size={14}/>
|
|
485
|
+
</TouchableScale>
|
|
486
|
+
</View>)}
|
|
487
|
+
</View>
|
|
488
|
+
|
|
489
|
+
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ paddingBottom: 24 }} showsVerticalScrollIndicator={true}>
|
|
490
|
+
{actionSubTab === 'payload' && (<View style={{ padding: 12 }}>
|
|
491
|
+
<JsonViewer data={selectedAction.payload ?? 'No payload data'} wrap forceOpen={actionForceOpen !== undefined ? actionForceOpen : undefined} fullHeight={true}/>
|
|
492
|
+
</View>)}
|
|
493
|
+
|
|
494
|
+
{actionSubTab === 'diff' && (<View style={{ padding: 12 }}>
|
|
495
|
+
<DiffViewer oldData={selectedAction.prevState} newData={selectedAction.nextState}/>
|
|
496
|
+
</View>)}
|
|
497
|
+
|
|
498
|
+
{actionSubTab === 'state' && (<View style={{ padding: 12 }}>
|
|
499
|
+
<JsonViewer data={selectedAction.nextState} wrap forceOpen={actionForceOpen !== undefined ? actionForceOpen : undefined} fullHeight={true}/>
|
|
500
|
+
</View>)}
|
|
501
|
+
</ScrollView>
|
|
502
|
+
</View>
|
|
503
|
+
</View>);
|
|
504
|
+
}
|
|
505
|
+
// ── Render List Views (Default) ──
|
|
506
|
+
return (<View style={styles.container}>
|
|
507
|
+
{/* ── Control Bar ── */}
|
|
508
|
+
<View style={styles.toolbar}>
|
|
509
|
+
<View style={styles.statusSection}>
|
|
510
|
+
<View style={[styles.statusBadge, autoRefresh ? styles.statusBadgeActive : styles.statusBadgePaused]}>
|
|
511
|
+
<View style={[styles.statusDot, autoRefresh ? styles.statusDotActive : styles.statusDotPaused]}/>
|
|
512
|
+
<Text style={[styles.statusBadgeText, autoRefresh ? styles.statusTextActive : styles.statusTextPaused]}>
|
|
513
|
+
{autoRefresh ? 'LIVE' : 'PAUSED'}
|
|
514
|
+
</Text>
|
|
515
|
+
</View>
|
|
516
|
+
</View>
|
|
517
|
+
|
|
518
|
+
<View style={styles.toolbarActions}>
|
|
519
|
+
{/* Sorting Toggle Button */}
|
|
520
|
+
<TouchableScale onPress={() => {
|
|
521
|
+
animateLayout();
|
|
522
|
+
if (activeTab === 'state') {
|
|
523
|
+
setStateSortAsc(prev => !prev);
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
setActionSortAsc(prev => !prev);
|
|
527
|
+
}
|
|
528
|
+
}} style={styles.iconBtn} hitSlop={8}>
|
|
529
|
+
<SortIcon ascending={activeTab === 'state' ? stateSortAsc : actionSortAsc} color={AppColors.purple} size={15}/>
|
|
530
|
+
</TouchableScale>
|
|
531
|
+
|
|
532
|
+
{onToggleAutoRefresh && (<TouchableScale onPress={onToggleAutoRefresh} style={styles.iconBtn} hitSlop={8}>
|
|
533
|
+
<HeaderPauseIcon isPaused={!autoRefresh} color={AppColors.purple} size={15}/>
|
|
534
|
+
</TouchableScale>)}
|
|
535
|
+
{onClearHistory && (<TouchableScale onPress={onClearHistory} style={[styles.iconBtn, { borderColor: AppColors.errorColor + '40', backgroundColor: AppColors.errorColor + '08' }]} hitSlop={8}>
|
|
536
|
+
<TrashIcon color={AppColors.errorColor} size={15}/>
|
|
537
|
+
</TouchableScale>)}
|
|
538
|
+
</View>
|
|
539
|
+
</View>
|
|
540
|
+
|
|
541
|
+
{/* ── Search Bar ── */}
|
|
542
|
+
{onSearchChange && (<View style={styles.searchContainer}>
|
|
543
|
+
<View style={styles.searchBar}>
|
|
544
|
+
<SearchIcon color={AppColors.grayTextWeak} size={14}/>
|
|
545
|
+
<TextInput placeholder="Search keys, values, or action payloads..." placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={onSearchChange} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
|
|
546
|
+
{search.length > 0 && (<TouchableScale onPress={() => onSearchChange('')} hitSlop={10}>
|
|
547
|
+
<ClearIcon color={AppColors.grayTextWeak} size={14}/>
|
|
548
|
+
</TouchableScale>)}
|
|
549
|
+
</View>
|
|
550
|
+
<View style={styles.filterChipRow}>
|
|
551
|
+
<Text style={styles.filterChipLabel}>FILTER BY:</Text>
|
|
552
|
+
<Pressable onPress={() => {
|
|
553
|
+
animateLayout();
|
|
554
|
+
if (searchModeType && !searchModeData)
|
|
555
|
+
return; // Prevent disabling both
|
|
556
|
+
setSearchModeType(!searchModeType);
|
|
557
|
+
}} style={[styles.filterChip, searchModeType && styles.filterChipActive]}>
|
|
558
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
|
|
559
|
+
<SignalIcon color={searchModeType ? AppColors.purple : AppColors.grayText} size={11}/>
|
|
560
|
+
<Text style={[styles.filterChipText, searchModeType && styles.filterChipTextActive]}>
|
|
561
|
+
Type / Key
|
|
562
|
+
</Text>
|
|
563
|
+
</View>
|
|
564
|
+
</Pressable>
|
|
565
|
+
<Pressable onPress={() => {
|
|
566
|
+
animateLayout();
|
|
567
|
+
if (!searchModeType && searchModeData)
|
|
568
|
+
return; // Prevent disabling both
|
|
569
|
+
setSearchModeData(!searchModeData);
|
|
570
|
+
}} style={[styles.filterChip, searchModeData && styles.filterChipActive]}>
|
|
571
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
|
|
572
|
+
<LayersIcon color={searchModeData ? AppColors.purple : AppColors.grayText} size={11}/>
|
|
573
|
+
<Text style={[styles.filterChipText, searchModeData && styles.filterChipTextActive]}>
|
|
574
|
+
Payload Data
|
|
575
|
+
</Text>
|
|
576
|
+
</View>
|
|
577
|
+
</Pressable>
|
|
578
|
+
</View>
|
|
414
579
|
</View>)}
|
|
580
|
+
|
|
581
|
+
{/* ── Tab Selector ── */}
|
|
582
|
+
<View style={styles.tabContainer}>
|
|
583
|
+
<Pressable style={[styles.tabButton, activeTab === 'state' && styles.tabButtonActive]} onPress={() => switchTab('state')}>
|
|
584
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
585
|
+
<LayersIcon color={activeTab === 'state' ? AppColors.purple : AppColors.grayText} size={12}/>
|
|
586
|
+
<Text style={[styles.tabButtonText, activeTab === 'state' && styles.tabButtonTextActive]}>
|
|
587
|
+
State Slices ({filteredStateKeys.length})
|
|
588
|
+
</Text>
|
|
589
|
+
</View>
|
|
590
|
+
</Pressable>
|
|
591
|
+
<Pressable style={[styles.tabButton, activeTab === 'timeline' && styles.tabButtonActive]} onPress={() => switchTab('timeline')}>
|
|
592
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
593
|
+
<TerminalIcon color={activeTab === 'timeline' ? AppColors.purple : AppColors.grayText} size={12}/>
|
|
594
|
+
<Text style={[styles.tabButtonText, activeTab === 'timeline' && styles.tabButtonTextActive]}>
|
|
595
|
+
Actions ({filteredActionHistory.length})
|
|
596
|
+
</Text>
|
|
597
|
+
</View>
|
|
598
|
+
</Pressable>
|
|
599
|
+
</View>
|
|
600
|
+
|
|
601
|
+
{/* ── State List View ── */}
|
|
602
|
+
{activeTab === 'state' && (<ScrollView style={styles.scrollList} contentContainerStyle={styles.scrollContent}>
|
|
603
|
+
{filteredStateKeys.length === 0 ? (<View style={styles.emptyState}>
|
|
604
|
+
<Text style={styles.emptyStateText}>
|
|
605
|
+
{isSearching ? 'No store slices match your search.' : 'Redux store contains no state.'}
|
|
606
|
+
</Text>
|
|
607
|
+
</View>) : (filteredStateKeys.map(reducerKey => {
|
|
608
|
+
const sliceData = state[reducerKey];
|
|
609
|
+
const fieldsCount = typeof sliceData === 'object' && sliceData !== null
|
|
610
|
+
? Object.keys(sliceData).length
|
|
611
|
+
: 1;
|
|
612
|
+
const lastAction = lastActionMap[reducerKey];
|
|
613
|
+
const lastUpdatedTime = lastAction ? lastAction.timestamp : 'Initial';
|
|
614
|
+
return (<TouchableScale key={reducerKey} onPress={() => {
|
|
615
|
+
animateLayout();
|
|
616
|
+
setActiveSliceKey(reducerKey);
|
|
617
|
+
}} style={styles.listRow}>
|
|
618
|
+
<View style={styles.sliceIcon}>
|
|
619
|
+
<FolderIcon color={AppColors.purple} size={11}/>
|
|
620
|
+
</View>
|
|
621
|
+
<View style={styles.rowMain}>
|
|
622
|
+
<Text style={styles.rowTitle}>{reducerKey}</Text>
|
|
623
|
+
<Text style={styles.rowSub}>
|
|
624
|
+
{getSize(sliceData)} • {fieldsCount} {fieldsCount === 1 ? 'field' : 'fields'} • Updated: {lastUpdatedTime}
|
|
625
|
+
</Text>
|
|
626
|
+
</View>
|
|
627
|
+
<ForwardChevronIcon color={AppColors.grayTextWeak} size={10}/>
|
|
628
|
+
</TouchableScale>);
|
|
629
|
+
}))}
|
|
630
|
+
</ScrollView>)}
|
|
631
|
+
|
|
632
|
+
{/* ── Timeline List View ── */}
|
|
633
|
+
{activeTab === 'timeline' && (<ScrollView style={styles.scrollList} contentContainerStyle={styles.scrollContent}>
|
|
634
|
+
{filteredActionHistory.length === 0 ? (<View style={styles.emptyState}>
|
|
635
|
+
<Text style={styles.emptyStateText}>
|
|
636
|
+
{isSearching
|
|
637
|
+
? 'No actions match your search.'
|
|
638
|
+
: 'No actions dispatched yet. Trigger an action in the app to populate history.'}
|
|
639
|
+
</Text>
|
|
640
|
+
</View>) : (filteredActionHistory.map((action, index) => {
|
|
641
|
+
const id = action.id;
|
|
642
|
+
const affectedSlices = action.affectedSlices || [];
|
|
643
|
+
return (<TouchableScale key={id} onPress={() => {
|
|
644
|
+
animateLayout();
|
|
645
|
+
setActiveActionId(id);
|
|
646
|
+
}} style={styles.listRow}>
|
|
647
|
+
<View style={styles.seqBadge}>
|
|
648
|
+
<Text style={styles.seqText}>#{action.id}</Text>
|
|
649
|
+
</View>
|
|
650
|
+
<View style={styles.rowMain}>
|
|
651
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
652
|
+
<TerminalIcon color={AppColors.purple} size={11}/>
|
|
653
|
+
<Text style={styles.actionTitleText} numberOfLines={1}>{action.type}</Text>
|
|
654
|
+
</View>
|
|
655
|
+
<View style={styles.actionMetaRow}>
|
|
656
|
+
<ClockIcon color={AppColors.grayTextWeak} size={9}/>
|
|
657
|
+
<Text style={styles.actionTimeText}>{action.timestamp} • {getSize(action.payload)}</Text>
|
|
658
|
+
</View>
|
|
659
|
+
{affectedSlices.length > 0 && (<View style={styles.tagsContainer}>
|
|
660
|
+
{affectedSlices.map(slice => (<View key={slice} style={styles.sliceTagMini}>
|
|
661
|
+
<Text style={styles.sliceTagMiniText}>{slice}</Text>
|
|
662
|
+
</View>))}
|
|
663
|
+
</View>)}
|
|
664
|
+
</View>
|
|
665
|
+
<ForwardChevronIcon color={AppColors.grayTextWeak} size={10}/>
|
|
666
|
+
</TouchableScale>);
|
|
667
|
+
}))}
|
|
668
|
+
</ScrollView>)}
|
|
415
669
|
</View>);
|
|
416
670
|
};
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
671
|
+
const styles = StyleSheet.create({
|
|
672
|
+
container: {
|
|
673
|
+
flex: 1,
|
|
674
|
+
backgroundColor: AppColors.grayBackground,
|
|
675
|
+
},
|
|
676
|
+
scrollList: {
|
|
677
|
+
flex: 1,
|
|
678
|
+
},
|
|
679
|
+
errorContainer: {
|
|
680
|
+
padding: 24,
|
|
681
|
+
alignItems: 'center',
|
|
420
682
|
},
|
|
421
|
-
|
|
683
|
+
errorText: {
|
|
684
|
+
fontFamily: AppFonts.interRegular,
|
|
685
|
+
fontSize: 12,
|
|
686
|
+
color: AppColors.grayTextWeak,
|
|
687
|
+
},
|
|
688
|
+
toolbar: {
|
|
422
689
|
flexDirection: 'row',
|
|
423
690
|
alignItems: 'center',
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
treeLeafConnector: {
|
|
429
|
-
position: 'absolute',
|
|
430
|
-
left: -12,
|
|
431
|
-
top: '50%',
|
|
432
|
-
width: 8,
|
|
433
|
-
height: 1,
|
|
691
|
+
justifyContent: 'space-between',
|
|
692
|
+
paddingHorizontal: 12,
|
|
693
|
+
paddingVertical: 10,
|
|
694
|
+
backgroundColor: AppColors.primaryLight,
|
|
434
695
|
borderBottomWidth: 1,
|
|
435
696
|
borderBottomColor: AppColors.dividerColor,
|
|
436
|
-
opacity: 0.5,
|
|
437
697
|
},
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
borderLeftColor: AppColors.dividerColor,
|
|
442
|
-
marginLeft: 6,
|
|
698
|
+
statusSection: {
|
|
699
|
+
flexDirection: 'row',
|
|
700
|
+
alignItems: 'center',
|
|
443
701
|
},
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
height: 10,
|
|
702
|
+
statusBadge: {
|
|
703
|
+
flexDirection: 'row',
|
|
447
704
|
alignItems: 'center',
|
|
448
|
-
|
|
705
|
+
paddingHorizontal: 8,
|
|
706
|
+
paddingVertical: 3,
|
|
707
|
+
borderRadius: 12,
|
|
708
|
+
borderWidth: 1,
|
|
449
709
|
},
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
color: AppColors.grayTextStrong,
|
|
710
|
+
statusBadgeActive: {
|
|
711
|
+
backgroundColor: AppColors.greenStatus,
|
|
712
|
+
borderColor: AppColors.greenColor + '30',
|
|
454
713
|
},
|
|
455
|
-
|
|
456
|
-
|
|
714
|
+
statusBadgePaused: {
|
|
715
|
+
backgroundColor: AppColors.grayBackground,
|
|
716
|
+
borderColor: AppColors.grayBorderSecondary,
|
|
457
717
|
},
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
718
|
+
statusDot: {
|
|
719
|
+
width: 6,
|
|
720
|
+
height: 6,
|
|
721
|
+
borderRadius: 3,
|
|
722
|
+
marginRight: 6,
|
|
461
723
|
},
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
fontSize: 11,
|
|
465
|
-
color: AppColors.grayTextWeak,
|
|
724
|
+
statusDotActive: {
|
|
725
|
+
backgroundColor: AppColors.greenBaggageText,
|
|
466
726
|
},
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
727
|
+
statusDotPaused: {
|
|
728
|
+
backgroundColor: AppColors.grayTextWeak,
|
|
729
|
+
},
|
|
730
|
+
statusBadgeText: {
|
|
731
|
+
fontFamily: AppFonts.interBold,
|
|
732
|
+
fontSize: 9,
|
|
733
|
+
letterSpacing: 0.5,
|
|
734
|
+
},
|
|
735
|
+
statusTextActive: {
|
|
736
|
+
color: AppColors.greenBaggageText,
|
|
737
|
+
},
|
|
738
|
+
statusTextPaused: {
|
|
739
|
+
color: AppColors.grayText,
|
|
471
740
|
},
|
|
472
|
-
|
|
741
|
+
toolbarActions: {
|
|
473
742
|
flexDirection: 'row',
|
|
474
743
|
alignItems: 'center',
|
|
475
|
-
|
|
476
|
-
marginBottom: 12,
|
|
744
|
+
gap: 8,
|
|
477
745
|
},
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
746
|
+
iconBtn: {
|
|
747
|
+
width: 28,
|
|
748
|
+
height: 28,
|
|
749
|
+
borderRadius: 6,
|
|
750
|
+
borderWidth: 1,
|
|
751
|
+
borderColor: AppColors.purple + '40',
|
|
752
|
+
backgroundColor: AppColors.purpleShade50,
|
|
753
|
+
alignItems: 'center',
|
|
754
|
+
justifyContent: 'center',
|
|
482
755
|
},
|
|
483
|
-
|
|
484
|
-
backgroundColor:
|
|
485
|
-
|
|
756
|
+
searchContainer: {
|
|
757
|
+
backgroundColor: AppColors.primaryLight,
|
|
758
|
+
borderBottomWidth: 1,
|
|
759
|
+
borderBottomColor: AppColors.dividerColor,
|
|
760
|
+
paddingBottom: 10,
|
|
761
|
+
},
|
|
762
|
+
searchBar: {
|
|
763
|
+
flexDirection: 'row',
|
|
764
|
+
alignItems: 'center',
|
|
765
|
+
backgroundColor: AppColors.grayBackground,
|
|
766
|
+
borderRadius: 8,
|
|
767
|
+
marginHorizontal: 12,
|
|
768
|
+
marginTop: 10,
|
|
769
|
+
marginBottom: 8,
|
|
770
|
+
paddingHorizontal: 10,
|
|
486
771
|
borderWidth: 1,
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
772
|
+
borderColor: AppColors.dividerColor,
|
|
773
|
+
height: 36,
|
|
774
|
+
},
|
|
775
|
+
filterChipRow: {
|
|
776
|
+
flexDirection: 'row',
|
|
777
|
+
alignItems: 'center',
|
|
778
|
+
paddingHorizontal: 12,
|
|
779
|
+
gap: 8,
|
|
490
780
|
},
|
|
491
|
-
|
|
781
|
+
filterChipLabel: {
|
|
492
782
|
fontFamily: AppFonts.interBold,
|
|
783
|
+
fontSize: 9,
|
|
784
|
+
color: AppColors.grayTextWeak,
|
|
785
|
+
letterSpacing: 0.5,
|
|
786
|
+
marginRight: 2,
|
|
787
|
+
},
|
|
788
|
+
filterChip: {
|
|
789
|
+
paddingHorizontal: 10,
|
|
790
|
+
paddingVertical: 4,
|
|
791
|
+
borderRadius: 12,
|
|
792
|
+
backgroundColor: AppColors.grayBackground,
|
|
793
|
+
borderWidth: 1,
|
|
794
|
+
borderColor: AppColors.grayBorderSecondary,
|
|
795
|
+
},
|
|
796
|
+
filterChipActive: {
|
|
797
|
+
backgroundColor: AppColors.purpleShade50,
|
|
798
|
+
borderColor: AppColors.purple,
|
|
799
|
+
},
|
|
800
|
+
filterChipText: {
|
|
801
|
+
fontFamily: AppFonts.interMedium,
|
|
493
802
|
fontSize: 10,
|
|
494
|
-
color:
|
|
803
|
+
color: AppColors.grayText,
|
|
804
|
+
},
|
|
805
|
+
filterChipTextActive: {
|
|
806
|
+
color: AppColors.purple,
|
|
807
|
+
fontFamily: AppFonts.interBold,
|
|
808
|
+
},
|
|
809
|
+
searchInput: {
|
|
810
|
+
flex: 1,
|
|
811
|
+
fontFamily: AppFonts.interRegular,
|
|
812
|
+
fontSize: 12,
|
|
813
|
+
color: AppColors.grayTextStrong,
|
|
814
|
+
marginLeft: 6,
|
|
815
|
+
padding: 0,
|
|
495
816
|
},
|
|
496
|
-
|
|
497
|
-
|
|
817
|
+
tabContainer: {
|
|
818
|
+
flexDirection: 'row',
|
|
819
|
+
backgroundColor: AppColors.dividerColor,
|
|
820
|
+
borderRadius: 8,
|
|
821
|
+
padding: 3,
|
|
822
|
+
marginHorizontal: 12,
|
|
823
|
+
marginTop: 10,
|
|
824
|
+
marginBottom: 10,
|
|
825
|
+
},
|
|
826
|
+
tabButton: {
|
|
827
|
+
flex: 1,
|
|
828
|
+
paddingVertical: 7,
|
|
498
829
|
alignItems: 'center',
|
|
499
830
|
justifyContent: 'center',
|
|
831
|
+
borderRadius: 6,
|
|
832
|
+
},
|
|
833
|
+
tabButtonActive: {
|
|
834
|
+
backgroundColor: AppColors.primaryLight,
|
|
835
|
+
shadowColor: AppColors.shadowColorString,
|
|
836
|
+
shadowOffset: { width: 0, height: 1 },
|
|
837
|
+
shadowOpacity: 0.08,
|
|
838
|
+
shadowRadius: 2,
|
|
839
|
+
elevation: 2,
|
|
840
|
+
},
|
|
841
|
+
tabButtonText: {
|
|
842
|
+
fontFamily: AppFonts.interMedium,
|
|
843
|
+
fontSize: 12,
|
|
844
|
+
color: AppColors.grayText,
|
|
500
845
|
},
|
|
501
|
-
|
|
846
|
+
tabButtonTextActive: {
|
|
847
|
+
color: AppColors.purple,
|
|
848
|
+
fontFamily: AppFonts.interBold,
|
|
849
|
+
},
|
|
850
|
+
scrollContent: {
|
|
851
|
+
paddingBottom: 24,
|
|
852
|
+
paddingHorizontal: 12,
|
|
853
|
+
},
|
|
854
|
+
emptyState: {
|
|
855
|
+
padding: 24,
|
|
856
|
+
alignItems: 'center',
|
|
857
|
+
},
|
|
858
|
+
emptyStateText: {
|
|
502
859
|
fontFamily: AppFonts.interRegular,
|
|
503
860
|
fontSize: 12,
|
|
504
861
|
color: AppColors.grayTextWeak,
|
|
505
862
|
textAlign: 'center',
|
|
506
|
-
lineHeight: 18,
|
|
507
|
-
},
|
|
508
|
-
listContainer: {
|
|
509
|
-
paddingLeft: 12,
|
|
510
|
-
},
|
|
511
|
-
timelineItem: {
|
|
512
|
-
position: 'relative',
|
|
513
|
-
paddingLeft: 20,
|
|
514
|
-
marginBottom: 12,
|
|
515
|
-
},
|
|
516
|
-
verticalLine: {
|
|
517
|
-
position: 'absolute',
|
|
518
|
-
left: 4,
|
|
519
|
-
top: 0,
|
|
520
|
-
bottom: -12,
|
|
521
|
-
width: 1,
|
|
522
|
-
backgroundColor: AppColors.dividerColor,
|
|
523
863
|
},
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
borderRadius:
|
|
864
|
+
listRow: {
|
|
865
|
+
flexDirection: 'row',
|
|
866
|
+
alignItems: 'center',
|
|
867
|
+
backgroundColor: AppColors.primaryLight,
|
|
868
|
+
paddingHorizontal: 12,
|
|
869
|
+
paddingVertical: 14,
|
|
870
|
+
borderRadius: 10,
|
|
871
|
+
borderWidth: 1,
|
|
872
|
+
borderColor: AppColors.grayBorderSecondary,
|
|
873
|
+
marginBottom: 8,
|
|
874
|
+
shadowColor: AppColors.shadowColorString,
|
|
875
|
+
shadowOffset: { width: 0, height: 1 },
|
|
876
|
+
shadowOpacity: 0.03,
|
|
877
|
+
shadowRadius: 2,
|
|
878
|
+
elevation: 1,
|
|
879
|
+
},
|
|
880
|
+
rowMain: {
|
|
881
|
+
flex: 1,
|
|
882
|
+
marginLeft: 10,
|
|
883
|
+
},
|
|
884
|
+
rowTitle: {
|
|
885
|
+
fontFamily: AppFonts.interBold,
|
|
886
|
+
fontSize: 13.5,
|
|
887
|
+
color: AppColors.primaryBlack,
|
|
888
|
+
},
|
|
889
|
+
rowSub: {
|
|
890
|
+
fontFamily: AppFonts.interRegular,
|
|
891
|
+
fontSize: 10,
|
|
892
|
+
color: AppColors.grayTextWeak,
|
|
893
|
+
marginTop: 2,
|
|
894
|
+
},
|
|
895
|
+
sliceIcon: {
|
|
896
|
+
width: 22,
|
|
897
|
+
height: 22,
|
|
898
|
+
borderRadius: 6,
|
|
531
899
|
backgroundColor: AppColors.purpleShade50,
|
|
900
|
+
alignItems: 'center',
|
|
901
|
+
justifyContent: 'center',
|
|
902
|
+
},
|
|
903
|
+
seqBadge: {
|
|
904
|
+
backgroundColor: AppColors.grayBackground,
|
|
905
|
+
borderColor: AppColors.grayBorderSecondary,
|
|
532
906
|
borderWidth: 1,
|
|
533
|
-
|
|
907
|
+
borderRadius: 4,
|
|
908
|
+
paddingHorizontal: 5,
|
|
909
|
+
paddingVertical: 2,
|
|
534
910
|
alignItems: 'center',
|
|
535
911
|
justifyContent: 'center',
|
|
912
|
+
minWidth: 28,
|
|
536
913
|
},
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
backgroundColor: AppColors.purple,
|
|
914
|
+
seqText: {
|
|
915
|
+
fontFamily: AppFonts.interBold,
|
|
916
|
+
fontSize: 9.5,
|
|
917
|
+
color: AppColors.grayText,
|
|
542
918
|
},
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
borderRadius: 8,
|
|
548
|
-
padding: 10,
|
|
919
|
+
actionTitleText: {
|
|
920
|
+
fontFamily: AppFonts.interBold,
|
|
921
|
+
fontSize: 13,
|
|
922
|
+
color: AppColors.purple,
|
|
549
923
|
},
|
|
550
|
-
|
|
924
|
+
actionMetaRow: {
|
|
551
925
|
flexDirection: 'row',
|
|
552
926
|
alignItems: 'center',
|
|
553
|
-
|
|
554
|
-
|
|
927
|
+
gap: 4,
|
|
928
|
+
marginTop: 2,
|
|
555
929
|
},
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
930
|
+
actionTimeText: {
|
|
931
|
+
fontFamily: AppFonts.interRegular,
|
|
932
|
+
fontSize: 9.5,
|
|
933
|
+
color: AppColors.grayTextWeak,
|
|
934
|
+
},
|
|
935
|
+
tagsContainer: {
|
|
936
|
+
flexDirection: 'row',
|
|
937
|
+
flexWrap: 'wrap',
|
|
938
|
+
gap: 4,
|
|
939
|
+
marginTop: 4,
|
|
940
|
+
},
|
|
941
|
+
sliceTagMini: {
|
|
942
|
+
backgroundColor: AppColors.purpleShade50,
|
|
943
|
+
borderColor: AppColors.purple + '20',
|
|
559
944
|
borderWidth: 1,
|
|
560
|
-
borderRadius:
|
|
561
|
-
paddingHorizontal:
|
|
562
|
-
paddingVertical:
|
|
563
|
-
|
|
945
|
+
borderRadius: 3,
|
|
946
|
+
paddingHorizontal: 4,
|
|
947
|
+
paddingVertical: 0.5,
|
|
948
|
+
},
|
|
949
|
+
sliceTagMiniText: {
|
|
950
|
+
fontFamily: AppFonts.interMedium,
|
|
951
|
+
fontSize: 8.5,
|
|
952
|
+
color: AppColors.purple,
|
|
953
|
+
},
|
|
954
|
+
// ── Detail Views Styles ──
|
|
955
|
+
detailHeaderBar: {
|
|
956
|
+
flexDirection: 'row',
|
|
957
|
+
alignItems: 'center',
|
|
958
|
+
paddingHorizontal: 12,
|
|
959
|
+
paddingVertical: 12,
|
|
960
|
+
backgroundColor: AppColors.primaryLight,
|
|
961
|
+
borderBottomWidth: 1,
|
|
962
|
+
borderBottomColor: AppColors.dividerColor,
|
|
963
|
+
},
|
|
964
|
+
backBtn: {
|
|
965
|
+
flexDirection: 'row',
|
|
966
|
+
alignItems: 'center',
|
|
967
|
+
paddingVertical: 6,
|
|
968
|
+
paddingRight: 8,
|
|
564
969
|
},
|
|
565
|
-
|
|
970
|
+
backBtnText: {
|
|
566
971
|
fontFamily: AppFonts.interBold,
|
|
567
|
-
fontSize:
|
|
972
|
+
fontSize: 12.5,
|
|
568
973
|
color: AppColors.purple,
|
|
569
974
|
},
|
|
570
|
-
|
|
975
|
+
detailHeaderInfo: {
|
|
976
|
+
flex: 1,
|
|
977
|
+
marginLeft: 10,
|
|
978
|
+
marginRight: 10,
|
|
979
|
+
},
|
|
980
|
+
detailTitle: {
|
|
981
|
+
fontFamily: AppFonts.interBold,
|
|
982
|
+
fontSize: 13.5,
|
|
983
|
+
color: AppColors.primaryBlack,
|
|
984
|
+
},
|
|
985
|
+
detailSub: {
|
|
571
986
|
fontFamily: AppFonts.interRegular,
|
|
572
987
|
fontSize: 10,
|
|
573
988
|
color: AppColors.grayTextWeak,
|
|
989
|
+
marginTop: 2,
|
|
574
990
|
},
|
|
575
|
-
|
|
991
|
+
detailHeaderActions: {
|
|
992
|
+
flexDirection: 'row',
|
|
993
|
+
alignItems: 'center',
|
|
994
|
+
gap: 6,
|
|
995
|
+
},
|
|
996
|
+
detailHeaderBtn: {
|
|
997
|
+
padding: 6,
|
|
998
|
+
borderRadius: 6,
|
|
999
|
+
borderWidth: 1,
|
|
1000
|
+
borderColor: AppColors.purple + '20',
|
|
1001
|
+
backgroundColor: AppColors.purpleShade50,
|
|
1002
|
+
alignItems: 'center',
|
|
1003
|
+
justifyContent: 'center',
|
|
1004
|
+
width: 28,
|
|
1005
|
+
height: 28,
|
|
1006
|
+
},
|
|
1007
|
+
detailCopyBtn: {
|
|
1008
|
+
padding: 6,
|
|
1009
|
+
borderRadius: 6,
|
|
1010
|
+
borderWidth: 1,
|
|
1011
|
+
borderColor: AppColors.purple + '20',
|
|
1012
|
+
backgroundColor: AppColors.purpleShade50,
|
|
1013
|
+
},
|
|
1014
|
+
detailTimeRow: {
|
|
576
1015
|
flexDirection: 'row',
|
|
577
1016
|
alignItems: 'center',
|
|
578
|
-
flexWrap: 'wrap',
|
|
579
1017
|
gap: 4,
|
|
580
|
-
marginTop:
|
|
1018
|
+
marginTop: 1,
|
|
581
1019
|
},
|
|
582
|
-
|
|
583
|
-
fontFamily: AppFonts.
|
|
1020
|
+
detailTimeText: {
|
|
1021
|
+
fontFamily: AppFonts.interRegular,
|
|
584
1022
|
fontSize: 10,
|
|
585
1023
|
color: AppColors.grayTextWeak,
|
|
586
|
-
marginRight: 2,
|
|
587
1024
|
},
|
|
588
|
-
|
|
1025
|
+
detailAffectedTags: {
|
|
1026
|
+
paddingHorizontal: 12,
|
|
1027
|
+
paddingVertical: 8,
|
|
589
1028
|
backgroundColor: AppColors.grayBackground,
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
borderRadius: 4,
|
|
593
|
-
paddingHorizontal: 4,
|
|
594
|
-
paddingVertical: 1,
|
|
595
|
-
},
|
|
596
|
-
sliceText: {
|
|
597
|
-
fontFamily: AppFonts.interMedium,
|
|
598
|
-
fontSize: 9,
|
|
599
|
-
color: AppColors.grayText,
|
|
600
|
-
},
|
|
601
|
-
payloadContainer: {
|
|
602
|
-
marginTop: 10,
|
|
603
|
-
borderTopWidth: 1,
|
|
604
|
-
borderTopColor: AppColors.dividerColor,
|
|
605
|
-
paddingTop: 8,
|
|
1029
|
+
borderBottomWidth: 1,
|
|
1030
|
+
borderBottomColor: AppColors.dividerColor,
|
|
606
1031
|
},
|
|
607
|
-
|
|
1032
|
+
affectedTagsLabel: {
|
|
608
1033
|
fontFamily: AppFonts.interBold,
|
|
609
|
-
fontSize:
|
|
1034
|
+
fontSize: 9,
|
|
610
1035
|
color: AppColors.grayTextWeak,
|
|
611
|
-
|
|
1036
|
+
letterSpacing: 0.5,
|
|
612
1037
|
marginBottom: 4,
|
|
613
1038
|
},
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
1039
|
+
detailTagsList: {
|
|
1040
|
+
flexDirection: 'row',
|
|
1041
|
+
flexWrap: 'wrap',
|
|
1042
|
+
gap: 4,
|
|
618
1043
|
},
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
1044
|
+
sliceTag: {
|
|
1045
|
+
backgroundColor: AppColors.purpleShade50,
|
|
1046
|
+
borderColor: AppColors.purple + '20',
|
|
1047
|
+
borderWidth: 1,
|
|
1048
|
+
borderRadius: 4,
|
|
1049
|
+
paddingHorizontal: 6,
|
|
1050
|
+
paddingVertical: 1.5,
|
|
1051
|
+
},
|
|
1052
|
+
sliceTagText: {
|
|
1053
|
+
fontFamily: AppFonts.interMedium,
|
|
1054
|
+
fontSize: 9.5,
|
|
1055
|
+
color: AppColors.purple,
|
|
623
1056
|
},
|
|
624
|
-
|
|
1057
|
+
subTabRow: {
|
|
625
1058
|
flexDirection: 'row',
|
|
626
|
-
|
|
627
|
-
backgroundColor: AppColors.purple,
|
|
628
|
-
borderRadius: 8,
|
|
629
|
-
paddingVertical: 10,
|
|
630
|
-
paddingHorizontal: 12,
|
|
1059
|
+
padding: 8,
|
|
631
1060
|
gap: 8,
|
|
1061
|
+
backgroundColor: AppColors.primaryLight,
|
|
1062
|
+
borderBottomWidth: 1,
|
|
1063
|
+
borderBottomColor: AppColors.dividerColor,
|
|
632
1064
|
},
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
1065
|
+
subTabPill: {
|
|
1066
|
+
paddingHorizontal: 12,
|
|
1067
|
+
paddingVertical: 5,
|
|
1068
|
+
borderRadius: 12,
|
|
1069
|
+
backgroundColor: AppColors.grayBackground,
|
|
1070
|
+
borderWidth: 1,
|
|
1071
|
+
borderColor: AppColors.grayBorderSecondary,
|
|
638
1072
|
},
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
color: '#FFFFFF',
|
|
1073
|
+
subTabPillActive: {
|
|
1074
|
+
backgroundColor: AppColors.purple,
|
|
1075
|
+
borderColor: AppColors.purple,
|
|
643
1076
|
},
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
borderRadius: 12,
|
|
649
|
-
marginLeft: 'auto',
|
|
1077
|
+
subTabPillText: {
|
|
1078
|
+
fontFamily: AppFonts.interMedium,
|
|
1079
|
+
fontSize: 10.5,
|
|
1080
|
+
color: AppColors.grayText,
|
|
650
1081
|
},
|
|
651
|
-
|
|
652
|
-
fontFamily: AppFonts.interBold,
|
|
653
|
-
fontSize: 10,
|
|
1082
|
+
subTabPillTextActive: {
|
|
654
1083
|
color: '#FFFFFF',
|
|
1084
|
+
fontFamily: AppFonts.interBold,
|
|
655
1085
|
},
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
marginTop: 4,
|
|
1086
|
+
detailScrollContent: {
|
|
1087
|
+
paddingBottom: 24,
|
|
659
1088
|
},
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
paddingVertical: 4,
|
|
1089
|
+
jsonViewerContainer: {
|
|
1090
|
+
paddingHorizontal: 12,
|
|
1091
|
+
paddingVertical: 8,
|
|
664
1092
|
},
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
left: 0,
|
|
668
|
-
top: 0,
|
|
669
|
-
bottom: 0,
|
|
670
|
-
width: 1,
|
|
671
|
-
backgroundColor: AppColors.dividerColor,
|
|
1093
|
+
detailBox: {
|
|
1094
|
+
padding: 10,
|
|
672
1095
|
},
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
backgroundColor: AppColors.dividerColor,
|
|
1096
|
+
detailBoxHeader: {
|
|
1097
|
+
flexDirection: 'row',
|
|
1098
|
+
alignItems: 'center',
|
|
1099
|
+
justifyContent: 'space-between',
|
|
1100
|
+
marginBottom: 8,
|
|
1101
|
+
paddingHorizontal: 4,
|
|
680
1102
|
},
|
|
681
|
-
|
|
1103
|
+
detailBoxTitle: {
|
|
1104
|
+
fontFamily: AppFonts.interBold,
|
|
1105
|
+
fontSize: 10,
|
|
1106
|
+
color: AppColors.grayTextWeak,
|
|
1107
|
+
letterSpacing: 0.5,
|
|
1108
|
+
textTransform: 'uppercase',
|
|
1109
|
+
},
|
|
1110
|
+
apiLikeInfoBar: {
|
|
1111
|
+
paddingHorizontal: 16,
|
|
1112
|
+
paddingVertical: 14,
|
|
1113
|
+
backgroundColor: AppColors.primaryLight,
|
|
1114
|
+
borderBottomWidth: 1,
|
|
1115
|
+
borderBottomColor: AppColors.dividerColor,
|
|
1116
|
+
},
|
|
1117
|
+
apiBadgeRow: {
|
|
682
1118
|
flexDirection: 'row',
|
|
683
1119
|
alignItems: 'center',
|
|
684
|
-
paddingVertical: 6,
|
|
685
1120
|
gap: 6,
|
|
1121
|
+
marginBottom: 8,
|
|
686
1122
|
},
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
1123
|
+
apiMethodBadge: {
|
|
1124
|
+
paddingHorizontal: 8,
|
|
1125
|
+
paddingVertical: 2,
|
|
690
1126
|
borderRadius: 4,
|
|
691
|
-
backgroundColor: AppColors.purpleShade50,
|
|
692
1127
|
alignItems: 'center',
|
|
693
1128
|
justifyContent: 'center',
|
|
694
1129
|
},
|
|
695
|
-
|
|
1130
|
+
apiMethodBadgeText: {
|
|
696
1131
|
fontFamily: AppFonts.interBold,
|
|
697
|
-
fontSize:
|
|
698
|
-
color:
|
|
699
|
-
|
|
700
|
-
reducerChildren: {
|
|
701
|
-
paddingLeft: 20,
|
|
702
|
-
position: 'relative',
|
|
703
|
-
marginTop: 4,
|
|
704
|
-
gap: 10,
|
|
705
|
-
},
|
|
706
|
-
childrenVerticalLine: {
|
|
707
|
-
position: 'absolute',
|
|
708
|
-
left: 8,
|
|
709
|
-
top: 0,
|
|
710
|
-
bottom: 16,
|
|
711
|
-
width: 1,
|
|
712
|
-
backgroundColor: AppColors.dividerColor,
|
|
1132
|
+
fontSize: 9.5,
|
|
1133
|
+
color: '#FFFFFF',
|
|
1134
|
+
letterSpacing: 0.5,
|
|
713
1135
|
},
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
left: -12,
|
|
724
|
-
top: 10,
|
|
725
|
-
width: 12,
|
|
726
|
-
height: 1,
|
|
727
|
-
backgroundColor: AppColors.dividerColor,
|
|
1136
|
+
apiChip: {
|
|
1137
|
+
paddingHorizontal: 7,
|
|
1138
|
+
paddingVertical: 2,
|
|
1139
|
+
borderRadius: 4,
|
|
1140
|
+
backgroundColor: 'rgba(104,75,155,0.06)',
|
|
1141
|
+
borderWidth: 1,
|
|
1142
|
+
borderColor: 'rgba(104,75,155,0.15)',
|
|
1143
|
+
alignItems: 'center',
|
|
1144
|
+
justifyContent: 'center',
|
|
728
1145
|
},
|
|
729
|
-
|
|
1146
|
+
apiChipText: {
|
|
730
1147
|
fontFamily: AppFonts.interBold,
|
|
731
|
-
fontSize:
|
|
732
|
-
color: AppColors.
|
|
733
|
-
marginTop: 2,
|
|
734
|
-
},
|
|
735
|
-
actionTypeBadge: {
|
|
736
|
-
backgroundColor: '#FCE7F3',
|
|
737
|
-
borderColor: '#FBCFE8',
|
|
738
|
-
borderWidth: 1,
|
|
739
|
-
borderRadius: 6,
|
|
740
|
-
paddingHorizontal: 6,
|
|
741
|
-
paddingVertical: 2,
|
|
1148
|
+
fontSize: 9.5,
|
|
1149
|
+
color: AppColors.purple,
|
|
742
1150
|
},
|
|
743
|
-
|
|
1151
|
+
apiTitleText: {
|
|
744
1152
|
fontFamily: AppFonts.interBold,
|
|
745
|
-
fontSize:
|
|
746
|
-
color:
|
|
1153
|
+
fontSize: 16,
|
|
1154
|
+
color: AppColors.primaryBlack,
|
|
1155
|
+
marginBottom: 8,
|
|
1156
|
+
},
|
|
1157
|
+
apiMetaRow: {
|
|
1158
|
+
flexDirection: 'row',
|
|
1159
|
+
alignItems: 'center',
|
|
1160
|
+
gap: 4,
|
|
747
1161
|
},
|
|
748
|
-
|
|
1162
|
+
apiMetaText: {
|
|
749
1163
|
fontFamily: AppFonts.interRegular,
|
|
750
1164
|
fontSize: 11,
|
|
751
1165
|
color: AppColors.grayTextWeak,
|
|
752
1166
|
},
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
1167
|
+
contentDetailSubHeader: {
|
|
1168
|
+
flexDirection: 'row',
|
|
1169
|
+
alignItems: 'center',
|
|
1170
|
+
justifyContent: 'space-between',
|
|
1171
|
+
paddingHorizontal: 12,
|
|
1172
|
+
paddingVertical: 10,
|
|
1173
|
+
borderBottomWidth: 1,
|
|
1174
|
+
borderBottomColor: AppColors.dividerColor,
|
|
1175
|
+
backgroundColor: AppColors.primaryLight,
|
|
758
1176
|
},
|
|
759
1177
|
});
|