shakebug-react-native 2.1.0 → 2.2.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.
Files changed (44) hide show
  1. package/README.md +17 -1
  2. package/VideoPlayer/index.js +37 -23
  3. package/android/build.gradle +0 -1
  4. package/android/src/main/java/com/shakebug/CreateThumbnailModule.java +217 -0
  5. package/android/src/main/java/com/shakebug/NetInfoModule.java +1 -2
  6. package/android/src/main/java/com/shakebug/RNDeviceModule.java +1 -3
  7. package/android/src/main/java/com/shakebug/ShakebugPackage.java +10 -10
  8. package/android/src/main/java/com/shakebug/{Shakebug.java → ShakebugSensors.java} +16 -22
  9. package/ios/GetLocals/ShakebugSDKRNLocalize.h +1 -0
  10. package/ios/Thumbnaill/{VideoThumbnailManager.h → CreateThumbnail.h} +2 -1
  11. package/ios/Thumbnaill/CreateThumbnail.m +82 -0
  12. package/package.json +2 -5
  13. package/shakebug-react-native.podspec +0 -1
  14. package/src/APIs/AddIssue.js +13 -4
  15. package/src/APIs/AddReview.js +46 -0
  16. package/src/APIs/CustomUserId.js +47 -0
  17. package/src/APIs/Event.js +8 -3
  18. package/src/APIs/StartSession.js +6 -2
  19. package/src/Assets/images/solar_star-bold.png +0 -0
  20. package/src/Assets/images/solar_star-line-duotone.png +0 -0
  21. package/src/Componanats/ReviewModal.js +171 -0
  22. package/src/Componanats/ShakeFunction.js +6 -3
  23. package/src/Screens/AddDetailsScreen.js +14 -10
  24. package/src/Screens/FirstScreen.js +2 -2
  25. package/src/Screens/Shakebug.js +63 -6
  26. package/src/Screens/TrimVideoScreen.js +13 -7
  27. package/src/index.tsx +6 -2
  28. package/android/src/main/java/com/shakebug/ShakebugSDKScreenShotModule.java +0 -196
  29. package/android/src/main/java/com/shakebug/VideoPlayerModule.java +0 -199
  30. package/android/src/main/java/com/shakebug/VideoThumbnailModule.java +0 -215
  31. package/android/src/main/java/com/shakebug/VideoTrimModule.java +0 -372
  32. package/android/src/main/java/com/shakebug/adapters/VideoTrimmerAdapter.java +0 -54
  33. package/android/src/main/java/com/shakebug/utils/StorageUtil.java +0 -139
  34. package/android/src/main/java/com/shakebug/utils/VideoTrimmerUtil.java +0 -89
  35. package/android/src/main/java/com/shakebug/widgets/RangeSeekBarView.java +0 -534
  36. package/android/src/main/java/com/shakebug/widgets/VideoPlayerView.java +0 -429
  37. package/android/src/main/java/com/shakebug/widgets/VideoTrimmerView.java +0 -477
  38. package/ios/Thumbnaill/VideoThumbnailManager.m +0 -73
  39. package/ios/TrimVideo/ShakebugSDKVideoTrim.swift +0 -372
  40. package/ios/TrimVideo/VideoTrim-Bridging-Header.h +0 -10
  41. package/ios/TrimVideo/VideoTrim.m +0 -23
  42. package/ios/TrimVideo/VideoTrimmer.swift +0 -810
  43. package/ios/TrimVideo/VideoTrimmerThumb.swift +0 -119
  44. package/ios/TrimVideo/VideoTrimmerViewController.swift +0 -301
@@ -1,6 +1,6 @@
1
- import { NativeEventEmitter, NativeModules, SafeAreaView, StatusBar } from 'react-native'
1
+ import { NativeEventEmitter, NativeModules, Platform, SafeAreaView, StatusBar } from 'react-native'
2
2
  import React, { useEffect, useState } from 'react'
3
- import { showEditor } from '../../trimVideo/src/index';
3
+ import { showEditor } from 'react-native-video-trim';
4
4
  import { useNavigation } from '../Componanats/NavigationContext';
5
5
  import { Localization } from '../Componanats/localization';
6
6
 
@@ -12,10 +12,15 @@ const TrimVideoScreen = ({ video, themeColor, SDKScreenTitle, Type, UpdateStatus
12
12
 
13
13
  const { goToScreenAtIndex, navigate, goBack } = useNavigation();
14
14
 
15
+
15
16
  // For display video trimmer
16
17
  useEffect(() => {
17
18
  StatusBar.setHidden(true)
18
- showEditor(video, {
19
+
20
+ const uri = video; // your path
21
+ const trimmedUri = uri.replace('file://', '');
22
+
23
+ showEditor(Platform.OS == 'android' ? trimmedUri : video, {
19
24
  saveButtonText: Localization[global.ShakebugLanguage].Choose,
20
25
  cancelButtonText: Localization[global.ShakebugLanguage].Cancel,
21
26
  trimmingText: Localization[global.ShakebugLanguage].TrimmingText,
@@ -32,8 +37,9 @@ const TrimVideoScreen = ({ video, themeColor, SDKScreenTitle, Type, UpdateStatus
32
37
  saveDialogCancelText: Localization[global.ShakebugLanguage].No,
33
38
  saveDialogConfirmText: Localization[global.ShakebugLanguage].Yes
34
39
  });
35
- const eventEmitter = new NativeEventEmitter(NativeModules.ShakebugSDKVideoTrim);
36
- const subscription = eventEmitter.addListener('ShakebugSDKVideoTrim', (event) => {
40
+
41
+ const eventEmitter = new NativeEventEmitter(NativeModules.VideoTrim);
42
+ const subscription = eventEmitter.addListener('VideoTrim', (event) => {
37
43
  switch (event.name) {
38
44
  case 'onShow': {
39
45
  break;
@@ -50,7 +56,7 @@ const TrimVideoScreen = ({ video, themeColor, SDKScreenTitle, Type, UpdateStatus
50
56
  navigate("AddDetailsScreen", { media: `${event.outputPath}`, mediaType: 'video', themeColor: themeColor, SDKScreenTitle: SDKScreenTitle, updateParent: updateParent });
51
57
  return;
52
58
  }
53
- case 'onCancelTrimming': {
59
+ case 'onCancel': {
54
60
  UpdateStatusBar()
55
61
  if (Type == "video") {
56
62
  goToScreenAtIndex(1)
@@ -62,7 +68,7 @@ const TrimVideoScreen = ({ video, themeColor, SDKScreenTitle, Type, UpdateStatus
62
68
  goToScreenAtIndex(3)
63
69
  }
64
70
  return;
65
- // break;
71
+ break;
66
72
  }
67
73
  case 'onError': {
68
74
  break;
package/src/index.tsx CHANGED
@@ -4,6 +4,7 @@ import { NavigationContainer, findFocusedRoute } from '@react-navigation/native'
4
4
  import { getLocales } from '../GetLocals/src';
5
5
  import { NativeModules, Text, View, findNodeHandle } from 'react-native';
6
6
  import { useGlobalState } from './Componanats/GlobalStateContext'
7
+ import { CustomUserIdApi } from './APIs/CustomUserId'
7
8
 
8
9
  let Languages = ['ENGLISH', 'HINDI', 'GUJARATI', 'ARABIC', 'CHINESE', 'DUTCH', 'FRENCH', 'GERMAN', 'GREEK', 'INDONESIAN', 'ITALIAN', 'JAPANESE', 'KOREAN', 'RUSSIAN', 'SPANISH', 'TURKISH', 'UKRAINIAN', 'MARATHI', 'TAMIL', 'TELUGU']
9
10
  let LanguageCodes = ['en', 'hi', 'gu', 'ar', 'zh', 'nl', 'fr', 'de', 'el', 'id', 'it', 'ja', 'ko', 'ru', 'es', 'tr', 'uk', 'mr', 'ta', 'te']
@@ -41,7 +42,7 @@ const ShakebugView: React.FC<ShakebugViewProps> = ({
41
42
  allowCrashReport = true,
42
43
  makeLogEnabled = false,
43
44
  ShakebugLanguage = LocalLanguage != -1 ? Languages[LocalLanguage] : "ENGLISH",
44
- setShakebugSDKEndPointURL = ["https://www.shakebug.com/api/v1.3/webservice.php/", "MarchShake2022", ""]
45
+ setShakebugSDKEndPointURL = ["https://www.shakebug.com/api/v1.4/webservice.php/", "MarchShake2022", ""]
45
46
  }) => {
46
47
 
47
48
  return (
@@ -76,7 +77,6 @@ export const triggerBugReporting = (): void => {
76
77
  global.isTrigger = true;
77
78
  }
78
79
 
79
-
80
80
  export const ShakebugSdkProtectedView = ({ children }) => {
81
81
 
82
82
  const { isRecordingStart, isScreenShot } = useGlobalState();
@@ -115,3 +115,7 @@ export const ShakebugSdkProtectedView = ({ children }) => {
115
115
  export const IsShakebugSDKOpen = () => {
116
116
  return global.isShakebugSDKOpen;
117
117
  }
118
+
119
+ export const addCustomUser = (id, name, email, country, other, applicationsid) => {
120
+ CustomUserIdApi(id, name, email, country, other, applicationsid)
121
+ }
@@ -1,196 +0,0 @@
1
-
2
- package com.shakebug;
3
-
4
- import android.app.Activity;
5
- import android.content.Context;
6
- import android.net.Uri;
7
- import android.os.AsyncTask;
8
- import androidx.annotation.NonNull;
9
- import android.util.DisplayMetrics;
10
- import android.util.Log;
11
-
12
- import com.facebook.react.bridge.GuardedAsyncTask;
13
- import com.facebook.react.bridge.Promise;
14
- import com.facebook.react.bridge.ReactApplicationContext;
15
- import com.facebook.react.bridge.ReactContext;
16
- import com.facebook.react.bridge.ReactContextBaseJavaModule;
17
- import com.facebook.react.bridge.ReactMethod;
18
- import com.facebook.react.bridge.ReadableMap;
19
- import com.facebook.react.uimanager.UIManagerModule;
20
-
21
- import java.io.File;
22
- import java.io.FilenameFilter;
23
- import java.io.IOException;
24
- import java.util.Collections;
25
- import java.util.Map;
26
- import java.util.concurrent.Executor;
27
- import java.util.concurrent.Executors;
28
-
29
- import com.shakebug.ViewShot.Formats;
30
- import com.shakebug.ViewShot.Results;
31
-
32
- public class ShakebugSDKScreenShotModule extends ReactContextBaseJavaModule {
33
-
34
- public static final String RNVIEW_SHOT = "ShakebugSDKRNViewShot";
35
-
36
- private final ReactApplicationContext reactContext;
37
-
38
- private final Executor executor = Executors.newCachedThreadPool();
39
-
40
- public ShakebugSDKScreenShotModule(ReactApplicationContext reactContext) {
41
- super(reactContext);
42
- this.reactContext = reactContext;
43
- }
44
-
45
- @Override
46
- public String getName() {
47
- return RNVIEW_SHOT;
48
- }
49
-
50
- @Override
51
- public Map<String, Object> getConstants() {
52
- return Collections.emptyMap();
53
- }
54
-
55
- @Override
56
- public void onCatalystInstanceDestroy() {
57
- super.onCatalystInstanceDestroy();
58
- new CleanTask(getReactApplicationContext()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
59
- }
60
-
61
- @ReactMethod
62
- public void releaseCapture(String uri) {
63
- final String path = Uri.parse(uri).getPath();
64
- if (path == null) return;
65
- File file = new File(path);
66
- if (!file.exists()) return;
67
- File parent = file.getParentFile();
68
- if (parent.equals(reactContext.getExternalCacheDir()) || parent.equals(reactContext.getCacheDir())) {
69
- file.delete();
70
- }
71
- }
72
-
73
- @ReactMethod
74
- public void captureRef(int tag, ReadableMap options, Promise promise) {
75
- final ReactApplicationContext context = getReactApplicationContext();
76
- final DisplayMetrics dm = context.getResources().getDisplayMetrics();
77
-
78
- final String extension = options.getString("format");
79
- final int imageFormat = "jpg".equals(extension)
80
- ? Formats.JPEG
81
- : "webm".equals(extension)
82
- ? Formats.WEBP
83
- : "raw".equals(extension)
84
- ? Formats.RAW
85
- : Formats.PNG;
86
-
87
- final double quality = options.getDouble("quality");
88
- final Integer scaleWidth = options.hasKey("width") ? options.getInt("width") : null;
89
- final Integer scaleHeight = options.hasKey("height") ? options.getInt("height") : null;
90
- final String resultStreamFormat = options.getString("result");
91
- final String fileName = options.hasKey("fileName") ? options.getString("fileName") : null;
92
- final Boolean snapshotContentContainer = options.getBoolean("snapshotContentContainer");
93
- final boolean handleGLSurfaceView = options.hasKey("handleGLSurfaceViewOnAndroid") && options.getBoolean("handleGLSurfaceViewOnAndroid");
94
-
95
- try {
96
- File outputFile = null;
97
- if (Results.TEMP_FILE.equals(resultStreamFormat)) {
98
- outputFile = createTempFile(getReactApplicationContext(), extension, fileName);
99
- }
100
-
101
- final Activity activity = getCurrentActivity();
102
- final UIManagerModule uiManager = this.reactContext.getNativeModule(UIManagerModule.class);
103
-
104
- uiManager.addUIBlock(new ViewShot(
105
- tag, extension, imageFormat, quality,
106
- scaleWidth, scaleHeight, outputFile, resultStreamFormat,
107
- snapshotContentContainer, reactContext, activity, handleGLSurfaceView, promise, executor)
108
- );
109
- } catch (final Throwable ex) {
110
- Log.e(RNVIEW_SHOT, "Failed to snapshot view tag " + tag, ex);
111
- promise.reject(ViewShot.ERROR_UNABLE_TO_SNAPSHOT, "Failed to snapshot view tag " + tag);
112
- }
113
- }
114
-
115
- @ReactMethod
116
- public void captureScreen(ReadableMap options, Promise promise) {
117
- captureRef(-1, options, promise);
118
- }
119
-
120
- private static final String TEMP_FILE_PREFIX = "ReactNative-snapshot-image";
121
-
122
- /**
123
- * Asynchronous task that cleans up cache dirs (internal and, if available, external) of cropped
124
- * image files. This is run when the catalyst instance is being destroyed (i.e. app is shutting
125
- * down) and when the module is instantiated, to handle the case where the app crashed.
126
- */
127
- private static class CleanTask extends GuardedAsyncTask<Void, Void> implements FilenameFilter {
128
- private final File cacheDir;
129
- private final File externalCacheDir;
130
-
131
- private CleanTask(ReactContext context) {
132
- super(context);
133
-
134
- cacheDir = context.getCacheDir();
135
- externalCacheDir = context.getExternalCacheDir();
136
- }
137
-
138
- @Override
139
- protected void doInBackgroundGuarded(Void... params) {
140
- if (null != cacheDir) {
141
- cleanDirectory(cacheDir);
142
- }
143
-
144
- if (externalCacheDir != null) {
145
- cleanDirectory(externalCacheDir);
146
- }
147
- }
148
-
149
- @Override
150
- public final boolean accept(File dir, String filename) {
151
- return filename.startsWith(TEMP_FILE_PREFIX);
152
- }
153
-
154
- private void cleanDirectory(@NonNull final File directory) {
155
- final File[] toDelete = directory.listFiles(this);
156
-
157
- if (toDelete != null) {
158
- for (File file : toDelete) {
159
- if (file.delete()) {
160
- Log.d(RNVIEW_SHOT, "deleted file: " + file.getAbsolutePath());
161
- }
162
- }
163
- }
164
- }
165
- }
166
-
167
- /**
168
- * Create a temporary file in the cache directory on either internal or external storage,
169
- * whichever is available and has more free space.
170
- */
171
- @NonNull
172
- private File createTempFile(@NonNull final Context context, @NonNull final String ext, String fileName) throws IOException {
173
- final File externalCacheDir = context.getExternalCacheDir();
174
- final File internalCacheDir = context.getCacheDir();
175
- final File cacheDir;
176
-
177
- if (externalCacheDir == null && internalCacheDir == null) {
178
- throw new IOException("No cache directory available");
179
- }
180
-
181
- if (externalCacheDir == null) {
182
- cacheDir = internalCacheDir;
183
- } else if (internalCacheDir == null) {
184
- cacheDir = externalCacheDir;
185
- } else {
186
- cacheDir = externalCacheDir.getFreeSpace() > internalCacheDir.getFreeSpace() ?
187
- externalCacheDir : internalCacheDir;
188
- }
189
-
190
- final String suffix = "." + ext;
191
- if (fileName != null) {
192
- return File.createTempFile(fileName, suffix, cacheDir);
193
- }
194
- return File.createTempFile(TEMP_FILE_PREFIX, suffix, cacheDir);
195
- }
196
- }
@@ -1,199 +0,0 @@
1
- package com.shakebug;
2
-
3
- import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread;
4
-
5
- import android.app.Activity;
6
- import android.media.MediaMetadataRetriever;
7
- import android.net.Uri;
8
- import android.widget.ProgressBar;
9
-
10
- import androidx.annotation.NonNull;
11
- import androidx.annotation.Nullable;
12
- import androidx.appcompat.app.AlertDialog;
13
-
14
- import com.facebook.react.bridge.Arguments;
15
- import com.facebook.react.bridge.LifecycleEventListener;
16
- import com.facebook.react.bridge.Promise;
17
- import com.facebook.react.bridge.ReactApplicationContext;
18
- import com.facebook.react.bridge.ReactContext;
19
- import com.facebook.react.bridge.ReactContextBaseJavaModule;
20
- import com.facebook.react.bridge.ReactMethod;
21
- import com.facebook.react.bridge.ReadableMap;
22
- import com.facebook.react.bridge.WritableMap;
23
- import com.facebook.react.modules.core.DeviceEventManagerModule;
24
- import com.shakebug.interfaces.VideoPlayerListner;
25
- import com.shakebug.widgets.VideoPlayerView;
26
-
27
- public class VideoPlayerModule extends ReactContextBaseJavaModule implements VideoPlayerListner, LifecycleEventListener {
28
-
29
-
30
- private static final String MODULE_NAME = "ShakebugSDKVideoPlayerModule";
31
- private static Boolean isInit = false;
32
- private VideoPlayerView trimmerView;
33
- private AlertDialog alertDialog;
34
- private AlertDialog mProgressDialog;
35
- private ProgressBar mProgressBar;
36
- private int listenerCount = 0;
37
-
38
- private Promise showEditorPromise;
39
-
40
- private boolean enableCancelDialog = true;
41
- private String cancelDialogTitle = "Warning!";
42
- private String cancelDialogMessage = "Are you sure want to cancel?";
43
- private String cancelDialogCancelText = "Close";
44
- private String cancelDialogConfirmText = "Proceed";
45
- // Activity activity = getReactApplicationContext().getCurrentActivity();
46
-
47
- public VideoPlayerModule(ReactApplicationContext reactContext) {
48
- super(reactContext);
49
- }
50
-
51
- @Override
52
- @NonNull
53
- public String getName() {
54
- return MODULE_NAME;
55
- }
56
-
57
-
58
-
59
-
60
-
61
- // From Trimmer
62
-
63
- @ReactMethod
64
- public void showEditor(String videoPath, ReadableMap config) {
65
- // if (trimmerView != null || alertDialog != null) {
66
- // return;
67
- // }
68
-
69
-
70
- if (!isValidVideo(videoPath)) {
71
- WritableMap map = Arguments.createMap();
72
- map.putString("message", "File is not a valid video");
73
- sendEvent(getReactApplicationContext(), "onError", map);
74
- return;
75
- }
76
-
77
- enableCancelDialog = config.hasKey("enableCancelDialog") ? config.getBoolean("enableCancelDialog") : true;
78
- cancelDialogTitle = config.hasKey("cancelDialogTitle") ? config.getString("cancelDialogTitle") : "Warning!";
79
- cancelDialogMessage = config.hasKey("cancelDialogMessage") ? config.getString("cancelDialogMessage") : "Are you sure want to cancel?";
80
- cancelDialogCancelText = config.hasKey("cancelDialogCancelText") ? config.getString("cancelDialogCancelText") : "Close";
81
- cancelDialogConfirmText = config.hasKey("cancelDialogConfirmText") ? config.getString("cancelDialogConfirmText") : "Proceed";
82
-
83
-
84
- Activity activity = getReactApplicationContext().getCurrentActivity();
85
-
86
- if (!isInit) {
87
- init(activity);
88
- isInit = true;
89
- }
90
-
91
- // here is NOT main thread, we need to create VideoTrimmerView on UI thread, so that later we can update it using same thread
92
-
93
- runOnUiThread(() -> {
94
- trimmerView = new VideoPlayerView(getReactApplicationContext(), config, null);
95
- trimmerView.setOnTrimVideoListener(this);
96
- trimmerView.initVideoByURI(Uri.parse(videoPath));
97
-
98
- AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
99
- builder.setCancelable(false);
100
- alertDialog = builder.create();
101
- alertDialog.setView(trimmerView);
102
- alertDialog.show();
103
-
104
- // this is to ensure to release resource if dialog is dismissed in unexpected way (Eg. open control/notification center by dragging from top of screen)
105
- alertDialog.setOnDismissListener(dialog -> {
106
- // This is called in same thread as the trimmer view -> UI thread
107
- if (trimmerView != null) {
108
- trimmerView.onDestroy();
109
- trimmerView = null;
110
- }
111
- sendEvent(getReactApplicationContext(), "onHide", null);
112
- });
113
- sendEvent(getReactApplicationContext(), "onShow", null);
114
- });
115
- }
116
-
117
- private void init(Activity activity) {
118
- isInit = true;
119
- // we have to init this before create videoTrimmerView
120
- BaseUtils.init(getReactApplicationContext());
121
- }
122
-
123
- @Override
124
- public void onHostResume() {
125
-
126
- }
127
-
128
- @Override
129
- public void onHostPause() {
130
-
131
- }
132
-
133
- @Override
134
- public void onHostDestroy() {
135
-
136
- }
137
-
138
-
139
- @Override public void onCancel() {
140
- if (!enableCancelDialog) {
141
- alertDialog.dismiss(); // Dismiss the alertDialog when the close button is clicked
142
- trimmerView.onDestroy(); // Clean up the trimmerView
143
- trimmerView = null;
144
- sendEvent(getReactApplicationContext(), "onCancelPlayer", null);
145
- return;
146
- }
147
-
148
- AlertDialog.Builder builder = new AlertDialog.Builder(getReactApplicationContext().getCurrentActivity());
149
- builder.setMessage(cancelDialogMessage);
150
- builder.setTitle(cancelDialogTitle);
151
- builder.setCancelable(false);
152
- builder.setPositiveButton(cancelDialogConfirmText, (dialog, which) -> {
153
- dialog.cancel();
154
- sendEvent(getReactApplicationContext(), "onCancelPlayer", null);
155
- });
156
- builder.setNegativeButton(cancelDialogCancelText, (dialog, which) -> {
157
- dialog.cancel();
158
- });
159
- AlertDialog alertDialog = builder.create();
160
- alertDialog.show();
161
- }
162
-
163
- @ReactMethod
164
- public void addListener(String eventName) {
165
- // This method is required by React
166
- listenerCount += 1;
167
- }
168
-
169
- @ReactMethod
170
- public void removeListeners(Integer count) {
171
- // This method is required by React
172
- listenerCount -= count;
173
- }
174
-
175
- private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
176
- if (listenerCount > 0) {
177
- WritableMap map = params != null ? params : Arguments.createMap();
178
- map.putString("name", eventName);
179
- reactContext
180
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
181
- .emit("ShakebugSDKVideoPlayer", map);
182
- }
183
- }
184
-
185
-
186
- public boolean isValidVideo(String filePath) {
187
- MediaMetadataRetriever retriever = new MediaMetadataRetriever();
188
-
189
- try {
190
- retriever.setDataSource(getReactApplicationContext(), Uri.parse(filePath));
191
- } catch (Exception e){
192
- e.printStackTrace();
193
- return false;
194
- }
195
-
196
- String hasVideo = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
197
- return "yes".equals(hasVideo);
198
- }
199
- }