related-ui-components 1.6.2 → 1.6.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.
- package/lib/commonjs/app.js +114 -253
- package/lib/commonjs/app.js.map +1 -1
- package/lib/commonjs/components/ScratchCard/ScratchCard.js +137 -94
- package/lib/commonjs/components/ScratchCard/ScratchCard.js.map +1 -1
- package/lib/module/app.js +120 -257
- package/lib/module/app.js.map +1 -1
- package/lib/module/components/ScratchCard/ScratchCard.js +149 -99
- package/lib/module/components/ScratchCard/ScratchCard.js.map +1 -1
- package/lib/typescript/commonjs/app.d.ts.map +1 -1
- package/lib/typescript/commonjs/components/ScratchCard/ScratchCard.d.ts.map +1 -1
- package/lib/typescript/module/app.d.ts.map +1 -1
- package/lib/typescript/module/components/ScratchCard/ScratchCard.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/app.tsx +99 -198
- package/src/components/ScratchCard/ScratchCard.tsx +179 -114
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
useRef,
|
|
3
|
+
useState,
|
|
4
|
+
useEffect,
|
|
5
|
+
useCallback, // Import useCallback
|
|
6
|
+
} from "react";
|
|
2
7
|
import {
|
|
3
8
|
Canvas,
|
|
4
9
|
Group,
|
|
@@ -7,12 +12,13 @@ import {
|
|
|
7
12
|
Path,
|
|
8
13
|
Rect,
|
|
9
14
|
Skia,
|
|
10
|
-
LinearGradient,
|
|
11
|
-
vec,
|
|
12
15
|
useImage,
|
|
13
16
|
Text,
|
|
14
17
|
useFont,
|
|
15
18
|
notifyChange,
|
|
19
|
+
SkPath, // Import SkPath type
|
|
20
|
+
SkFont, // Import SkFont type
|
|
21
|
+
SkImage, // Import SkImage type
|
|
16
22
|
} from "@shopify/react-native-skia";
|
|
17
23
|
import {
|
|
18
24
|
StyleProp,
|
|
@@ -20,17 +26,25 @@ import {
|
|
|
20
26
|
ViewStyle,
|
|
21
27
|
StyleSheet,
|
|
22
28
|
ImageRequireSource,
|
|
23
|
-
|
|
29
|
+
LogBox, // Optional: for ignoring specific logs if needed
|
|
24
30
|
} from "react-native";
|
|
25
|
-
|
|
26
|
-
import {
|
|
31
|
+
// Import runOnJS and useSharedValue
|
|
32
|
+
import { runOnJS, useSharedValue } from "react-native-reanimated";
|
|
33
|
+
import {
|
|
34
|
+
Gesture,
|
|
35
|
+
GestureDetector,
|
|
36
|
+
GestureHandlerRootView,
|
|
37
|
+
} from "react-native-gesture-handler";
|
|
38
|
+
|
|
39
|
+
// Ignore specific warning if it appears, related to path mutation - use cautiously
|
|
40
|
+
// LogBox.ignoreLogs(['Skia: SkPath.Make()']);
|
|
27
41
|
|
|
28
42
|
type ScratchCardProps = {
|
|
29
43
|
style?: StyleProp<ViewStyle>;
|
|
30
44
|
image?: ImageRequireSource;
|
|
31
45
|
children?: React.ReactNode;
|
|
32
46
|
brushStrokeWidth?: number;
|
|
33
|
-
revealThreshold?: number;
|
|
47
|
+
revealThreshold?: number; // Percentage (0 to 1) - Based on bounding box
|
|
34
48
|
width?: number;
|
|
35
49
|
height?: number;
|
|
36
50
|
backgroundColor?: string;
|
|
@@ -44,7 +58,7 @@ type ScratchCardProps = {
|
|
|
44
58
|
const ScratchCard: React.FC<ScratchCardProps> = ({
|
|
45
59
|
style,
|
|
46
60
|
children,
|
|
47
|
-
image
|
|
61
|
+
image,
|
|
48
62
|
brushStrokeWidth = 50,
|
|
49
63
|
revealThreshold = 0.8,
|
|
50
64
|
width = 300,
|
|
@@ -56,123 +70,174 @@ const ScratchCard: React.FC<ScratchCardProps> = ({
|
|
|
56
70
|
textFontSize = 16,
|
|
57
71
|
onScratched,
|
|
58
72
|
}) => {
|
|
59
|
-
const
|
|
60
|
-
const
|
|
73
|
+
const loadedImg = useImage(image);
|
|
74
|
+
const loadedFont = useFont(textFont, textFontSize);
|
|
61
75
|
|
|
62
76
|
const [[areaWidth, areaHeight], setSize] = useState([0, 0]);
|
|
63
77
|
const [isScratched, setScratched] = useState(false);
|
|
78
|
+
const [isLayoutReady, setLayoutReady] = useState(false);
|
|
64
79
|
|
|
65
|
-
const
|
|
80
|
+
const isThresholdReached = useSharedValue(false);
|
|
66
81
|
|
|
67
|
-
|
|
68
|
-
const checkScratchProgress = () => {
|
|
69
|
-
const bounds = path.value.getBounds();
|
|
70
|
-
const scratchedArea = bounds.height * bounds.width;
|
|
71
|
-
const totalArea = areaWidth * areaHeight;
|
|
82
|
+
const path = useSharedValue<SkPath>(Skia.Path.Make());
|
|
72
83
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
path.value = Skia.Path.Make();
|
|
86
|
+
isThresholdReached.value = false;
|
|
87
|
+
setScratched(false);
|
|
88
|
+
}, [areaWidth, areaHeight]);
|
|
89
|
+
|
|
90
|
+
const handleLayout = useCallback(
|
|
91
|
+
(event: any) => {
|
|
92
|
+
const { width: newWidth, height: newHeight } = event.nativeEvent.layout;
|
|
93
|
+
if (newWidth > 0 && newHeight > 0) {
|
|
94
|
+
if (newWidth !== areaWidth || newHeight !== areaHeight) {
|
|
95
|
+
setSize([newWidth, newHeight]);
|
|
96
|
+
}
|
|
97
|
+
if (!isLayoutReady) {
|
|
98
|
+
setLayoutReady(true);
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
setLayoutReady(false);
|
|
77
102
|
}
|
|
78
|
-
}
|
|
79
|
-
|
|
103
|
+
},
|
|
104
|
+
[areaWidth, areaHeight, isLayoutReady]
|
|
105
|
+
);
|
|
80
106
|
|
|
81
|
-
|
|
107
|
+
const revealCardOnJS = useCallback(() => {
|
|
108
|
+
setScratched(true);
|
|
109
|
+
onScratched?.();
|
|
110
|
+
}, [onScratched]);
|
|
82
111
|
|
|
83
112
|
const pan = Gesture.Pan()
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
113
|
+
.averageTouches(true)
|
|
114
|
+
.maxPointers(1)
|
|
115
|
+
.onBegin((e) => {
|
|
116
|
+
if (!isLayoutReady) return;
|
|
117
|
+
try {
|
|
118
|
+
const newPath = path.value.copy();
|
|
119
|
+
newPath.moveTo(e.x, e.y);
|
|
120
|
+
newPath.lineTo(e.x + 0.001, e.y + 0.001);
|
|
121
|
+
path.value = newPath;
|
|
122
|
+
notifyChange(path as any);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error("ScratchCard: Error in onBegin:", error);
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
.onChange((e) => {
|
|
128
|
+
if (!isLayoutReady || isThresholdReached.value) return;
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const newPath = path.value.copy();
|
|
132
|
+
newPath.lineTo(e.x, e.y);
|
|
133
|
+
path.value = newPath;
|
|
134
|
+
notifyChange(path as any);
|
|
135
|
+
|
|
136
|
+
const bounds = path.value.getBounds();
|
|
137
|
+
|
|
138
|
+
if (!bounds || areaWidth <= 0 || areaHeight <= 0) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const scratchedArea = bounds.width * bounds.height;
|
|
143
|
+
const totalArea = areaWidth * areaHeight;
|
|
144
|
+
|
|
145
|
+
if (totalArea > 0 && scratchedArea / totalArea > revealThreshold) {
|
|
146
|
+
if (!isThresholdReached.value) {
|
|
147
|
+
isThresholdReached.value = true;
|
|
148
|
+
runOnJS(revealCardOnJS)();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error("ScratchCard: Error in onChange (UI Thread):", error);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const textMetrics = React.useMemo(() => {
|
|
157
|
+
if (loadedFont && text && areaWidth > 0 && areaHeight > 0) {
|
|
158
|
+
const metrics = loadedFont.measureText(text);
|
|
159
|
+
const textX = areaWidth / 2 - metrics.width / 2;
|
|
160
|
+
const textY = areaHeight / 2 + metrics.height / 3;
|
|
161
|
+
return {
|
|
162
|
+
x: textX,
|
|
163
|
+
y: textY,
|
|
164
|
+
width: metrics.width,
|
|
165
|
+
height: metrics.height,
|
|
166
|
+
};
|
|
109
167
|
}
|
|
168
|
+
return null;
|
|
169
|
+
}, [loadedFont, text, areaWidth, areaHeight]);
|
|
110
170
|
|
|
111
|
-
|
|
112
|
-
})
|
|
171
|
+
const canRenderCanvas = isLayoutReady && areaWidth > 0 && areaHeight > 0;
|
|
113
172
|
|
|
114
173
|
return (
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
<GestureHandlerRootView style={{justifyContent:"center", alignContent: "center"}}>
|
|
175
|
+
<View
|
|
176
|
+
onLayout={handleLayout}
|
|
177
|
+
style={[styles.container, style, { width, height }]}
|
|
178
|
+
>
|
|
179
|
+
<View style={styles.content}>{children}</View>
|
|
180
|
+
|
|
181
|
+
{!isScratched && canRenderCanvas && (
|
|
182
|
+
<GestureDetector gesture={pan}>
|
|
183
|
+
<Canvas style={styles.canvas}>
|
|
184
|
+
<Mask
|
|
185
|
+
mode="luminance"
|
|
186
|
+
mask={
|
|
187
|
+
<Group>
|
|
188
|
+
<Rect
|
|
189
|
+
x={0}
|
|
190
|
+
y={0}
|
|
191
|
+
width={areaWidth}
|
|
192
|
+
height={areaHeight}
|
|
193
|
+
color="white"
|
|
194
|
+
/>
|
|
195
|
+
<Path
|
|
196
|
+
path={path}
|
|
197
|
+
color="black"
|
|
198
|
+
style="stroke"
|
|
199
|
+
strokeJoin="round"
|
|
200
|
+
strokeCap="round"
|
|
201
|
+
strokeWidth={brushStrokeWidth}
|
|
202
|
+
/>
|
|
203
|
+
</Group>
|
|
204
|
+
}
|
|
205
|
+
>
|
|
206
|
+
{loadedImg ? (
|
|
207
|
+
<Image
|
|
208
|
+
image={loadedImg}
|
|
209
|
+
fit="cover"
|
|
210
|
+
x={0}
|
|
211
|
+
y={0}
|
|
212
|
+
width={areaWidth}
|
|
213
|
+
height={areaHeight}
|
|
214
|
+
/>
|
|
215
|
+
) : (
|
|
216
|
+
<Group>
|
|
217
|
+
<Rect
|
|
218
|
+
x={0}
|
|
219
|
+
y={0}
|
|
220
|
+
width={areaWidth}
|
|
221
|
+
height={areaHeight}
|
|
222
|
+
color={backgroundColor}
|
|
223
|
+
/>
|
|
224
|
+
{loadedFont && textMetrics && text ? (
|
|
225
|
+
<Text
|
|
226
|
+
x={textMetrics.x}
|
|
227
|
+
y={textMetrics.y}
|
|
228
|
+
text={text}
|
|
229
|
+
color={textFontColor}
|
|
230
|
+
font={loadedFont}
|
|
231
|
+
/>
|
|
232
|
+
) : null}
|
|
233
|
+
</Group>
|
|
234
|
+
)}
|
|
235
|
+
</Mask>
|
|
236
|
+
</Canvas>
|
|
237
|
+
</GestureDetector>
|
|
238
|
+
)}
|
|
239
|
+
</View>
|
|
240
|
+
</GestureHandlerRootView>
|
|
176
241
|
);
|
|
177
242
|
};
|
|
178
243
|
|
|
@@ -180,8 +245,6 @@ const styles = StyleSheet.create({
|
|
|
180
245
|
container: {
|
|
181
246
|
position: "relative",
|
|
182
247
|
overflow: "hidden",
|
|
183
|
-
width: "100%",
|
|
184
|
-
height: "100%",
|
|
185
248
|
},
|
|
186
249
|
content: {
|
|
187
250
|
position: "absolute",
|
|
@@ -189,6 +252,7 @@ const styles = StyleSheet.create({
|
|
|
189
252
|
left: 0,
|
|
190
253
|
width: "100%",
|
|
191
254
|
height: "100%",
|
|
255
|
+
zIndex: 1,
|
|
192
256
|
},
|
|
193
257
|
canvas: {
|
|
194
258
|
position: "absolute",
|
|
@@ -196,7 +260,8 @@ const styles = StyleSheet.create({
|
|
|
196
260
|
left: 0,
|
|
197
261
|
width: "100%",
|
|
198
262
|
height: "100%",
|
|
263
|
+
zIndex: 2,
|
|
199
264
|
},
|
|
200
265
|
});
|
|
201
266
|
|
|
202
|
-
export default ScratchCard;
|
|
267
|
+
export default ScratchCard;
|