shakebug-react-native 2.4.14 → 2.4.16

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.
Files changed (32) hide show
  1. package/README.md +6 -0
  2. package/android/src/main/java/com/shakebug/ConnectivityReceiver.java +12 -7
  3. package/android/src/main/java/com/shakebug/DrawingView.java +13 -3
  4. package/android/src/main/java/com/shakebug/RecordScreenModule.java +6 -3
  5. package/android/src/main/java/com/shakebug/ViewShot.java +1 -0
  6. package/ios/CustomScreenShot/ShakebugSDKCustomScreenCapture.m +6 -2
  7. package/ios/ScreenShot/ShakebugCapture.m +3 -1
  8. package/ios/SketchCanvas/DrawingView.m +14 -1
  9. package/package.json +1 -1
  10. package/shakebug-react-native-2.4.15.tgz +0 -0
  11. package/src/APIs/MatchKey.js +1 -1
  12. package/src/Assets/images/shake.png +0 -0
  13. package/src/Assets/images/undo.png +0 -0
  14. package/src/Assets/images/undo@2x.png +0 -0
  15. package/src/Assets/images/undo@3x.png +0 -0
  16. package/src/Componanats/CustomAlert.js +8 -4
  17. package/src/Componanats/Icons.js +278 -0
  18. package/src/Componanats/KeyboardAvoidingWrapper.js +31 -39
  19. package/src/Componanats/LoadingModal.js +7 -4
  20. package/src/Componanats/NavigationContext.js +8 -1
  21. package/src/Componanats/ReviewModal.js +18 -11
  22. package/src/Componanats/SafeAreaHelper.js +70 -0
  23. package/src/Componanats/ShakeFunction.js +655 -364
  24. package/src/Componanats/ShakebugBottomSheet.js +729 -0
  25. package/src/Componanats/Theme.js +96 -0
  26. package/src/Componanats/ZoomableImage.js +350 -0
  27. package/src/Componanats/localization.js +111 -82
  28. package/src/Screens/AddDetailsScreen.js +800 -439
  29. package/src/Screens/FirstScreen.js +950 -540
  30. package/src/Screens/Shakebug.js +78 -44
  31. package/src/Screens/TrimVideoScreen.js +40 -7
  32. package/src/index.tsx +16 -7
@@ -1,475 +1,836 @@
1
- import { View, Text, TouchableOpacity, Image, TextInput, PanResponder, Alert, StyleSheet, Keyboard, LogBox, Platform, StatusBar, NativeModules, BackHandler, DeviceEventEmitter, Dimensions, SafeAreaView } from 'react-native'
2
- import React, { useState, useRef, useEffect } from 'react'
1
+ import {
2
+ View,
3
+ Text,
4
+ TouchableOpacity,
5
+ Image,
6
+ TextInput,
7
+ Alert,
8
+ StyleSheet,
9
+ Keyboard,
10
+ LogBox,
11
+ Platform,
12
+ StatusBar,
13
+ NativeModules,
14
+ BackHandler,
15
+ SafeAreaView,
16
+ ScrollView,
17
+ Dimensions,
18
+ } from 'react-native';
19
+ import React, { useState, useRef, useEffect } from 'react';
3
20
  import { launchImageLibrary } from '../../ImageOrVedioPicker/src/index';
4
21
  import { AddIssueApi } from '../APIs/AddIssue';
5
- import { logger, consoleTransport } from "react-native-logs";
6
- import VideoPlayer from '../../VideoPlayer/index'
22
+ import { logger, consoleTransport } from 'react-native-logs';
23
+ import VideoPlayer from '../../VideoPlayer/index';
7
24
  import { useNavigation } from '../Componanats/NavigationContext';
8
- import { Localization } from '../Componanats/localization';
9
- import { ActivityIndicator } from 'react-native';
10
- import { UIManager } from 'react-native';
11
- import { ScrollView } from 'react-native';
25
+ import { Localization, getLocalizedString } from '../Componanats/localization';
12
26
  import KeyboardAvoidingWrapper from '../Componanats/KeyboardAvoidingWrapper';
13
27
  import { useGlobalState } from '../Componanats/GlobalStateContext';
14
28
  import CustomeAlert from '../Componanats/CustomAlert';
15
29
  import LoadingModal from '../Componanats/LoadingModal';
16
30
  import NetInfo from '../../NetInfo/src';
17
- import DeviceInfo from '../../DeviceInfo';
18
-
19
- console.ignoredYellowBox = ["Warning: Each", "Warning: Failed", "Non-serializable"];
20
-
21
- LogBox.ignoreLogs([
22
- 'Non-serializable values were found in the navigation state',
23
- ]);
31
+ import { useTheme } from '../Componanats/Theme';
32
+ import ZoomableImage from '../Componanats/ZoomableImage';
33
+ import {
34
+ BackChevronIcon,
35
+ MailIcon,
36
+ CameraIcon,
37
+ CamcorderIcon,
38
+ PhotoGalleryIcon,
39
+ CloseCircleIcon,
40
+ } from '../Componanats/Icons';
41
+ import { getBottomSpace, getTopSpace, isIPhoneWithNotch } from '../Componanats/SafeAreaHelper';
42
+
43
+ console.ignoredYellowBox = ['Warning: Each', 'Warning: Failed', 'Non-serializable'];
44
+ LogBox.ignoreLogs(['Non-serializable values were found in the navigation state']);
24
45
 
25
46
  const { CreateThumbnail } = NativeModules;
26
47
 
27
48
  const AddDetailsScreen = ({ media, updateParent, mediaType, themeColor, SDKScreenTitle }) => {
49
+ const { setIsScreenShot, setIsRecordingStart } = useGlobalState();
50
+ const resolvedColor = (themeColor && themeColor !== '#313A46')
51
+ ? themeColor
52
+ : (global.ShakebugThemeColor || themeColor || '#313A46');
53
+ const ThemeColor = resolvedColor;
54
+ const theme = useTheme(resolvedColor);
55
+ const { goBack, navigate, goToScreenAtIndex, navigationState } = useNavigation();
56
+
57
+ const { height: screenHeight, width: screenWidth } = Dimensions.get('window');
58
+ const bottomInset = getBottomSpace();
59
+ const topSpace = getTopSpace();
60
+
61
+ const [mediaRatio, setMediaRatio] = useState(null);
62
+
63
+ useEffect(() => {
64
+ if (path && type !== 'Recordvideo') {
65
+ const uri = (Platform.OS === 'android' && typeof path === 'string' && !path.startsWith('file://') && !path.startsWith('content://') && !path.startsWith('http'))
66
+ ? `file://${path}`
67
+ : path;
68
+ Image.getSize(
69
+ uri,
70
+ (w, h) => {
71
+ if (w > 0 && h > 0) {
72
+ setMediaRatio(w / h);
73
+ }
74
+ },
75
+ () => { }
76
+ );
77
+ }
78
+ }, [path, type]);
79
+
80
+ // Manage proper dimensions for media preview and bottom anchoring
81
+ const bottomSpace = Platform.OS === 'ios' ? (isIPhoneWithNotch() ? 24 : 10) : 46;
82
+ const availableWidth = screenWidth - 32;
83
+
84
+ // Attached media card spans full width matching description and email inputs
85
+ const cardWidth = availableWidth;
86
+
87
+ const [desc, setDesc] = useState('');
88
+ const [email, setEmail] = useState('');
89
+ const [descBorder, setDescBorder] = useState('');
90
+ const [EmailBorder, setEmailBorder] = useState('');
91
+ const [path, setPath] = useState(media);
92
+ const [type, setType] = useState(mediaType);
93
+ const [isLoading, setIsLoading] = useState(false);
94
+ const [poster, setPoster] = useState('');
95
+ const title = SDKScreenTitle ? SDKScreenTitle.split(',')[1] : null;
96
+ const [isErrorInAPI, setErrorInAPI] = useState(false);
97
+ const [isBugReported, setIsBugReported] = useState(false);
98
+ const [apiMessage, setAPIMessage] = useState('');
99
+ const [isInternet, setIsInternet] = useState(null);
100
+
101
+ const descRef = useRef();
102
+
103
+ const handleBack = () => {
104
+ StatusBar.setHidden(false);
105
+ global.isScreenShotDetected = false;
106
+ setIsScreenShot(false);
107
+ setIsRecordingStart(false);
108
+
109
+ const isVideo =
110
+ mediaType === 'video' ||
111
+ mediaType === 'Recordvideo' ||
112
+ type === 'video' ||
113
+ type === 'Recordvideo';
114
+
115
+ const firstScreenIndex = navigationState
116
+ ? navigationState.findIndex((s) => s.name === 'FirstScreen')
117
+ : -1;
118
+
119
+ if (isVideo || firstScreenIndex === -1) {
120
+ // Exit SDK and return to host application
121
+ global.isTrigger = false;
122
+ global.isShakebugOpen = false;
123
+ global.isScreenShotDetected = false;
124
+ global.isScreeenShotTaken = false;
125
+ global.isShakebugSDKOpen = false;
126
+ global.IsBackground = 'true';
127
+ goToScreenAtIndex(0);
128
+ } else {
129
+ // Return to FirstScreen (annotation editor for screenshot/image)
130
+ global.IsBackground = 'false';
131
+ goToScreenAtIndex(firstScreenIndex);
132
+ }
133
+ };
28
134
 
29
- const { setIsScreenShot, setIsRecordingStart } = useGlobalState()
30
-
31
- const { goBack, navigate, goToScreenAtIndex } = useNavigation();
32
-
33
- const ThemeColor = themeColor ? themeColor : "#1dd3e3";
34
-
35
- const [desc, setDesc] = useState("")
36
- const [email, setEmail] = useState("")
37
- const [descBorder, setDescBorder] = useState("black")
38
- const [EmailBorder, setEmailBorder] = useState('black')
39
- const [path, setPath] = useState(media)
40
- const [type, setType] = useState(mediaType)
41
- const [isLoading, setIsLoading] = useState(false)
42
- const [poster, setPoster] = useState("")
43
- const title = SDKScreenTitle ? SDKScreenTitle.split(",")[1] : null
44
- const [isErrorInAPI, setErrorInAPI] = useState(false)
45
- const [isBugReported, setIsBugReported] = useState(false)
46
- const [apiMessage, setAPIMessage] = useState("")
47
- const [isInternet, setIsInternet] = useState(null)
48
-
49
- // For handel system back navigation
50
- useEffect(() => {
51
-
52
- const unsubscribeNetInfo = NetInfo.addEventListener(stat => {
53
- setIsInternet(stat.isConnected)
54
- })
55
-
56
- StatusBar.setHidden(false)
57
- const backAction = () => {
58
- global.isScreenShotDetected = false
59
- setIsScreenShot(false)
60
- setIsRecordingStart(false)
61
- if (mediaType == "Recordvideo") {
62
- global.isShakebugSDKOpen = false
63
- global.IsBackground = 'true'
64
- goToScreenAtIndex(0)
65
- }
66
- else {
67
- global.IsBackground = 'false'
68
- goToScreenAtIndex(1)
69
- }
70
-
71
- return true; // Return true to prevent default behavior (exit app)
72
- };
73
-
74
- const backHandler = BackHandler.addEventListener('hardwareBackPress', backAction);
75
-
76
- return () => {
77
- backHandler.remove();
78
- if (unsubscribeNetInfo) {
79
- unsubscribeNetInfo();
80
- }
81
- }; // Clean up the event listener on unmount
82
- }, []);
83
-
84
-
85
- useEffect(() => {
86
- if (mediaType != "img") {
87
- setIsLoading(true)
88
- generateThumbnail()
89
- }
90
- if (type == "img") {
91
- setIsLoading(false)
92
- }
93
- }, [type])
94
-
95
- // For ganrate thumbanill image of video
96
- const generateThumbnail = async () => {
97
- if (type != "img") {
98
- try {
99
-
100
- const { create: createThumbnail } = CreateThumbnail;
101
- const cleanVideoPath = path ? path.replace('file://', '') : '';
102
-
103
- const thumbnail = await createThumbnail({
104
- url: `file://${cleanVideoPath}`,
105
- timeStamp: 1000,
106
- })
135
+ useEffect(() => {
136
+ const unsubscribeNetInfo = NetInfo.addEventListener((stat) => {
137
+ setIsInternet(stat.isConnected);
138
+ });
107
139
 
108
- if (thumbnail) {
109
- setPoster(thumbnail?.path)
110
- setIsLoading(false)
111
- }
112
- } catch (error) {
113
- console.log(error);
140
+ StatusBar.setHidden(false);
114
141
 
115
- setIsLoading(false)
116
- throw new Error(error.message);
117
- }
118
- }
142
+ const backAction = () => {
143
+ handleBack();
144
+ return true;
119
145
  };
120
146
 
121
- const ref = useRef();
122
- const descRef = useRef()
123
-
147
+ const backHandler = BackHandler.addEventListener('hardwareBackPress', backAction);
124
148
 
125
- const defaultConfig = {
126
- levels: {
127
- debug: 0,
128
- info: 1,
129
- warn: 2,
130
- error: 3,
131
- },
132
- severity: "debug",
133
- transport: consoleTransport,
134
- transportOptions: {
135
- colors: {
136
- info: "blueBright",
137
- warn: "yellowBright",
138
- error: "redBright",
139
- },
140
- },
141
- async: true,
142
- dateFormat: "time",
143
- printLevel: false,
144
- printDate: false,
145
- fixedExtLvlLength: false,
146
- enabled: global.makeLogEnabled == true ? true : false,
149
+ return () => {
150
+ backHandler.remove();
151
+ if (unsubscribeNetInfo) {
152
+ unsubscribeNetInfo();
153
+ }
147
154
  };
155
+ }, []);
148
156
 
157
+ useEffect(() => {
158
+ if (mediaType !== 'img') {
159
+ setIsLoading(true);
160
+ generateThumbnail();
161
+ }
162
+ if (type === 'img') {
163
+ setIsLoading(false);
164
+ }
165
+ }, [type, path]);
149
166
 
150
- var log = logger.createLogger(defaultConfig);
151
-
152
- // For open Video picker
153
- const openVideoPicker = () => {
154
- global.IsBackground = "false"
155
- setIsLoading(true)
156
-
157
- const options = {
158
- mediaType: 'video',
159
- includeBase64: false,
160
- selectionLimit: 1
161
- };
162
-
163
- launchImageLibrary(options, async (response) => {
164
- if (response.didCancel) {
165
- setIsLoading(false)
166
- global.IsBackground = "false"
167
- // console.log('User cancelled image picker');
168
- } else if (response.error || response.errorCode) {
169
- setIsLoading(false)
170
- global.IsBackground = "true"
171
- // console.log('Image picker error: ', response.error);
172
- } else {
173
- global.IsBackground = "true"
174
- let videoUri = await response.uri || response.assets?.[0]?.uri;
175
- if (response.assets?.[0]?.type?.slice(0, 5).trim() == "video") {
176
- if (mediaType == "img")
177
- navigate("TrimVideoScreen", { video: `${videoUri}`, Type: "imgReplacedWithVideo", themeColor: ThemeColor, SDKScreenTitle: SDKScreenTitle, UpdateStatusBar: UpdateStatusBar, updateParent: updateParent })
178
- else
179
- navigate("TrimVideoScreen", { video: `${videoUri}`, Type: "videoReplacedWithVideo", themeColor: ThemeColor, SDKScreenTitle: SDKScreenTitle, UpdateStatusBar: UpdateStatusBar, updateParent: updateParent })
180
- setIsLoading(false)
181
- }
182
- else {
183
- setIsLoading(false)
184
- setAPIMessage("Invalid selection, Please choose a valid video file")
185
- setErrorInAPI(true)
186
- }
167
+ const generateThumbnail = async () => {
168
+ if (type !== 'img') {
169
+ try {
170
+ const { create: createThumbnail } = CreateThumbnail;
171
+ const cleanVideoPath = path ? path.replace('file://', '') : '';
187
172
 
188
- }
173
+ const thumbnail = await createThumbnail({
174
+ url: `file://${cleanVideoPath}`,
175
+ timeStamp: 1000,
189
176
  });
190
- };
191
177
 
192
- // For open image picker
193
- const openImagePicker = () => {
194
- global.IsBackground = "false"
195
- setIsLoading(true)
196
- const options = {
197
- mediaType: 'photo',
198
- includeBase64: false,
199
- selectionLimit: 1
200
- };
201
- launchImageLibrary(options, async (response) => {
202
- if (response.didCancel) {
203
- setIsLoading(false)
204
- global.IsBackground = "true"
205
- // console.log('User cancelled image picker');
206
- } else if (response.error || response.errorCode) {
207
- setIsLoading(false)
208
- global.IsBackground = "true"
209
- // console.log('Image picker error: ', response.error);
210
- } else {
211
- global.IsBackground = "true"
212
- if (response.assets?.[0]?.type?.slice(0, 5).trim() == "image") {
213
- let imageuri = await response.uri || response.assets?.[0]?.uri;
214
- setIsLoading(false)
215
- if (imageuri != "" || imageuri != undefined) {
216
- setPath(imageuri)
217
- setType("img")
218
-
219
- if (mediaType != "Recordvideo")
220
- // For update image in FirstScreen
221
- updateParent(
222
- imageuri
223
- )
224
- }
225
- }
226
- else {
227
- setIsLoading(false)
228
- setAPIMessage("Invalid selection, Please choose a valid image file")
229
- setErrorInAPI(true)
230
- }
231
-
232
- }
233
- });
178
+ if (thumbnail) {
179
+ setPoster(thumbnail?.path);
180
+ setIsLoading(false);
181
+ }
182
+ } catch (error) {
183
+ setIsLoading(false);
184
+ }
234
185
  }
186
+ };
187
+
188
+ const defaultConfig = {
189
+ levels: { debug: 0, info: 1, warn: 2, error: 3 },
190
+ severity: 'debug',
191
+ transport: consoleTransport,
192
+ transportOptions: {
193
+ colors: { info: 'blueBright', warn: 'yellowBright', error: 'redBright' },
194
+ },
195
+ async: true,
196
+ dateFormat: 'time',
197
+ printLevel: false,
198
+ printDate: false,
199
+ fixedExtLvlLength: false,
200
+ enabled: global.makeLogEnabled === true,
201
+ };
202
+
203
+ const log = logger.createLogger(defaultConfig);
204
+
205
+ const openVideoPicker = () => {
206
+ global.IsBackground = 'false';
207
+ setIsLoading(true);
208
+
209
+ const options = {
210
+ mediaType: 'video',
211
+ includeBase64: false,
212
+ selectionLimit: 1,
213
+ };
235
214
 
236
- // For report bug
237
- const ReportBugFunc = async () => {
238
- setIsLoading(true)
239
- let EmailReg = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
240
- if (desc.trim() == "" || desc.trim() == undefined) {
241
- setDescBorder("red")
242
- setIsLoading(false)
243
- }
244
- else if (email != "" && !EmailReg.test(email)) {
245
- setEmailBorder("red")
246
- setIsLoading(false)
215
+ launchImageLibrary(options, async (response) => {
216
+ if (response.didCancel) {
217
+ setIsLoading(false);
218
+ global.IsBackground = 'false';
219
+ } else if (response.error || response.errorCode) {
220
+ setIsLoading(false);
221
+ global.IsBackground = 'true';
222
+ } else {
223
+ global.IsBackground = 'true';
224
+ let videoUri = (await response.uri) || response.assets?.[0]?.uri;
225
+ if (response.assets?.[0]?.type?.slice(0, 5).trim() === 'video') {
226
+ if (mediaType === 'img') {
227
+ navigate('TrimVideoScreen', {
228
+ video: `${videoUri}`,
229
+ Type: 'imgReplacedWithVideo',
230
+ themeColor: ThemeColor,
231
+ SDKScreenTitle: SDKScreenTitle,
232
+ UpdateStatusBar: () => StatusBar.setHidden(false),
233
+ updateParent: updateParent,
234
+ });
235
+ } else {
236
+ navigate('TrimVideoScreen', {
237
+ video: `${videoUri}`,
238
+ Type: 'videoReplacedWithVideo',
239
+ themeColor: ThemeColor,
240
+ SDKScreenTitle: SDKScreenTitle,
241
+ UpdateStatusBar: () => StatusBar.setHidden(false),
242
+ updateParent: updateParent,
243
+ });
244
+ }
245
+ setIsLoading(false);
246
+ } else {
247
+ setIsLoading(false);
248
+ setAPIMessage('Invalid selection, Please choose a valid video file');
249
+ setErrorInAPI(true);
247
250
  }
248
- else {
249
-
250
- let isVideo = type == 'img' ? 0 : 1;
251
-
252
-
253
- if (isInternet != false) {
254
- try {
255
- const { success, message } = await AddIssueApi(global.session_id, global.application_id, path, isVideo, desc, email);
256
- if (success) {
257
- setIsLoading(false)
258
- setIsBugReported(true)
259
- setAPIMessage(Localization[global.ShakebugLanguage].BugSubmited)
260
- log.debug("------------------------------------------------------------------------------------------")
261
- log.info('API response:', message);
262
- log.debug("------------------------------------------------------------------------------------------")
263
- } else {
264
- setIsLoading(false)
265
- setAPIMessage("Something went wrong! Please try again later!")
266
- setErrorInAPI(true)
267
- log.debug("------------------------------------------------------------------------------------------")
268
- log.warn('API response:', message);
269
- log.debug("------------------------------------------------------------------------------------------")
270
- }
271
- }
272
- catch (err) {
273
- setAPIMessage("Something went wrong! Please try again later!")
274
- setErrorInAPI(true)
275
- setIsLoading(false)
276
- log.error("err", err)
277
- }
278
- }
279
- else {
280
- setErrorInAPI(true)
281
- setAPIMessage(Localization[global.ShakebugLanguage].InterNetConnetionError)
251
+ }
252
+ });
253
+ };
254
+
255
+ const openImagePicker = () => {
256
+ global.IsBackground = 'false';
257
+ setIsLoading(true);
258
+ const options = {
259
+ mediaType: 'photo',
260
+ includeBase64: false,
261
+ selectionLimit: 1,
262
+ };
263
+ launchImageLibrary(options, async (response) => {
264
+ if (response.didCancel) {
265
+ setIsLoading(false);
266
+ global.IsBackground = 'true';
267
+ } else if (response.error || response.errorCode) {
268
+ setIsLoading(false);
269
+ global.IsBackground = 'true';
270
+ } else {
271
+ global.IsBackground = 'true';
272
+ if (response.assets?.[0]?.type?.slice(0, 5).trim() === 'image') {
273
+ let imageuri = (await response.uri) || response.assets?.[0]?.uri;
274
+ setIsLoading(false);
275
+ if (imageuri != '' && imageuri != undefined) {
276
+ setPath(imageuri);
277
+ setType('img');
278
+
279
+ if (mediaType !== 'Recordvideo' && updateParent) {
280
+ updateParent(imageuri);
282
281
  }
283
-
284
-
285
- // Manage submit bug api
286
-
287
-
282
+ }
283
+ } else {
284
+ setIsLoading(false);
285
+ setAPIMessage('Invalid selection, Please choose a valid image file');
286
+ setErrorInAPI(true);
288
287
  }
288
+ }
289
+ });
290
+ };
291
+
292
+ const EmailReg =
293
+ /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
294
+
295
+ const ReportBugFunc = async () => {
296
+ Keyboard.dismiss();
297
+
298
+ if (!desc || desc.trim() === '') {
299
+ setDescBorder('red');
300
+ setIsLoading(false);
301
+ setAPIMessage(getLocalizedString('DescriptionError'));
302
+ setErrorInAPI(true);
303
+ return;
289
304
  }
290
305
 
291
- UpdateStatusBar = () => {
292
- StatusBar.setHidden(false)
306
+ const trimmedEmail = email ? email.trim() : '';
307
+ if (trimmedEmail !== '' && !EmailReg.test(trimmedEmail)) {
308
+ setEmailBorder('red');
309
+ setIsLoading(false);
310
+ setAPIMessage(getLocalizedString('EmailError'));
311
+ setErrorInAPI(true);
312
+ return;
293
313
  }
294
314
 
295
- return (
296
- <View style={{ flex: 1, backgroundColor: '#fff' }}>
297
- <StatusBar backgroundColor={ThemeColor} hidden={false} barStyle="dark-content" />
298
- <SafeAreaView style={{ flex: 0, backgroundColor: ThemeColor }} />
299
- {Platform.OS === 'android' && StatusBar.currentHeight ? (
300
- <View style={{ height: StatusBar.currentHeight, backgroundColor: ThemeColor }} />
301
- ) : null}
302
- <SafeAreaView style={{ flex: 1, backgroundColor: '#fff' }}>
303
- <View style={{
304
- flex: 1,
305
- backgroundColor: "#fff",
306
- paddingBottom: Platform.OS === 'android' ? 48 : 0 // Standard bottom navigation bar height
307
- }}>
308
- <KeyboardAvoidingWrapper>
309
- <>
310
- <View style={{ height: '6%', padding: 5, paddingHorizontal: 15, backgroundColor: ThemeColor, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
311
- <TouchableOpacity
312
- style={{
313
- width: '10%',
314
- alignItems: 'center'
315
- }}
316
- onPress={() => {
317
- StatusBar.setHidden(false)
318
- global.isScreenShotDetected = false
319
- setIsScreenShot(false)
320
- setIsRecordingStart(false)
321
- if (mediaType == "Recordvideo") {
322
- global.isShakebugSDKOpen = false
323
- global.IsBackground = 'true'
324
- goToScreenAtIndex(0)
325
- }
326
- else {
327
- global.IsBackground = 'false'
328
- goToScreenAtIndex(1)
329
- }
330
- }}
331
- >
332
- <Image source={require('../Assets/images/close.png')} resizeMode='contain' style={{ width: 30, height: 30 }} />
333
- </TouchableOpacity>
334
- <Text style={{ width: '80%', textAlign: 'center', fontSize: 18, fontWeight: '300', color: 'white' }}>
335
- {title}
336
- </Text>
337
- <TouchableOpacity
338
- style={{
339
- width: '10%',
340
- alignItems: 'center'
341
- }}
342
- onPress={() => {
343
- Keyboard.dismiss()
344
- ReportBugFunc()
345
-
346
- }}
347
- >
348
- <Image source={require('../Assets/images/next.png')} resizeMode='contain' style={{ width: 25, height: 25 }} />
349
- </TouchableOpacity>
350
- </View>
351
-
352
- <TouchableOpacity
353
- style={{ marginHorizontal: 20, marginTop: '3%', borderWidth: descBorder == 'red' ? 2 : 1, height: "20%", paddingHorizontal: 10, borderRadius: 6, borderColor: descBorder, alignItems: 'center' }}
354
- activeOpacity={1}
355
- onPress={() => descRef.current.focus()}
356
- >
357
- <TextInput
358
- ref={descRef}
359
- placeholder={Localization[global.ShakebugLanguage].Description}
360
- placeholderTextColor={"#b2b2b2"}
361
- multiline
362
- style={{
363
- fontSize: 16,
364
- color: '#000',
365
- width: '100%',
366
- paddingVertical: 8,
367
-
368
- }}
369
- value={desc}
370
- onChangeText={(e) => {
371
- setDesc(e)
372
- setDescBorder("black")
373
- }}
374
- />
375
-
376
- {descBorder == "red" ? (<View
377
- style={{
378
- position: 'absolute',
379
- bottom: 10,
380
- right: 10,
381
- alignItems: 'center',
382
- flexDirection: 'row'
383
- }}
384
- >
385
- <View style={{ width: 20, height: 20, backgroundColor: 'red', alignItems: 'center', justifyContent: 'center', borderRadius: 50 }}>
386
- <Text style={{ fontSize: 16, fontWeight: '900', textAlign: 'center', textAlignVertical: 'center', color: 'white' }}>!</Text>
387
- </View>
388
- <Text style={{ color: 'red', fontWeight: '500', marginLeft: 5 }}>{Localization[global.ShakebugLanguage].DescriptionError}</Text>
389
- </View>) : null}
390
- </TouchableOpacity>
391
-
392
- <View style={{ marginHorizontal: 20, marginTop: '2%', borderWidth: EmailBorder == "red" ? 2 : 1, borderColor: EmailBorder, height: '7%', paddingHorizontal: 10, borderRadius: 6 }}>
393
- <TextInput
394
- placeholder={Localization[global.ShakebugLanguage].Email}
395
- placeholderTextColor={"#b2b2b2"}
396
- style={{
397
- fontSize: 16,
398
- color: '#000',
399
- height: '100%',
400
- width: '100%',
401
- textAlignVertical: 'center',
402
- }}
403
- value={email}
404
- onChangeText={(val) => {
405
- setEmail(val)
406
- setEmailBorder("black")
407
- }}
408
- />
409
-
410
- </View>
411
- <Text style={{ fontSize: 12, color: 'red', fontWeight: '500', marginRight: '6%', textAlign: 'right' }}>{EmailBorder == "red" ? Localization[global.ShakebugLanguage].EmailError : ""}</Text>
412
-
413
- <TouchableOpacity activeOpacity={1} onPress={Keyboard.dismiss} style={{ height: Platform.OS == 'ios' ? Dimensions.get('window').height * 0.50 : '50%', width: '100%', marginTop: '0%', justifyContent: 'center', alignItems: 'center' }}>
414
- {type == "img" ?
415
- path != "" && <Image source={{ uri: path }} resizeMode='contain' style={{ width: '100%', height: '100%' }} />
416
- :
417
- <View style={{ width: '100%', height: '100%' }}>
418
- {poster != "" && <VideoPlayer videoUri={path} img={poster} themeColor={ThemeColor} goToScreenAtIndex={goToScreenAtIndex} goBack={goBack} SDKScreenTitle={SDKScreenTitle} UpdateStatusBar={UpdateStatusBar} />}
419
- </View>}
420
- </TouchableOpacity>
421
-
422
- <View style={{ marginTop: '2%', width: '100%'}}>
423
- <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginHorizontal: 20 }}>
424
- <TouchableOpacity onPress={() => openImagePicker()}>
425
- <Text style={{ fontSize: 16, color: ThemeColor, fontWeight: '300' }}>{Localization[global.ShakebugLanguage].ReplaceImage}</Text>
426
- </TouchableOpacity>
427
- <TouchableOpacity
428
- onPress={() => {
429
- if (global.AllowVideo == false) {
430
- setAPIMessage(`You have reached your max video limitation. \n\n Used Videos: ${global.used_video} \nAllowed Videos: ${global.total_video}\n\nPlease visit https://www.shakebug.com website and upgrade your plan.`)
431
- setErrorInAPI(true)
432
- }
433
- else {
434
- openVideoPicker()
435
- }
436
- }}
437
- >
438
- <Text style={{ fontSize: 16, color: ThemeColor, fontWeight: '300' }}>{Localization[global.ShakebugLanguage].ReplaceVideo}</Text>
439
- </TouchableOpacity>
440
- </View>
441
-
442
- <View style={{ alignItems: 'flex-end', marginHorizontal: 15, marginTop: '0.5%' }}>
443
- <Text style={{ fontSize: 12, color: "gray", fontWeight: '300' }}>© <Text>ShakeBug v{global.sdk_version}</Text></Text>
444
- </View>
445
-
446
- </View>
447
-
448
-
449
- <CustomeAlert
450
- isVisible={isErrorInAPI || isBugReported}
451
- text={apiMessage}
452
- color={ThemeColor}
453
- OnPrees={() => {
454
- if (isBugReported) {
455
- global.isScreenShotDetected = false;
456
- setIsScreenShot(false)
457
- setIsRecordingStart(false)
458
- goToScreenAtIndex(0)
459
- }
460
- setIsBugReported(false)
461
- setErrorInAPI(false)
462
- setIsLoading(false)
463
- }}
464
- />
465
- </>
466
- </KeyboardAvoidingWrapper>
315
+ setIsLoading(true);
316
+ let isVideo = type === 'img' ? 0 : 1;
317
+
318
+ if (isInternet !== false) {
319
+ try {
320
+ const { success, message } = await AddIssueApi(
321
+ global.session_id,
322
+ global.application_id,
323
+ path,
324
+ isVideo,
325
+ desc,
326
+ email
327
+ );
328
+ if (success) {
329
+ setIsLoading(false);
330
+ setIsBugReported(true);
331
+ setAPIMessage(getLocalizedString('BugSubmited'));
332
+ log.info('API response:', message);
333
+ } else {
334
+ setIsLoading(false);
335
+ setAPIMessage('Something went wrong! Please try again later!');
336
+ setErrorInAPI(true);
337
+ log.warn('API response:', message);
338
+ }
339
+ } catch (err) {
340
+ setAPIMessage('Something went wrong! Please try again later!');
341
+ setErrorInAPI(true);
342
+ setIsLoading(false);
343
+ log.error('err', err);
344
+ }
345
+ } else {
346
+ setErrorInAPI(true);
347
+ setAPIMessage(getLocalizedString('InterNetConnetionError'));
348
+ setIsLoading(false);
349
+ }
350
+ };
351
+
352
+ const isImageMedia = type === 'img';
353
+
354
+ return (
355
+ <View style={{ flex: 1, backgroundColor: theme.detailsBg }}>
356
+ <StatusBar backgroundColor={theme.headerBg} hidden={false} barStyle="light-content" />
357
+
358
+ {/* Top Header with Safe Area */}
359
+ <SafeAreaView style={{ backgroundColor: theme.headerBg }}>
360
+ {Platform.OS === 'android' && StatusBar.currentHeight ? (
361
+ <View style={{ height: StatusBar.currentHeight, backgroundColor: theme.headerBg }} />
362
+ ) : null}
363
+ <View style={[styles.header, { backgroundColor: theme.headerBg }]}>
364
+ {/* Back chevron < */}
365
+ {type === 'img' ? (<TouchableOpacity
366
+ style={styles.headerBtn}
367
+ onPress={handleBack}
368
+ hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
369
+ activeOpacity={0.7}
370
+ >
371
+ <BackChevronIcon color="#ffffff" size={22} />
372
+ </TouchableOpacity>) : (
373
+ <TouchableOpacity
374
+ style={styles.headerBtn}
375
+ onPress={handleBack}
376
+ hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
377
+ activeOpacity={0.7}
378
+ >
379
+ <CloseCircleIcon color="#ffffff" size={22} />
380
+ </TouchableOpacity>
381
+ )}
382
+
383
+ {/* Screen Title */}
384
+ <Text style={styles.headerTitle} numberOfLines={1}>
385
+ {title || getLocalizedString('SecondScreenTitle') || getLocalizedString('AddDetails') || 'Add details'}
386
+ </Text>
387
+
388
+ {/* Send pill button */}
389
+ <TouchableOpacity
390
+ style={[styles.sendButton, { borderWidth: 1, borderColor: "#ffffff" }]}
391
+ onPress={() => {
392
+ Keyboard.dismiss();
393
+ ReportBugFunc();
394
+ }}
395
+ hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
396
+ activeOpacity={0.8}
397
+ >
398
+ <Text style={[styles.sendButtonText, { color: "#ffffff" }]}>
399
+ {getLocalizedString('Send')}
400
+ </Text>
401
+ </TouchableOpacity>
402
+ </View>
403
+ </SafeAreaView>
404
+
405
+ {/* Form Content */}
406
+ <View style={[styles.body, { backgroundColor: theme.detailsBg }]}>
407
+ <KeyboardAvoidingWrapper
408
+ style={{ backgroundColor: theme.detailsBg }}
409
+ contentContainerStyle={{ flexGrow: 1, paddingBottom: 4 }}
410
+ scrollEnabled={true}
411
+ bounces={false}
412
+ >
413
+ <View style={styles.formContainer}>
414
+ {/* Description Input Card */}
415
+ <TouchableOpacity
416
+ style={[
417
+ styles.descriptionCard,
418
+ {
419
+ backgroundColor: theme.inputBg,
420
+ borderColor:
421
+ descBorder === 'red' ? '#ef4444' : theme.inputBorder,
422
+ borderWidth: descBorder === 'red' ? 2 : 1,
423
+ },
424
+ ]}
425
+ activeOpacity={1}
426
+ onPress={() => descRef.current?.focus()}
427
+ >
428
+ <TextInput
429
+ ref={descRef}
430
+ placeholder={getLocalizedString('Description')}
431
+ placeholderTextColor={theme.placeholderColor}
432
+ multiline
433
+ style={[styles.descriptionInput, { color: theme.textColor }]}
434
+ value={desc}
435
+ cursorColor={theme.isDark ? '#ffffff' : ThemeColor}
436
+ selectionColor={theme.isDark ? 'rgba(255,255,255,0.3)' : 'rgba(49,58,70,0.3)'}
437
+ keyboardAppearance={theme.isDark ? 'dark' : 'light'}
438
+ onChangeText={(e) => {
439
+ setDesc(e);
440
+ if (descBorder) setDescBorder('');
441
+ }}
442
+ />
443
+ </TouchableOpacity>
444
+
445
+ {/* Email Input Card with Envelope Icon */}
446
+ <View
447
+ style={[
448
+ styles.emailCard,
449
+ {
450
+ backgroundColor: theme.inputBg,
451
+ borderColor:
452
+ EmailBorder === 'red' ? '#ef4444' : theme.inputBorder,
453
+ borderWidth: EmailBorder === 'red' ? 1.5 : 1,
454
+ },
455
+ ]}
456
+ >
457
+ <View style={styles.emailIconWrapper}>
458
+ <MailIcon
459
+ color={
460
+ EmailBorder === 'red'
461
+ ? '#ef4444'
462
+ : theme.placeholderColor
463
+ }
464
+ size={18}
465
+ />
466
+ </View>
467
+ <TextInput
468
+ placeholder={getLocalizedString('Email')}
469
+ placeholderTextColor={theme.placeholderColor}
470
+ keyboardType="email-address"
471
+ autoCapitalize="none"
472
+ style={[styles.emailInput, { color: theme.textColor }]}
473
+ value={email}
474
+ cursorColor={theme.isDark ? '#ffffff' : ThemeColor}
475
+ selectionColor={
476
+ theme.isDark
477
+ ? 'rgba(255,255,255,0.3)'
478
+ : 'rgba(49,58,70,0.3)'
479
+ }
480
+ keyboardAppearance={theme.isDark ? 'dark' : 'light'}
481
+ onChangeText={(val) => {
482
+ setEmail(val);
483
+ if (EmailBorder) setEmailBorder('');
484
+ }}
485
+ />
467
486
  </View>
468
487
 
469
- </SafeAreaView>
470
- <LoadingModal visible={isLoading} color={ThemeColor} />
471
- </View>
472
- )
473
- }
488
+ {/* ATTACHED MEDIA Label */}
489
+ <Text
490
+ style={[
491
+ styles.sectionLabel,
492
+ { color: theme.textSecondary },
493
+ ]}
494
+ >
495
+ {getLocalizedString('AttachedMedia')}
496
+ </Text>
497
+
498
+ {/* Attached Media Container Card with Badges - Expands to fill available vertical space */}
499
+ <View
500
+ style={[
501
+ styles.mediaCard,
502
+ {
503
+ width: cardWidth,
504
+ backgroundColor: theme.isDark ? '#2b2f38' : '#f8fafc',
505
+ borderColor: theme.borderColor,
506
+ },
507
+ ]}
508
+ >
509
+ {/* Floating Top Badges (pointerEvents="none" so touches pass directly to ZoomableImage) */}
510
+ <View style={styles.badgesRow} pointerEvents="none">
511
+ {/* Left Badge: Screenshot or Video */}
512
+ <View style={styles.mediaBadge}>
513
+ <View style={styles.badgeIconWrapper}>
514
+ {isImageMedia ? (
515
+ <CameraIcon color="#ffffff" size={13} />
516
+ ) : (
517
+ <CamcorderIcon color="#ffffff" size={13} />
518
+ )}
519
+ </View>
520
+ <Text style={styles.badgeText}>
521
+ {isImageMedia
522
+ ? getLocalizedString('Screenshot')
523
+ : 'Video'}
524
+ </Text>
525
+ </View>
526
+
527
+ {/* Right Badge: Pinch to zoom (for image only, when card width allows) */}
528
+ {isImageMedia && cardWidth >= 220 ? (
529
+ <View style={styles.mediaBadge}>
530
+ <Text style={styles.badgeText}>
531
+ {getLocalizedString('PinchToZoom')}
532
+ </Text>
533
+ </View>
534
+ ) : null}
535
+ </View>
536
+
537
+ {/* Media Preview View */}
538
+ {isImageMedia ? (
539
+ <ZoomableImage uri={path} style={styles.zoomableImageContainer} resizeMode="contain" />
540
+ ) : (
541
+ <View style={styles.videoPlayerContainer}>
542
+ {poster !== '' && (
543
+ <VideoPlayer
544
+ videoUri={path}
545
+ img={poster}
546
+ themeColor={ThemeColor}
547
+ goToScreenAtIndex={goToScreenAtIndex}
548
+ goBack={goBack}
549
+ SDKScreenTitle={SDKScreenTitle}
550
+ UpdateStatusBar={() => StatusBar.setHidden(false)}
551
+ />
552
+ )}
553
+ </View>
554
+ )}
555
+ </View>
474
556
 
475
- export default AddDetailsScreen
557
+ {/* Bottom Actions and Version Footer - Anchored at the bottom */}
558
+ <View style={styles.bottomSection}>
559
+ {/* Replace Action Buttons */}
560
+ <View style={styles.replaceButtonsRow}>
561
+ {/* Replace Image Button */}
562
+ <TouchableOpacity
563
+ style={[
564
+ styles.replaceButton,
565
+ {
566
+ backgroundColor: theme.detailsBg,
567
+ borderColor: theme.buttonBorder,
568
+ },
569
+ ]}
570
+ activeOpacity={0.8}
571
+ onPress={openImagePicker}
572
+ >
573
+ <View style={styles.replaceButtonIconWrapper}>
574
+ <PhotoGalleryIcon color={theme.textSecondary} size={18} />
575
+ </View>
576
+ <Text
577
+ style={[
578
+ styles.replaceButtonText,
579
+ { color: theme.textSecondary },
580
+ ]}
581
+ >
582
+ {getLocalizedString('ReplaceImage')}
583
+ </Text>
584
+ </TouchableOpacity>
585
+
586
+ {/* Replace Video Button */}
587
+ <TouchableOpacity
588
+ style={[
589
+ styles.replaceButton,
590
+ {
591
+ backgroundColor: theme.detailsBg,
592
+ borderColor: theme.buttonBorder,
593
+ },
594
+ ]}
595
+ activeOpacity={0.8}
596
+ onPress={() => {
597
+ if (global.AllowVideo === false) {
598
+ setAPIMessage(
599
+ `You have reached your max video limitation. \n\n Used Videos: ${global.used_video} \nAllowed Videos: ${global.total_video}\n\nPlease visit https://www.shakebug.com website and upgrade your plan.`
600
+ );
601
+ setErrorInAPI(true);
602
+ } else {
603
+ openVideoPicker();
604
+ }
605
+ }}
606
+ >
607
+ <View style={styles.replaceButtonIconWrapper}>
608
+ <CamcorderIcon color={theme.textSecondary} size={18} />
609
+ </View>
610
+ <Text
611
+ style={[
612
+ styles.replaceButtonText,
613
+ { color: theme.textSecondary },
614
+ ]}
615
+ >
616
+ {getLocalizedString('ReplaceVideo')}
617
+ </Text>
618
+ </TouchableOpacity>
619
+ </View>
620
+
621
+ {/* Version Footer */}
622
+ <View style={styles.footerContainer}>
623
+ <Text style={[styles.footerText, { color: theme.textSecondary }]}>
624
+ ©ShakeBug v{global.sdk_version || '2.4.16'}
625
+ </Text>
626
+ </View>
627
+ </View>
628
+ </View>
629
+ </KeyboardAvoidingWrapper>
630
+ </View>
631
+
632
+ {/* Bottom Safe Area Spacer */}
633
+ <View style={{ height: bottomSpace, width: '100%', backgroundColor: theme.detailsBg }} />
634
+
635
+ <CustomeAlert
636
+ isVisible={isErrorInAPI || isBugReported}
637
+ text={apiMessage}
638
+ color={ThemeColor}
639
+ OnPrees={() => {
640
+ if (isBugReported) {
641
+ global.isTrigger = false;
642
+ global.isShakebugOpen = false;
643
+ global.isScreenShotDetected = false;
644
+ global.isScreeenShotTaken = false;
645
+ global.isShakebugSDKOpen = false;
646
+ global.IsBackground = 'true';
647
+ setIsScreenShot(false);
648
+ setIsRecordingStart(false);
649
+ goToScreenAtIndex(0);
650
+ }
651
+ setIsBugReported(false);
652
+ setErrorInAPI(false);
653
+ setIsLoading(false);
654
+ }}
655
+ />
656
+
657
+ <LoadingModal visible={isLoading} color={ThemeColor} />
658
+ </View>
659
+ );
660
+ };
661
+
662
+ const styles = StyleSheet.create({
663
+ header: {
664
+ height: 52,
665
+ flexDirection: 'row',
666
+ alignItems: 'center',
667
+ justifyContent: 'space-between',
668
+ paddingHorizontal: 16,
669
+ },
670
+ headerBtn: {
671
+ width: 44,
672
+ height: 44,
673
+ justifyContent: 'center',
674
+ alignItems: 'center',
675
+ },
676
+ headerTitle: {
677
+ fontSize: 18,
678
+ fontWeight: '700',
679
+ color: '#ffffff',
680
+ textAlign: 'center',
681
+ flex: 1,
682
+ },
683
+ sendButton: {
684
+ // backgroundColor: '#ffffff',
685
+ borderRadius: 20,
686
+ paddingHorizontal: 18,
687
+ paddingVertical: 6,
688
+ justifyContent: 'center',
689
+ alignItems: 'center'
690
+ },
691
+ sendButtonText: {
692
+ fontSize: 14,
693
+ fontWeight: '700',
694
+ },
695
+ body: {
696
+ flex: 1,
697
+ },
698
+ formContainer: {
699
+ flex: 1,
700
+ paddingHorizontal: 16,
701
+ paddingTop: 8,
702
+ paddingBottom: 0,
703
+ },
704
+ descriptionCard: {
705
+ borderRadius: 12,
706
+ paddingHorizontal: 12,
707
+ paddingVertical: 8,
708
+ height: 120,
709
+ marginBottom: 10,
710
+ justifyContent: 'flex-start',
711
+ },
712
+ descriptionInput: {
713
+ fontSize: 15,
714
+ width: '100%',
715
+ height: '100%',
716
+ padding: 0,
717
+ textAlignVertical: 'top',
718
+ },
719
+ emailCard: {
720
+ borderRadius: 12,
721
+ height: 45,
722
+ paddingHorizontal: 12,
723
+ flexDirection: 'row',
724
+ alignItems: 'center',
725
+ marginBottom: 10,
726
+ },
727
+ emailIconWrapper: {
728
+ width: 24,
729
+ height: 24,
730
+ alignItems: 'center',
731
+ justifyContent: 'center',
732
+ marginRight: 10,
733
+ },
734
+ emailInput: {
735
+ fontSize: 15,
736
+ flex: 1,
737
+ height: '100%',
738
+ padding: 0,
739
+ },
740
+ sectionLabel: {
741
+ fontSize: 12,
742
+ fontWeight: '700',
743
+ letterSpacing: 0.5,
744
+ marginTop: 8,
745
+ marginBottom: 6,
746
+ },
747
+ mediaCard: {
748
+ flex: 1,
749
+ minHeight: 180,
750
+ borderRadius: 14,
751
+ borderWidth: 1,
752
+ overflow: 'hidden',
753
+ position: 'relative',
754
+ justifyContent: 'center',
755
+ alignItems: 'center',
756
+ backgroundColor: 'transparent',
757
+ alignSelf: 'center',
758
+ marginBottom: 8,
759
+ },
760
+ badgesRow: {
761
+ position: 'absolute',
762
+ top: 10,
763
+ left: 10,
764
+ right: 10,
765
+ flexDirection: 'row',
766
+ justifyContent: 'space-between',
767
+ zIndex: 10,
768
+ elevation: 4,
769
+ },
770
+ mediaBadge: {
771
+ backgroundColor: 'rgba(30, 41, 59, 0.78)',
772
+ borderRadius: 12,
773
+ paddingHorizontal: 10,
774
+ paddingVertical: 5,
775
+ flexDirection: 'row',
776
+ alignItems: 'center',
777
+ },
778
+ badgeIconWrapper: {
779
+ marginRight: 5,
780
+ alignItems: 'center',
781
+ justifyContent: 'center',
782
+ },
783
+ badgeText: {
784
+ color: '#ffffff',
785
+ fontSize: 12,
786
+ fontWeight: '600',
787
+ },
788
+ zoomableImageContainer: {
789
+ width: '100%',
790
+ height: '100%',
791
+ },
792
+ videoPlayerContainer: {
793
+ width: '100%',
794
+ height: '100%',
795
+ },
796
+ bottomSection: {
797
+ width: '100%',
798
+ marginTop: 'auto',
799
+ },
800
+ replaceButtonsRow: {
801
+ flexDirection: 'row',
802
+ justifyContent: 'space-between',
803
+ marginTop: 4,
804
+ marginBottom: 4,
805
+ },
806
+ replaceButton: {
807
+ flex: 1,
808
+ height: 42,
809
+ borderRadius: 12,
810
+ borderWidth: 1,
811
+ flexDirection: 'row',
812
+ alignItems: 'center',
813
+ justifyContent: 'center',
814
+ marginHorizontal: 4,
815
+ },
816
+ replaceButtonIconWrapper: {
817
+ marginRight: 8,
818
+ alignItems: 'center',
819
+ justifyContent: 'center',
820
+ },
821
+ replaceButtonText: {
822
+ fontSize: 12,
823
+ fontWeight: '700',
824
+ },
825
+ footerContainer: {
826
+ alignItems: 'center',
827
+ marginTop: 2,
828
+ marginBottom: 4,
829
+ },
830
+ footerText: {
831
+ fontSize: 12,
832
+ fontWeight: '400',
833
+ },
834
+ });
835
+
836
+ export default AddDetailsScreen;