react-native-seel-widget 0.1.0 → 0.1.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/package.json +1 -1
- package/src/assets/images/accredited.png +0 -0
- package/src/assets/images/background_image.jpg +0 -0
- package/src/assets/images/button_close.png +0 -0
- package/src/assets/images/button_close_background.png +0 -0
- package/src/assets/images/checkbox_normal.png +0 -0
- package/src/assets/images/checkbox_selected.png +0 -0
- package/src/assets/images/close_white.png +0 -0
- package/src/assets/images/ic_launcher.png +0 -0
- package/src/assets/images/icon_arrow_left.png +0 -0
- package/src/assets/images/icon_bg_select.png +0 -0
- package/src/assets/images/icon_check_selected_black.png +0 -0
- package/src/assets/images/icon_select.png +0 -0
- package/src/assets/images/info_black.png +0 -0
- package/src/assets/images/seel_icon.png +0 -0
- package/src/assets/images/seel_logo.png +0 -0
- package/src/assets/images/seel_word.png +0 -0
- package/src/assets/images/tick_small_minor.png +0 -0
- package/src/constants/key_value.ts +48 -0
- package/src/constants/network_request_statue_enum.ts +13 -0
- package/src/core/SeelWidgetSDK.ts +103 -0
- package/src/dto/EventsRequest.ts +71 -0
- package/src/dto/EventsResponse.ts +13 -0
- package/src/dto/IEvents.ts +51 -0
- package/src/dto/IQuotes.ts +36 -0
- package/src/dto/IQuotesRequest.ts +220 -0
- package/src/dto/IQuotesResponse.ts +111 -0
- package/src/network/request.ts +214 -0
- package/src/ui/coverage-info-footer.tsx +186 -0
- package/src/ui/gradient-animation-text.tsx +185 -0
- package/src/ui/gradient-animation-view.tsx +150 -0
- package/src/ui/index.ts +4 -0
- package/src/ui/seel-wfp-info-view.tsx +351 -0
- package/src/ui/seel-wfp-title-view.tsx +388 -0
- package/src/ui/seel-wfp-widget.tsx +270 -0
- package/src/utils/format_util.ts +117 -0
- package/src/utils/http_util.ts +30 -0
- package/src/utils/storage_util.ts +42 -0
- package/src/utils/uuid.ts +18 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { IShippingOrigin, IShippingAddress } from './IQuotes';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Quote status type
|
|
5
|
+
*/
|
|
6
|
+
export type QuoteStatus = 'accepted' | 'rejected';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Customer information for quotes response
|
|
10
|
+
*/
|
|
11
|
+
export interface IQuotesResponseCustomer {
|
|
12
|
+
customer_id: string;
|
|
13
|
+
email: string;
|
|
14
|
+
first_name: string;
|
|
15
|
+
last_name: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface II18N {
|
|
19
|
+
lang: string;
|
|
20
|
+
texts: Map<string, string>[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Extra information for quotes response
|
|
25
|
+
*/
|
|
26
|
+
export interface IQuotesResponseExtraInfo {
|
|
27
|
+
coverage_details_text?: string[];
|
|
28
|
+
display_widget_text?: string[];
|
|
29
|
+
opt_out_warning_text?: string;
|
|
30
|
+
privacy_policy_url?: string;
|
|
31
|
+
shipping_fee?: number;
|
|
32
|
+
terms_url?: string;
|
|
33
|
+
widget_title?: string;
|
|
34
|
+
i18n?: II18N;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Line item extra info (for nested structure)
|
|
39
|
+
*/
|
|
40
|
+
export interface IQuotesResponseLineItemExtraInfo {
|
|
41
|
+
[key: string]: unknown;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Line item information for quotes response
|
|
46
|
+
*/
|
|
47
|
+
export interface IQuotesResponseLineItem {
|
|
48
|
+
allocated_discounts?: number;
|
|
49
|
+
brand_name?: string;
|
|
50
|
+
category_1?: string;
|
|
51
|
+
category_2?: string;
|
|
52
|
+
category_3?: string;
|
|
53
|
+
category_4?: string;
|
|
54
|
+
condition?: string;
|
|
55
|
+
currency?: string;
|
|
56
|
+
extra_info?: IQuotesResponseLineItemExtraInfo;
|
|
57
|
+
final_price?: string;
|
|
58
|
+
image_url?: string;
|
|
59
|
+
is_final_sale?: boolean;
|
|
60
|
+
line_item_id?: string;
|
|
61
|
+
price?: number;
|
|
62
|
+
product_attributes?: string;
|
|
63
|
+
product_description?: string;
|
|
64
|
+
product_id?: string;
|
|
65
|
+
product_title?: string;
|
|
66
|
+
product_url?: string;
|
|
67
|
+
quantity?: number;
|
|
68
|
+
requires_shipping?: boolean;
|
|
69
|
+
retail_price?: number;
|
|
70
|
+
sales_tax?: number;
|
|
71
|
+
seller_id?: string;
|
|
72
|
+
seller_name?: string;
|
|
73
|
+
shipping_fee?: number;
|
|
74
|
+
shipping_origin?: IShippingOrigin;
|
|
75
|
+
sku?: string;
|
|
76
|
+
variant_id?: string;
|
|
77
|
+
variant_title?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Quotes Response interface
|
|
82
|
+
* Fields are ordered logically: identifiers, metadata, configuration, data, status
|
|
83
|
+
*/
|
|
84
|
+
export default interface IQuotesResponse {
|
|
85
|
+
// Identifiers
|
|
86
|
+
quote_id: string;
|
|
87
|
+
cart_id: string;
|
|
88
|
+
merchant_id: string;
|
|
89
|
+
device_id: string;
|
|
90
|
+
session_id: string;
|
|
91
|
+
|
|
92
|
+
// Metadata
|
|
93
|
+
created_ts: string;
|
|
94
|
+
status: string;
|
|
95
|
+
|
|
96
|
+
// Configuration
|
|
97
|
+
type: string;
|
|
98
|
+
device_category: string;
|
|
99
|
+
device_platform: string;
|
|
100
|
+
is_default_on: boolean;
|
|
101
|
+
|
|
102
|
+
// Financial data
|
|
103
|
+
currency: string;
|
|
104
|
+
price: number;
|
|
105
|
+
|
|
106
|
+
// Data
|
|
107
|
+
line_items?: IQuotesResponseLineItem[];
|
|
108
|
+
customer?: IQuotesResponseCustomer;
|
|
109
|
+
shipping_address?: IShippingAddress;
|
|
110
|
+
extra_info?: IQuotesResponseExtraInfo;
|
|
111
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { SeelWidgetSDK } from '../core/SeelWidgetSDK';
|
|
2
|
+
|
|
3
|
+
export interface RequestHeaders {
|
|
4
|
+
'X-Seel-API-Key': string;
|
|
5
|
+
'X-Seel-API-Version': string;
|
|
6
|
+
[key: string]: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface RequestOptions {
|
|
10
|
+
headers?: RequestHeaders;
|
|
11
|
+
timeout?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface GetRequestParams {
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const defaultXSeelApiKey: string = SeelWidgetSDK.shared.apiKey || '';
|
|
19
|
+
const defaultXSeelApiVersion: string = SeelWidgetSDK.shared.apiVersion || '';
|
|
20
|
+
const defaultRequestTimeout: number = SeelWidgetSDK.shared.requestTimeout;
|
|
21
|
+
|
|
22
|
+
class Request {
|
|
23
|
+
private defaultHeaders: RequestHeaders = {
|
|
24
|
+
'Accept': 'application/json',
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
'X-Seel-API-Key': defaultXSeelApiKey,
|
|
27
|
+
'X-Seel-API-Version': defaultXSeelApiVersion,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
private defaultTimeout: number = defaultRequestTimeout;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Set default request headers
|
|
34
|
+
*/
|
|
35
|
+
setDefaultHeaders(headers: RequestHeaders): void {
|
|
36
|
+
this.defaultHeaders = { ...this.defaultHeaders, ...headers };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Set default timeout
|
|
41
|
+
*/
|
|
42
|
+
setDefaultTimeout(timeout: number): void {
|
|
43
|
+
this.defaultTimeout = timeout;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Build query string
|
|
48
|
+
*/
|
|
49
|
+
private buildQueryString(params: GetRequestParams): string {
|
|
50
|
+
const queryParams = new URLSearchParams();
|
|
51
|
+
Object.keys(params).forEach((key) => {
|
|
52
|
+
const value = params[key];
|
|
53
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
54
|
+
queryParams.append(key, String(value));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return queryParams.toString();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Handle request timeout
|
|
63
|
+
*/
|
|
64
|
+
private createTimeoutPromise(timeout: number): Promise<never> {
|
|
65
|
+
return new Promise((_, reject) => {
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
reject(new Error(`Request timeout after ${timeout}ms`));
|
|
68
|
+
}, timeout);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* GET request
|
|
74
|
+
* @param url Request URL
|
|
75
|
+
* @param params Request parameters
|
|
76
|
+
* @param options Request options (headers, timeout, etc.)
|
|
77
|
+
* @returns Promise<any>
|
|
78
|
+
*/
|
|
79
|
+
async get<T = any>(
|
|
80
|
+
url: string,
|
|
81
|
+
params?: GetRequestParams,
|
|
82
|
+
options?: RequestOptions
|
|
83
|
+
): Promise<T> {
|
|
84
|
+
try {
|
|
85
|
+
// Build full URL (including query parameters)
|
|
86
|
+
let fullUrl = url;
|
|
87
|
+
if (params && Object.keys(params).length > 0) {
|
|
88
|
+
const queryString = this.buildQueryString(params);
|
|
89
|
+
fullUrl = `${url}${url.includes('?') ? '&' : '?'}${queryString}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Merge request headers
|
|
93
|
+
const headers = {
|
|
94
|
+
...this.defaultHeaders,
|
|
95
|
+
...options?.headers,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Create request configuration
|
|
99
|
+
const requestOptions: RequestInit = {
|
|
100
|
+
method: 'GET',
|
|
101
|
+
headers,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Handle timeout
|
|
105
|
+
const timeout = options?.timeout || this.defaultTimeout;
|
|
106
|
+
const timeoutPromise = this.createTimeoutPromise(timeout);
|
|
107
|
+
|
|
108
|
+
// Send request
|
|
109
|
+
const fetchPromise = fetch(fullUrl, requestOptions);
|
|
110
|
+
|
|
111
|
+
// Use Promise.race to handle timeout
|
|
112
|
+
const response = await Promise.race([fetchPromise, timeoutPromise]);
|
|
113
|
+
|
|
114
|
+
// Check response status
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Parse response data
|
|
120
|
+
const data = await response.json();
|
|
121
|
+
return data as T;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
// Unified error handling
|
|
124
|
+
if (error instanceof Error) {
|
|
125
|
+
throw new Error(`GET request failed: ${error.message}`);
|
|
126
|
+
}
|
|
127
|
+
throw new Error('GET request failed: Unknown error');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* POST request
|
|
133
|
+
* @param url Request URL
|
|
134
|
+
* @param data Request body data
|
|
135
|
+
* @param options Request options (headers, timeout, etc.)
|
|
136
|
+
* @returns Promise<any>
|
|
137
|
+
*/
|
|
138
|
+
async post<T = any>(
|
|
139
|
+
url: string,
|
|
140
|
+
data?: any,
|
|
141
|
+
options?: RequestOptions
|
|
142
|
+
): Promise<T> {
|
|
143
|
+
try {
|
|
144
|
+
// Merge request headers
|
|
145
|
+
const headers = {
|
|
146
|
+
...this.defaultHeaders,
|
|
147
|
+
...options?.headers,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Build request body
|
|
151
|
+
let body: string | undefined;
|
|
152
|
+
if (data !== undefined && data !== null) {
|
|
153
|
+
// If data is already a string, use it directly
|
|
154
|
+
if (typeof data === 'string') {
|
|
155
|
+
body = data;
|
|
156
|
+
} else {
|
|
157
|
+
// Otherwise, serialize to JSON
|
|
158
|
+
body = JSON.stringify(data);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Create request configuration
|
|
163
|
+
const requestOptions: RequestInit = {
|
|
164
|
+
method: 'POST',
|
|
165
|
+
headers,
|
|
166
|
+
...(body && { body }),
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Handle timeout
|
|
170
|
+
const timeout = options?.timeout || this.defaultTimeout;
|
|
171
|
+
const timeoutPromise = this.createTimeoutPromise(timeout);
|
|
172
|
+
|
|
173
|
+
// Send request
|
|
174
|
+
const fetchPromise = fetch(url, requestOptions);
|
|
175
|
+
|
|
176
|
+
// Use Promise.race to handle timeout
|
|
177
|
+
const response = await Promise.race([fetchPromise, timeoutPromise]);
|
|
178
|
+
// Check response status
|
|
179
|
+
if (!response.ok) {
|
|
180
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Parse response data
|
|
184
|
+
const responseData = await response.json();
|
|
185
|
+
return responseData as T;
|
|
186
|
+
} catch (error) {
|
|
187
|
+
// Unified error handling
|
|
188
|
+
if (error instanceof Error) {
|
|
189
|
+
throw new Error(`POST request failed: ${error.message}`);
|
|
190
|
+
}
|
|
191
|
+
throw new Error('POST request failed: Unknown error');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Export singleton instance
|
|
197
|
+
export const instance = new Request();
|
|
198
|
+
|
|
199
|
+
// Export convenience methods
|
|
200
|
+
export const get = <T = any>(
|
|
201
|
+
url: string,
|
|
202
|
+
params?: GetRequestParams,
|
|
203
|
+
options?: RequestOptions
|
|
204
|
+
): Promise<T> => {
|
|
205
|
+
return instance.get<T>(url, params, options);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export const post = <T = any>(
|
|
209
|
+
url: string,
|
|
210
|
+
data?: any,
|
|
211
|
+
options?: RequestOptions
|
|
212
|
+
): Promise<T> => {
|
|
213
|
+
return instance.post<T>(url, data, options);
|
|
214
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Image,
|
|
3
|
+
Linking,
|
|
4
|
+
Platform,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Text,
|
|
7
|
+
TouchableOpacity,
|
|
8
|
+
useColorScheme,
|
|
9
|
+
View,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
import KeyValue from '../constants/key_value';
|
|
12
|
+
|
|
13
|
+
interface CoverageInfoFooterProps {
|
|
14
|
+
termsUrl: string;
|
|
15
|
+
privacyPolicyUrl: string;
|
|
16
|
+
dictionary: any;
|
|
17
|
+
onChangeOptedInValue: (value: boolean) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default function CoverageInfoFooter({
|
|
21
|
+
termsUrl = '',
|
|
22
|
+
privacyPolicyUrl = '',
|
|
23
|
+
dictionary = {},
|
|
24
|
+
onChangeOptedInValue = (_: boolean) => {},
|
|
25
|
+
}: CoverageInfoFooterProps) {
|
|
26
|
+
const colorScheme = useColorScheme();
|
|
27
|
+
const isDark = false && colorScheme === 'dark';
|
|
28
|
+
|
|
29
|
+
const _poweredByText = [
|
|
30
|
+
defaultStyles.poweredByText,
|
|
31
|
+
isDark ? defaultStyles.dartPoweredByText : defaultStyles.lightPoweredByText,
|
|
32
|
+
];
|
|
33
|
+
return (
|
|
34
|
+
<View style={[defaultStyles.container]}>
|
|
35
|
+
<TouchableOpacity
|
|
36
|
+
style={defaultStyles.optedInButton}
|
|
37
|
+
onPress={() => {
|
|
38
|
+
onChangeOptedInValue(true);
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
<Text style={defaultStyles.optedInButtonTitle}>
|
|
42
|
+
{dictionary[KeyValue.cta_secure_purchase] ??
|
|
43
|
+
'Secure Your Purchase Now'}
|
|
44
|
+
</Text>
|
|
45
|
+
</TouchableOpacity>
|
|
46
|
+
<TouchableOpacity
|
|
47
|
+
style={defaultStyles.noNeedButton}
|
|
48
|
+
hitSlop={{ top: 8, bottom: 8 }}
|
|
49
|
+
onPress={() => {
|
|
50
|
+
onChangeOptedInValue(false);
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<Text style={defaultStyles.noNeedButtonTitle}>
|
|
54
|
+
{dictionary[KeyValue.cta_continue_without] ??
|
|
55
|
+
'Continue Without Protection'}
|
|
56
|
+
</Text>
|
|
57
|
+
</TouchableOpacity>
|
|
58
|
+
<View style={defaultStyles.horizontalStackView}>
|
|
59
|
+
<TouchableOpacity
|
|
60
|
+
style={defaultStyles.privacyPolicyButton}
|
|
61
|
+
hitSlop={{ top: 14 }}
|
|
62
|
+
onPress={() => {
|
|
63
|
+
Linking.openURL(privacyPolicyUrl);
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
<Text style={defaultStyles.privacyPolicyButtonTitle}>
|
|
67
|
+
{dictionary[KeyValue.privacy_policy] ?? 'Privacy Policy'}
|
|
68
|
+
</Text>
|
|
69
|
+
</TouchableOpacity>
|
|
70
|
+
<TouchableOpacity
|
|
71
|
+
style={defaultStyles.termsButton}
|
|
72
|
+
hitSlop={{ top: 14 }}
|
|
73
|
+
onPress={() => {
|
|
74
|
+
Linking.openURL(termsUrl);
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<Text style={defaultStyles.termsButtonTitle}>
|
|
78
|
+
{dictionary[KeyValue.terms_of_service] ?? 'Terms of Service'}
|
|
79
|
+
</Text>
|
|
80
|
+
</TouchableOpacity>
|
|
81
|
+
</View>
|
|
82
|
+
<View style={defaultStyles.poweredByContainer}>
|
|
83
|
+
<Text style={_poweredByText}>
|
|
84
|
+
{dictionary[KeyValue.powered_by] ?? 'Powered by'}
|
|
85
|
+
</Text>
|
|
86
|
+
<Image
|
|
87
|
+
style={defaultStyles.seelWordIcon}
|
|
88
|
+
source={require('../assets/images/seel_word.png')}
|
|
89
|
+
/>
|
|
90
|
+
</View>
|
|
91
|
+
</View>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const defaultStyles = StyleSheet.create({
|
|
96
|
+
container: {
|
|
97
|
+
paddingTop: 12,
|
|
98
|
+
flexDirection: 'column',
|
|
99
|
+
justifyContent: 'center',
|
|
100
|
+
alignItems: 'center',
|
|
101
|
+
},
|
|
102
|
+
optedInButton: {
|
|
103
|
+
paddingLeft: 16,
|
|
104
|
+
paddingRight: 16,
|
|
105
|
+
width: '100%',
|
|
106
|
+
height: 52,
|
|
107
|
+
borderRadius: 10,
|
|
108
|
+
justifyContent: 'center',
|
|
109
|
+
alignItems: 'center',
|
|
110
|
+
backgroundColor: '#333333',
|
|
111
|
+
},
|
|
112
|
+
optedInButtonTitle: {
|
|
113
|
+
color: '#ffffff',
|
|
114
|
+
fontSize: 14,
|
|
115
|
+
fontFamily: Platform.select({
|
|
116
|
+
ios: 'Inter',
|
|
117
|
+
}),
|
|
118
|
+
textAlign: 'center',
|
|
119
|
+
fontWeight: 600,
|
|
120
|
+
},
|
|
121
|
+
noNeedButton: {
|
|
122
|
+
marginTop: 12,
|
|
123
|
+
justifyContent: 'center',
|
|
124
|
+
alignItems: 'center',
|
|
125
|
+
},
|
|
126
|
+
noNeedButtonTitle: {
|
|
127
|
+
color: '#808692',
|
|
128
|
+
fontSize: 14,
|
|
129
|
+
fontFamily: Platform.select({
|
|
130
|
+
ios: 'Inter',
|
|
131
|
+
}),
|
|
132
|
+
textAlign: 'center',
|
|
133
|
+
fontWeight: 600,
|
|
134
|
+
},
|
|
135
|
+
horizontalStackView: {
|
|
136
|
+
width: '100%',
|
|
137
|
+
marginTop: 12,
|
|
138
|
+
padding: 14,
|
|
139
|
+
flexDirection: 'row',
|
|
140
|
+
justifyContent: 'space-around',
|
|
141
|
+
alignItems: 'center',
|
|
142
|
+
},
|
|
143
|
+
privacyPolicyButton: {
|
|
144
|
+
justifyContent: 'center',
|
|
145
|
+
alignItems: 'center',
|
|
146
|
+
},
|
|
147
|
+
privacyPolicyButtonTitle: {
|
|
148
|
+
color: '#5C5F62',
|
|
149
|
+
fontSize: 10,
|
|
150
|
+
fontWeight: 400,
|
|
151
|
+
lineHeight: 16,
|
|
152
|
+
textDecorationLine: 'underline',
|
|
153
|
+
},
|
|
154
|
+
termsButton: {
|
|
155
|
+
justifyContent: 'center',
|
|
156
|
+
alignItems: 'center',
|
|
157
|
+
},
|
|
158
|
+
termsButtonTitle: {
|
|
159
|
+
color: '#5C5F62',
|
|
160
|
+
fontSize: 10,
|
|
161
|
+
fontWeight: 400,
|
|
162
|
+
lineHeight: 16,
|
|
163
|
+
textDecorationLine: 'underline',
|
|
164
|
+
},
|
|
165
|
+
poweredByContainer: {
|
|
166
|
+
marginTop: 'auto',
|
|
167
|
+
flexDirection: 'row',
|
|
168
|
+
alignItems: 'center',
|
|
169
|
+
},
|
|
170
|
+
poweredByText: {
|
|
171
|
+
color: '#000000',
|
|
172
|
+
fontSize: 10,
|
|
173
|
+
fontWeight: 400,
|
|
174
|
+
lineHeight: 16,
|
|
175
|
+
},
|
|
176
|
+
lightPoweredByText: {
|
|
177
|
+
color: '#565656',
|
|
178
|
+
},
|
|
179
|
+
dartPoweredByText: {
|
|
180
|
+
color: 'white',
|
|
181
|
+
},
|
|
182
|
+
seelWordIcon: {
|
|
183
|
+
width: 32,
|
|
184
|
+
height: 16,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useImperativeHandle,
|
|
6
|
+
useRef,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import { Animated, StyleSheet, Text, View } from 'react-native';
|
|
9
|
+
import type { TextStyle, ViewStyle } from 'react-native';
|
|
10
|
+
|
|
11
|
+
export interface GradientAnimationTextProps {
|
|
12
|
+
/**
|
|
13
|
+
* Text content to display
|
|
14
|
+
*/
|
|
15
|
+
text?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Custom text style
|
|
18
|
+
*/
|
|
19
|
+
textStyle?: TextStyle;
|
|
20
|
+
/**
|
|
21
|
+
* Custom container style
|
|
22
|
+
*/
|
|
23
|
+
containerStyle?: ViewStyle;
|
|
24
|
+
/**
|
|
25
|
+
* Whether to start animation automatically (default: true)
|
|
26
|
+
*/
|
|
27
|
+
autoStart?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Animation duration in milliseconds for one cycle (default: 1500)
|
|
30
|
+
*/
|
|
31
|
+
animationDuration?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Gradient colors for loading effect (default: ['rgba(255,255,255,0)', 'rgba(255,255,255,0.3)', 'rgba(255,255,255,0)'])
|
|
34
|
+
*/
|
|
35
|
+
gradientColors?: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface GradientAnimationTextRef {
|
|
39
|
+
/**
|
|
40
|
+
* Start the loading animation
|
|
41
|
+
*/
|
|
42
|
+
start: () => void;
|
|
43
|
+
/**
|
|
44
|
+
* Stop the loading animation
|
|
45
|
+
*/
|
|
46
|
+
stop: () => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const GradientAnimationText = forwardRef<
|
|
50
|
+
GradientAnimationTextRef,
|
|
51
|
+
GradientAnimationTextProps
|
|
52
|
+
>(
|
|
53
|
+
(
|
|
54
|
+
{
|
|
55
|
+
text = '',
|
|
56
|
+
textStyle,
|
|
57
|
+
containerStyle,
|
|
58
|
+
autoStart = true,
|
|
59
|
+
animationDuration = 1500,
|
|
60
|
+
gradientColors = [
|
|
61
|
+
'rgba(255, 255, 255, 0.3)',
|
|
62
|
+
'rgba(255, 255, 255, 0.6)',
|
|
63
|
+
'rgba(255, 255, 255, 0.3)',
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
ref
|
|
67
|
+
) => {
|
|
68
|
+
// Use a larger range for smoother animation across the container
|
|
69
|
+
const translateX = useRef(new Animated.Value(-300)).current;
|
|
70
|
+
const animationRef = useRef<any>(null);
|
|
71
|
+
|
|
72
|
+
const startAnimation = useCallback(() => {
|
|
73
|
+
// Stop existing animation if any
|
|
74
|
+
if (animationRef.current) {
|
|
75
|
+
animationRef.current.stop();
|
|
76
|
+
}
|
|
77
|
+
// Reset position to start (off-screen left)
|
|
78
|
+
translateX.setValue(-300);
|
|
79
|
+
// Create loop animation (move from left to right)
|
|
80
|
+
animationRef.current = Animated.loop(
|
|
81
|
+
Animated.timing(translateX, {
|
|
82
|
+
toValue: 300,
|
|
83
|
+
duration: animationDuration,
|
|
84
|
+
useNativeDriver: true,
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
animationRef.current.start();
|
|
88
|
+
}, [animationDuration, translateX]);
|
|
89
|
+
|
|
90
|
+
const stopAnimation = useCallback(() => {
|
|
91
|
+
if (animationRef.current) {
|
|
92
|
+
animationRef.current.stop();
|
|
93
|
+
animationRef.current = null;
|
|
94
|
+
}
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
useImperativeHandle(ref, () => ({
|
|
98
|
+
start: startAnimation,
|
|
99
|
+
stop: stopAnimation,
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (autoStart) {
|
|
104
|
+
startAnimation();
|
|
105
|
+
}
|
|
106
|
+
return () => {
|
|
107
|
+
stopAnimation();
|
|
108
|
+
};
|
|
109
|
+
}, [autoStart, startAnimation, stopAnimation]);
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<View style={[styles.container, containerStyle]}>
|
|
113
|
+
<View style={styles.textContainer}>
|
|
114
|
+
<Text style={[styles.text, textStyle]}>{text}</Text>
|
|
115
|
+
{/* Gradient loading effect - moving from left to right */}
|
|
116
|
+
<Animated.View
|
|
117
|
+
style={[
|
|
118
|
+
styles.gradientMask,
|
|
119
|
+
{
|
|
120
|
+
transform: [{ translateX }],
|
|
121
|
+
},
|
|
122
|
+
]}
|
|
123
|
+
pointerEvents="none"
|
|
124
|
+
>
|
|
125
|
+
{/* Create gradient effect with three colors */}
|
|
126
|
+
<View style={styles.gradientOverlay}>
|
|
127
|
+
<View
|
|
128
|
+
style={[
|
|
129
|
+
styles.gradientSection,
|
|
130
|
+
{ backgroundColor: gradientColors[0] },
|
|
131
|
+
]}
|
|
132
|
+
/>
|
|
133
|
+
<View
|
|
134
|
+
style={[
|
|
135
|
+
styles.gradientSection,
|
|
136
|
+
{ backgroundColor: gradientColors[1] },
|
|
137
|
+
]}
|
|
138
|
+
/>
|
|
139
|
+
<View
|
|
140
|
+
style={[
|
|
141
|
+
styles.gradientSection,
|
|
142
|
+
{ backgroundColor: gradientColors[2] },
|
|
143
|
+
]}
|
|
144
|
+
/>
|
|
145
|
+
</View>
|
|
146
|
+
</Animated.View>
|
|
147
|
+
</View>
|
|
148
|
+
</View>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const styles = StyleSheet.create({
|
|
154
|
+
container: {
|
|
155
|
+
overflow: 'hidden',
|
|
156
|
+
},
|
|
157
|
+
textContainer: {
|
|
158
|
+
position: 'relative',
|
|
159
|
+
overflow: 'hidden',
|
|
160
|
+
},
|
|
161
|
+
text: {
|
|
162
|
+
fontSize: 16,
|
|
163
|
+
color: '#333333',
|
|
164
|
+
},
|
|
165
|
+
gradientMask: {
|
|
166
|
+
position: 'absolute',
|
|
167
|
+
top: 0,
|
|
168
|
+
left: 0,
|
|
169
|
+
bottom: 0,
|
|
170
|
+
width: 300,
|
|
171
|
+
height: '100%',
|
|
172
|
+
overflow: 'hidden',
|
|
173
|
+
},
|
|
174
|
+
gradientOverlay: {
|
|
175
|
+
flex: 1,
|
|
176
|
+
flexDirection: 'row',
|
|
177
|
+
width: 300,
|
|
178
|
+
height: '100%',
|
|
179
|
+
},
|
|
180
|
+
gradientSection: {
|
|
181
|
+
flex: 1,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
export default GradientAnimationText;
|