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