related-ui-components 1.6.2 → 1.6.3

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.
@@ -1,4 +1,9 @@
1
- import React, { useEffect, useRef, useState } from "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
- NativeTouchEvent,
29
+ LogBox, // Optional: for ignoring specific logs if needed
24
30
  } from "react-native";
25
- import { useDerivedValue, useSharedValue } from "react-native-reanimated";
26
- import { Gesture, GestureDetector } from "react-native-gesture-handler";
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 = null,
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 img = useImage(image);
60
- const font = useFont(textFont, textFontSize);
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 path = useSharedValue(Skia.Path.Make().moveTo(0,0));
80
+ const isThresholdReached = useSharedValue(false);
66
81
 
67
- //Check scratch progress
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
- if (scratchedArea / totalArea > revealThreshold && !isScratched) {
74
- setScratched(true);
75
- if (onScratched) {
76
- onScratched();
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
- useEffect(() => {console.log("HI")}, [path])
107
+ const revealCardOnJS = useCallback(() => {
108
+ setScratched(true);
109
+ onScratched?.();
110
+ }, [onScratched]);
82
111
 
83
112
  const pan = Gesture.Pan()
84
- .averageTouches(true)
85
- .maxPointers(1)
86
- .onBegin(e => {
87
- path.value.moveTo(e.x, e.y);
88
- path.value.lineTo(e.x, e.y);
89
- notifyChange(path as any);
90
- })
91
- .onChange(e => {
92
- try {
93
- // checkScratchProgress();
94
- path.value.lineTo(e.x, e.y);
95
- notifyChange(path as any);
96
- // const bounds = path.value.getBounds();
97
- // const scratchedArea = bounds.height * bounds.width;
98
- // const totalArea = areaWidth * areaHeight;
99
-
100
- // if (scratchedArea / totalArea > revealThreshold && !isScratched) {
101
- // console.log("SCRATCHED")
102
- // // setScratched(true);
103
- // // if (onScratched) {
104
- // // onScratched();
105
- // // }
106
- // }
107
- } catch (error) {
108
- console.log(error)
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
- // checkScratchProgress();
112
- })
171
+ const canRenderCanvas = isLayoutReady && areaWidth > 0 && areaHeight > 0;
113
172
 
114
173
  return (
115
- <View
116
- onLayout={(e) => {
117
- setSize([e.nativeEvent.layout.width, e.nativeEvent.layout.height]);
118
- }}
119
- style={[styles.container, style, { width, height }]}
120
- >
121
- <View style={styles.content}>{children}</View>
122
-
123
- {!isScratched && (
124
- <GestureDetector gesture={pan}>
125
- <Canvas
126
- style={styles.canvas}
127
- >
128
- <Mask
129
- mode="luminance"
130
- mask={
131
- <Group>
132
- <Rect x={0} y={0} width={1000} height={1000} color="white" />
133
- <Path
134
- path={path}
135
- color="black"
136
- style="stroke"
137
- strokeJoin="round"
138
- strokeCap="round"
139
- strokeWidth={brushStrokeWidth}
140
- />
141
- </Group>
142
- }
143
- >
144
- {img ? (
145
- <Image
146
- image={img}
147
- fit="cover"
148
- x={0}
149
- y={0}
150
- width={areaWidth}
151
- height={areaHeight}
152
- />
153
- ) : (
154
- <Group>
155
- <Rect
156
- x={0}
157
- y={0}
158
- width={areaWidth}
159
- height={areaHeight}
160
- color={backgroundColor}
161
- />
162
- <Text
163
- x={areaWidth / 2 - (font?.measureText(text).width || 0) / 2}
164
- y={areaHeight / 2 + (font?.measureText(text).height || 0) / 2}
165
- text={text}
166
- color={textFontColor}
167
- font={font}
168
- />
169
- </Group>
170
- )}
171
- </Mask>
172
- </Canvas>
173
- </GestureDetector>
174
- )}
175
- </View>
174
+ <GestureHandlerRootView>
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;