related-ui-components 1.6.1 → 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.
- package/lib/commonjs/app.js +114 -253
- package/lib/commonjs/app.js.map +1 -1
- package/lib/commonjs/components/ScratchCard/ScratchCard.js +53 -51
- 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 +54 -52
- 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 +96 -82
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScratchCard.d.ts","sourceRoot":"","sources":["../../../../../src/components/ScratchCard/ScratchCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAKN,MAAM,OAAO,CAAC;AAiBf,OAAO,EACL,SAAS,EAET,SAAS,EAET,kBAAkB,EAEnB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"ScratchCard.d.ts","sourceRoot":"","sources":["../../../../../src/components/ScratchCard/ScratchCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAKN,MAAM,OAAO,CAAC;AAiBf,OAAO,EACL,SAAS,EAET,SAAS,EAET,kBAAkB,EAEnB,MAAM,cAAc,CAAC;AAYtB,KAAK,gBAAgB,GAAG;IACtB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B,CAAC;AAEF,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAwL3C,CAAC;AAyBF,eAAe,WAAW,CAAC"}
|
package/package.json
CHANGED
package/src/app.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
ScrollView,
|
|
8
8
|
FlatList,
|
|
9
9
|
Alert,
|
|
10
|
+
Modal, // Import Modal
|
|
10
11
|
} from "react-native";
|
|
11
12
|
import { Ionicons } from "@expo/vector-icons";
|
|
12
13
|
import { RelatedProvider, useTheme } from "./theme";
|
|
@@ -15,212 +16,91 @@ import {
|
|
|
15
16
|
Filters,
|
|
16
17
|
Popup,
|
|
17
18
|
RedemptionOption,
|
|
18
|
-
ScratchCard,
|
|
19
|
+
ScratchCard, // Your component
|
|
19
20
|
ScratchCardContent,
|
|
20
21
|
UnlockRewards,
|
|
21
22
|
} from "./components";
|
|
22
23
|
import { SafeAreaView } from "react-native-safe-area-context";
|
|
23
|
-
|
|
24
|
-
import
|
|
25
|
-
import SummaryBar from "./components/TravelBooking/SummaryBar";
|
|
26
|
-
import NumericStepper from "./components/NumericStepper/NumericStepper";
|
|
27
|
-
import FlightSummaryBar from "./components/TravelBooking/FlightSummary";
|
|
28
|
-
import HotelSummaryBar from "./components/TravelBooking/HotelSummary";
|
|
29
|
-
import TabSelector from "./components/TravelBooking/TabSelector";
|
|
30
|
-
import FlightForm from "./components/TravelBooking/FlightForm";
|
|
31
|
-
import DateRangePicker from "./components/DateRangePicker/DateRangePicker";
|
|
32
|
-
import HotelForm from "./components/TravelBooking/HotelForm";
|
|
33
|
-
import CarRentalForm from "./components/TravelBooking/CarRentalForm";
|
|
34
|
-
import TravelBooking from "./components/TravelBooking/TravelBooking";
|
|
35
|
-
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
36
|
-
// import BRANDS from "./constants/BRANDS";
|
|
24
|
+
// ... other imports
|
|
25
|
+
import { GestureHandlerRootView } from "react-native-gesture-handler"; // Ensure import
|
|
37
26
|
|
|
38
27
|
export default function App() {
|
|
39
28
|
const { theme } = useTheme();
|
|
40
|
-
|
|
41
|
-
const [mode, setMode] = useState<"flight" | "hotel">("flight");
|
|
29
|
+
// ... other state and constants
|
|
42
30
|
|
|
43
|
-
//
|
|
44
|
-
const
|
|
45
|
-
{ id: 1, name: "Price: Low to High", value: "price_asc" },
|
|
46
|
-
{ id: 2, name: "Price: High to Low", value: "price_desc" },
|
|
47
|
-
{ id: 3, name: "Newest", value: "newest" },
|
|
48
|
-
];
|
|
49
|
-
|
|
50
|
-
const iconSize = 24;
|
|
51
|
-
const iconColor = theme.onSurface;
|
|
52
|
-
|
|
53
|
-
const redemptionOption = [
|
|
54
|
-
{
|
|
55
|
-
icon: (
|
|
56
|
-
<Ionicons name="bag-handle-outline" size={iconSize} color={iconColor} />
|
|
57
|
-
),
|
|
58
|
-
key: "redemptionOptions.onlineShopping", // Use translation key
|
|
59
|
-
orientation: "horizontal",
|
|
60
|
-
width: 150,
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
icon: (
|
|
64
|
-
<Ionicons name="ticket-outline" size={iconSize} color={iconColor} />
|
|
65
|
-
),
|
|
66
|
-
key: "redemptionOptions.eVouchers", // Use translation key
|
|
67
|
-
width: 105,
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
icon: <Ionicons name="cash-outline" size={iconSize} color={iconColor} />,
|
|
71
|
-
key: "redemptionOptions.cashback", // Use translation key
|
|
72
|
-
width: 105,
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
icon: <Ionicons name="heart-outline" size={iconSize} color={iconColor} />,
|
|
76
|
-
key: "redemptionOptions.donate", // Use translation key
|
|
77
|
-
width: 87,
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
icon: (
|
|
81
|
-
<Ionicons name="airplane-outline" size={iconSize} color={iconColor} />
|
|
82
|
-
),
|
|
83
|
-
key: "redemptionOptions.travel", // Use translation key
|
|
84
|
-
width: 87,
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
icon: (
|
|
88
|
-
<Ionicons
|
|
89
|
-
name="swap-horizontal-outline"
|
|
90
|
-
size={iconSize}
|
|
91
|
-
color={iconColor}
|
|
92
|
-
/>
|
|
93
|
-
),
|
|
94
|
-
key: "redemptionOptions.pointsExchange", // Use translation key
|
|
95
|
-
width: 87,
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
icon: <Ionicons name="send-outline" size={iconSize} color={iconColor} />,
|
|
99
|
-
key: "redemptionOptions.transferPoints", // Use translation key
|
|
100
|
-
width: 87,
|
|
101
|
-
},
|
|
102
|
-
];
|
|
103
|
-
|
|
104
|
-
const redemptionOptions = redemptionOption.map((item) => ({
|
|
105
|
-
...item,
|
|
106
|
-
text: "Online shopping",
|
|
107
|
-
}));
|
|
108
|
-
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
|
|
109
|
-
|
|
110
|
-
const rewardsData = [
|
|
111
|
-
{
|
|
112
|
-
icon: <Ionicons name="water-outline" size={24} color={theme.helper} />,
|
|
113
|
-
activeIcon: <Ionicons name="water" size={24} color={theme.primary} />,
|
|
114
|
-
title: "Aqua Guardian",
|
|
115
|
-
description:
|
|
116
|
-
"Maintain water usage below the community average for a month.",
|
|
117
|
-
isActive: false,
|
|
118
|
-
status: "0/1",
|
|
119
|
-
statusBackgroundColor: theme.disabled,
|
|
120
|
-
statusTextColor: theme.text,
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
icon: <Ionicons name="build-outline" size={24} color={theme.helper} />,
|
|
124
|
-
activeIcon: <Ionicons name="build" size={24} color={theme.secondary} />,
|
|
125
|
-
title: "Leak Detective",
|
|
126
|
-
description: "Successfully report and fix a water leak on your property.",
|
|
127
|
-
isActive: true,
|
|
128
|
-
status: "1/1",
|
|
129
|
-
statusBackgroundColor: theme.primary,
|
|
130
|
-
statusTextColor: theme.secondary,
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
icon: <Ionicons name="leaf-outline" size={24} color={theme.helper} />,
|
|
134
|
-
activeIcon: <Ionicons name="leaf" size={24} color={theme.primary} />,
|
|
135
|
-
title: "Eco-Warrior",
|
|
136
|
-
description:
|
|
137
|
-
"Implement a rainwater harvesting system or greywater recycling.",
|
|
138
|
-
isActive: false,
|
|
139
|
-
status: "0/1",
|
|
140
|
-
statusBackgroundColor: theme.disabled,
|
|
141
|
-
statusTextColor: theme.text,
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
icon: <Ionicons name="leaf-outline" size={24} color={theme.helper} />,
|
|
145
|
-
activeIcon: <Ionicons name="leaf" size={24} color={theme.primary} />,
|
|
146
|
-
title: "Eco-Warrior",
|
|
147
|
-
description:
|
|
148
|
-
"Implement a rainwater harvesting system or greywater recycling.",
|
|
149
|
-
isActive: false,
|
|
150
|
-
status: "0/1",
|
|
151
|
-
statusBackgroundColor: theme.disabled,
|
|
152
|
-
statusTextColor: theme.text,
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
icon: <Ionicons name="leaf-outline" size={24} color={theme.helper} />,
|
|
156
|
-
activeIcon: <Ionicons name="leaf" size={24} color={theme.primary} />,
|
|
157
|
-
title: "Eco-Warrior",
|
|
158
|
-
description:
|
|
159
|
-
"Implement a rainwater harvesting system or greywater recycling.",
|
|
160
|
-
isActive: false,
|
|
161
|
-
status: "0/1",
|
|
162
|
-
statusBackgroundColor: theme.disabled,
|
|
163
|
-
statusTextColor: theme.text,
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
icon: <Ionicons name="leaf-outline" size={24} color={theme.helper} />,
|
|
167
|
-
activeIcon: <Ionicons name="leaf" size={24} color={theme.primary} />,
|
|
168
|
-
title: "Eco-Warrior",
|
|
169
|
-
description:
|
|
170
|
-
"Implement a rainwater harvesting system or greywater recycling.",
|
|
171
|
-
isActive: false,
|
|
172
|
-
status: "0/1",
|
|
173
|
-
statusBackgroundColor: theme.disabled,
|
|
174
|
-
statusTextColor: theme.text,
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
icon: <Ionicons name="leaf-outline" size={24} color={theme.helper} />,
|
|
178
|
-
activeIcon: <Ionicons name="leaf" size={24} color={theme.primary} />,
|
|
179
|
-
title: "Eco-Warrior",
|
|
180
|
-
description:
|
|
181
|
-
"Implement a rainwater harvesting system or greywater recycling.",
|
|
182
|
-
isActive: false,
|
|
183
|
-
status: "0/1",
|
|
184
|
-
statusBackgroundColor: theme.disabled,
|
|
185
|
-
statusTextColor: theme.text,
|
|
186
|
-
},
|
|
187
|
-
];
|
|
31
|
+
// State to control modal visibility
|
|
32
|
+
const [isScratchModalVisible, setScratchModalVisible] = useState(false); // Example state
|
|
188
33
|
|
|
189
34
|
return (
|
|
190
|
-
|
|
35
|
+
// Root view for the main app
|
|
36
|
+
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
191
37
|
<RelatedProvider>
|
|
192
38
|
<SafeAreaView
|
|
193
|
-
style={{
|
|
39
|
+
style={{ flex: 1, padding: 10, backgroundColor: theme.background }}
|
|
194
40
|
>
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
41
|
+
{/* Example Button to open the modal */}
|
|
42
|
+
<TouchableOpacity
|
|
43
|
+
onPress={() => setScratchModalVisible(true)}
|
|
44
|
+
style={styles.testButton}
|
|
45
|
+
>
|
|
46
|
+
<Text style={styles.buttonText}>Show Scratch Card Modal</Text>
|
|
47
|
+
</TouchableOpacity>
|
|
48
|
+
|
|
49
|
+
{/* --- The Modal --- */}
|
|
50
|
+
<Modal
|
|
51
|
+
visible={isScratchModalVisible}
|
|
52
|
+
onRequestClose={() => setScratchModalVisible(false)} // Allow closing
|
|
53
|
+
transparent={true} // Example: make it transparent
|
|
54
|
+
animationType="slide" // Example animation
|
|
55
|
+
>
|
|
56
|
+
{/* Root view specifically for the modal's content */}
|
|
57
|
+
<GestureHandlerRootView style={styles.modalRootView}>
|
|
58
|
+
{/* Optional: Add a background overlay */}
|
|
59
|
+
<View style={styles.modalOverlay}>
|
|
60
|
+
{/* Center the card or position as needed */}
|
|
61
|
+
<View style={styles.modalContentContainer}>
|
|
62
|
+
<ScratchCard
|
|
63
|
+
backgroundColor="#8A2BE2"
|
|
64
|
+
text="Scratch to reveal your prize!"
|
|
65
|
+
textFontColor="#FFFFFF"
|
|
66
|
+
textFontSize={18}
|
|
67
|
+
width={300}
|
|
68
|
+
height={150}
|
|
69
|
+
onScratched={() => {
|
|
70
|
+
Alert.alert("Congratulations!", "You won a prize!");
|
|
71
|
+
// Optionally close modal after scratching
|
|
72
|
+
// setTimeout(() => setScratchModalVisible(false), 500);
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
<ScratchCardContent style={{ backgroundColor: "#FFD700" }}>
|
|
76
|
+
<Text
|
|
77
|
+
style={{
|
|
78
|
+
fontSize: 24,
|
|
79
|
+
fontWeight: "bold",
|
|
80
|
+
color: "#000",
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
50% OFF COUPON
|
|
84
|
+
</Text>
|
|
85
|
+
<Text style={{ marginTop: 8, color: "#333" }}>
|
|
86
|
+
Use code: SCRATCH50
|
|
87
|
+
</Text>
|
|
88
|
+
</ScratchCardContent>
|
|
89
|
+
</ScratchCard>
|
|
90
|
+
|
|
91
|
+
{/* Button to close the modal */}
|
|
92
|
+
<TouchableOpacity
|
|
93
|
+
onPress={() => setScratchModalVisible(false)}
|
|
94
|
+
style={styles.closeButton}
|
|
95
|
+
>
|
|
96
|
+
<Text style={styles.buttonText}>Close</Text>
|
|
97
|
+
</TouchableOpacity>
|
|
98
|
+
</View>
|
|
99
|
+
</View>
|
|
100
|
+
</GestureHandlerRootView>
|
|
101
|
+
</Modal>
|
|
102
|
+
|
|
103
|
+
{/* Other app content can go here */}
|
|
224
104
|
</SafeAreaView>
|
|
225
105
|
</RelatedProvider>
|
|
226
106
|
</GestureHandlerRootView>
|
|
@@ -228,17 +108,14 @@ export default function App() {
|
|
|
228
108
|
}
|
|
229
109
|
|
|
230
110
|
const styles = StyleSheet.create({
|
|
231
|
-
|
|
232
|
-
flex: 1,
|
|
233
|
-
backgroundColor: "black",
|
|
234
|
-
padding: 10,
|
|
235
|
-
},
|
|
111
|
+
// ... your existing styles
|
|
236
112
|
testButton: {
|
|
237
113
|
backgroundColor: "#4a90e2",
|
|
238
114
|
paddingVertical: 12,
|
|
239
115
|
paddingHorizontal: 30,
|
|
240
116
|
borderRadius: 8,
|
|
241
117
|
marginBottom: 20,
|
|
118
|
+
alignSelf: "center",
|
|
242
119
|
},
|
|
243
120
|
buttonText: {
|
|
244
121
|
color: "#FFFFFF",
|
|
@@ -246,4 +123,28 @@ const styles = StyleSheet.create({
|
|
|
246
123
|
fontWeight: "bold",
|
|
247
124
|
textAlign: "center",
|
|
248
125
|
},
|
|
126
|
+
// Styles for the Modal
|
|
127
|
+
modalRootView: {
|
|
128
|
+
flex: 1, // Important for the root view to take up space
|
|
129
|
+
},
|
|
130
|
+
modalOverlay: {
|
|
131
|
+
flex: 1,
|
|
132
|
+
backgroundColor: "rgba(0, 0, 0, 0.5)", // Semi-transparent background
|
|
133
|
+
justifyContent: "center", // Center content vertically
|
|
134
|
+
alignItems: "center", // Center content horizontally
|
|
135
|
+
},
|
|
136
|
+
modalContentContainer: {
|
|
137
|
+
// Optional: Add padding or background to the content area if needed
|
|
138
|
+
// backgroundColor: 'white',
|
|
139
|
+
// padding: 20,
|
|
140
|
+
// borderRadius: 10,
|
|
141
|
+
alignItems: "center", // Center items like the close button
|
|
142
|
+
},
|
|
143
|
+
closeButton: {
|
|
144
|
+
marginTop: 20,
|
|
145
|
+
backgroundColor: "#d9534f",
|
|
146
|
+
paddingVertical: 10,
|
|
147
|
+
paddingHorizontal: 25,
|
|
148
|
+
borderRadius: 8,
|
|
149
|
+
},
|
|
249
150
|
});
|
|
@@ -30,7 +30,11 @@ import {
|
|
|
30
30
|
} from "react-native";
|
|
31
31
|
// Import runOnJS and useSharedValue
|
|
32
32
|
import { runOnJS, useSharedValue } from "react-native-reanimated";
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
Gesture,
|
|
35
|
+
GestureDetector,
|
|
36
|
+
GestureHandlerRootView,
|
|
37
|
+
} from "react-native-gesture-handler";
|
|
34
38
|
|
|
35
39
|
// Ignore specific warning if it appears, related to path mutation - use cautiously
|
|
36
40
|
// LogBox.ignoreLogs(['Skia: SkPath.Make()']);
|
|
@@ -48,7 +52,7 @@ type ScratchCardProps = {
|
|
|
48
52
|
textFont?: ImageRequireSource;
|
|
49
53
|
textFontSize?: number;
|
|
50
54
|
textFontColor?: string;
|
|
51
|
-
onScratched?: () => void;
|
|
55
|
+
onScratched?: () => void;
|
|
52
56
|
};
|
|
53
57
|
|
|
54
58
|
const ScratchCard: React.FC<ScratchCardProps> = ({
|
|
@@ -56,7 +60,7 @@ const ScratchCard: React.FC<ScratchCardProps> = ({
|
|
|
56
60
|
children,
|
|
57
61
|
image,
|
|
58
62
|
brushStrokeWidth = 50,
|
|
59
|
-
revealThreshold = 0.8,
|
|
63
|
+
revealThreshold = 0.8,
|
|
60
64
|
width = 300,
|
|
61
65
|
height = 300,
|
|
62
66
|
backgroundColor = "#CCCCCC",
|
|
@@ -78,29 +82,32 @@ const ScratchCard: React.FC<ScratchCardProps> = ({
|
|
|
78
82
|
const path = useSharedValue<SkPath>(Skia.Path.Make());
|
|
79
83
|
|
|
80
84
|
useEffect(() => {
|
|
81
|
-
path.value = Skia.Path.Make();
|
|
82
|
-
isThresholdReached.value = false
|
|
83
|
-
setScratched(false);
|
|
85
|
+
path.value = Skia.Path.Make();
|
|
86
|
+
isThresholdReached.value = false;
|
|
87
|
+
setScratched(false);
|
|
84
88
|
}, [areaWidth, areaHeight]);
|
|
85
89
|
|
|
86
|
-
const handleLayout = useCallback(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (newWidth
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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);
|
|
94
102
|
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}, [areaWidth, areaHeight, isLayoutReady]);
|
|
103
|
+
},
|
|
104
|
+
[areaWidth, areaHeight, isLayoutReady]
|
|
105
|
+
);
|
|
99
106
|
|
|
100
107
|
const revealCardOnJS = useCallback(() => {
|
|
101
108
|
setScratched(true);
|
|
102
|
-
onScratched?.();
|
|
103
|
-
}, [onScratched]);
|
|
109
|
+
onScratched?.();
|
|
110
|
+
}, [onScratched]);
|
|
104
111
|
|
|
105
112
|
const pan = Gesture.Pan()
|
|
106
113
|
.averageTouches(true)
|
|
@@ -144,14 +151,19 @@ const ScratchCard: React.FC<ScratchCardProps> = ({
|
|
|
144
151
|
} catch (error) {
|
|
145
152
|
console.error("ScratchCard: Error in onChange (UI Thread):", error);
|
|
146
153
|
}
|
|
147
|
-
})
|
|
154
|
+
});
|
|
148
155
|
|
|
149
156
|
const textMetrics = React.useMemo(() => {
|
|
150
157
|
if (loadedFont && text && areaWidth > 0 && areaHeight > 0) {
|
|
151
158
|
const metrics = loadedFont.measureText(text);
|
|
152
159
|
const textX = areaWidth / 2 - metrics.width / 2;
|
|
153
160
|
const textY = areaHeight / 2 + metrics.height / 3;
|
|
154
|
-
return {
|
|
161
|
+
return {
|
|
162
|
+
x: textX,
|
|
163
|
+
y: textY,
|
|
164
|
+
width: metrics.width,
|
|
165
|
+
height: metrics.height,
|
|
166
|
+
};
|
|
155
167
|
}
|
|
156
168
|
return null;
|
|
157
169
|
}, [loadedFont, text, areaWidth, areaHeight]);
|
|
@@ -159,71 +171,73 @@ const ScratchCard: React.FC<ScratchCardProps> = ({
|
|
|
159
171
|
const canRenderCanvas = isLayoutReady && areaWidth > 0 && areaHeight > 0;
|
|
160
172
|
|
|
161
173
|
return (
|
|
162
|
-
<
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
<
|
|
171
|
-
<
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
<
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
y={0}
|
|
199
|
-
width={areaWidth}
|
|
200
|
-
height={areaHeight}
|
|
201
|
-
/>
|
|
202
|
-
) : (
|
|
203
|
-
<Group>
|
|
204
|
-
<Rect
|
|
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"
|
|
205
210
|
x={0}
|
|
206
211
|
y={0}
|
|
207
212
|
width={areaWidth}
|
|
208
213
|
height={areaHeight}
|
|
209
|
-
color={backgroundColor}
|
|
210
214
|
/>
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
215
|
+
) : (
|
|
216
|
+
<Group>
|
|
217
|
+
<Rect
|
|
218
|
+
x={0}
|
|
219
|
+
y={0}
|
|
220
|
+
width={areaWidth}
|
|
221
|
+
height={areaHeight}
|
|
222
|
+
color={backgroundColor}
|
|
218
223
|
/>
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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>
|
|
227
241
|
);
|
|
228
242
|
};
|
|
229
243
|
|
|
@@ -238,7 +252,7 @@ const styles = StyleSheet.create({
|
|
|
238
252
|
left: 0,
|
|
239
253
|
width: "100%",
|
|
240
254
|
height: "100%",
|
|
241
|
-
zIndex: 1,
|
|
255
|
+
zIndex: 1,
|
|
242
256
|
},
|
|
243
257
|
canvas: {
|
|
244
258
|
position: "absolute",
|