scoplan.camera 1.2.5 → 1.2.9

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.
@@ -0,0 +1,14 @@
1
+ ---
2
+ alwaysApply: true
3
+ always_on: true
4
+ trigger: always_on
5
+ applyTo: "**"
6
+ description: Snyk Security At Inception
7
+ ---
8
+
9
+ # Project security best practices
10
+
11
+ - Always run snyk_code_scan tool for new first party code that is generated in a Snyk-supported language.
12
+ - If any security issues are found based on newly introduced or modified code or dependencies, attempt to fix the issues using the results context from Snyk.
13
+ - Rescan the code after fixing the issues to ensure that the issues were fixed and that there are no newly introduced issues.
14
+ - Repeat this process until no new issues are found.
@@ -0,0 +1,3 @@
1
+ {
2
+ "snyk.advanced.autoSelectOrganization": true
3
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scoplan.camera",
3
- "version": "1.2.5",
3
+ "version": "1.2.9",
4
4
  "description": "",
5
5
  "cordova": {
6
6
  "id": "scoplan.camera",
package/plugin.xml CHANGED
@@ -65,7 +65,7 @@
65
65
  <framework custom="true" src="src/android/gradle/scoplanCamera.gradle" type="gradleReference" />
66
66
  <framework src="com.google.android.material:material:1.5.0-alpha02" />
67
67
  <framework src="com.squareup.picasso:picasso:2.8" />
68
- <framework src="io.sentry:sentry-android:7.2.0" />
68
+ <framework src="io.sentry:sentry-android:8.31.0" />
69
69
  <framework src="androidx.constraintlayout:constraintlayout:2.1.4" />
70
70
  <lib-file src="src/android/libs/ds-photo-editor-sdk-v9.aar" />
71
71
  </platform>
@@ -2,6 +2,12 @@ package scoplan.camera;
2
2
 
3
3
  import android.content.Context;
4
4
  import android.os.Bundle;
5
+ import android.view.View;
6
+
7
+ import androidx.core.graphics.Insets;
8
+ import androidx.core.view.ViewCompat;
9
+ import androidx.core.view.WindowCompat;
10
+ import androidx.core.view.WindowInsetsCompat;
5
11
 
6
12
  import com.dsphotoeditor.sdk.activity.DsPhotoEditorCropActivity;
7
13
 
@@ -13,5 +19,16 @@ public class CCropActivity extends DsPhotoEditorCropActivity {
13
19
  int themeId = context.getResources().getIdentifier("AppTheme.NoActionBar", "style", context.getPackageName());
14
20
  setTheme(themeId);
15
21
  super.onCreate(savedInstanceState);
22
+
23
+ WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
24
+
25
+ View rootView = findViewById(com.dsphotoeditor.sdk.R.id.ds_photo_editor_crop_root_layout);
26
+ if (rootView != null) {
27
+ ViewCompat.setOnApplyWindowInsetsListener(rootView, (v, windowInsets) -> {
28
+ Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
29
+ v.setPadding(insets.left, insets.top, insets.right, insets.bottom);
30
+ return WindowInsetsCompat.CONSUMED;
31
+ });
32
+ }
16
33
  }
17
34
  }
@@ -2,6 +2,13 @@ package scoplan.camera;
2
2
 
3
3
  import android.content.Context;
4
4
  import android.os.Bundle;
5
+ import android.view.View;
6
+
7
+ import androidx.core.graphics.Insets;
8
+ import androidx.core.view.ViewCompat;
9
+ import androidx.core.view.WindowCompat;
10
+ import androidx.core.view.WindowInsetsCompat;
11
+
5
12
  import com.dsphotoeditor.sdk.activity.DsPhotoEditorDrawActivity;
6
13
 
7
14
  public class CDrawActivity extends DsPhotoEditorDrawActivity {
@@ -12,5 +19,16 @@ public class CDrawActivity extends DsPhotoEditorDrawActivity {
12
19
  int themeId = context.getResources().getIdentifier("AppTheme.NoActionBar", "style", context.getPackageName());
13
20
  setTheme(themeId);
14
21
  super.onCreate(savedInstanceState);
22
+
23
+ WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
24
+
25
+ View rootView = findViewById(com.dsphotoeditor.sdk.R.id.ds_photo_editor_draw_root_layout);
26
+ if (rootView != null) {
27
+ ViewCompat.setOnApplyWindowInsetsListener(rootView, (v, windowInsets) -> {
28
+ Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
29
+ v.setPadding(insets.left, insets.top, insets.right, insets.bottom);
30
+ return WindowInsetsCompat.CONSUMED;
31
+ });
32
+ }
15
33
  }
16
34
  }
@@ -2,6 +2,13 @@ package scoplan.camera;
2
2
 
3
3
  import android.content.Context;
4
4
  import android.os.Bundle;
5
+ import android.view.View;
6
+
7
+ import androidx.core.graphics.Insets;
8
+ import androidx.core.view.ViewCompat;
9
+ import androidx.core.view.WindowCompat;
10
+ import androidx.core.view.WindowInsetsCompat;
11
+
5
12
  import com.dsphotoeditor.sdk.activity.DsPhotoEditorTextActivity;
6
13
 
7
14
  public class CTextActivity extends DsPhotoEditorTextActivity {
@@ -12,6 +19,17 @@ public class CTextActivity extends DsPhotoEditorTextActivity {
12
19
  int themeId = context.getResources().getIdentifier("AppTheme.NoActionBar", "style", context.getPackageName());
13
20
  setTheme(themeId);
14
21
  super.onCreate(savedInstanceState);
22
+
23
+ WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
24
+
25
+ View rootView = findViewById(com.dsphotoeditor.sdk.R.id.ds_photo_editor_text_sticker_root_layout);
26
+ if (rootView != null) {
27
+ ViewCompat.setOnApplyWindowInsetsListener(rootView, (v, windowInsets) -> {
28
+ Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
29
+ v.setPadding(insets.left, insets.top, insets.right, insets.bottom);
30
+ return WindowInsetsCompat.CONSUMED;
31
+ });
32
+ }
15
33
  }
16
34
  }
17
35
 
@@ -31,6 +31,7 @@ import androidx.activity.result.ActivityResult;
31
31
  import androidx.activity.result.ActivityResultLauncher;
32
32
  import androidx.activity.result.contract.ActivityResultContracts;
33
33
  import androidx.annotation.NonNull;
34
+ import androidx.annotation.Nullable;
34
35
  import androidx.core.app.ActivityCompat;
35
36
  import androidx.core.view.WindowCompat;
36
37
  import androidx.core.view.WindowInsetsControllerCompat;
@@ -41,6 +42,7 @@ import android.os.Handler;
41
42
  import android.os.HandlerThread;
42
43
  import android.util.Log;
43
44
  import android.util.Size;
45
+ import android.view.ContextThemeWrapper;
44
46
  import android.view.LayoutInflater;
45
47
  import android.view.OrientationEventListener;
46
48
  import android.view.Surface;
@@ -66,6 +68,7 @@ import java.util.Arrays;
66
68
  import java.util.Date;
67
69
  import java.util.List;
68
70
 
71
+ import capacitor.cordova.android.plugins.R;
69
72
  import io.sentry.Sentry;
70
73
 
71
74
  public class CameraFragment extends Fragment implements scoplan.camera.OnImageCaptureListener, View.OnClickListener {
@@ -87,7 +90,7 @@ public class CameraFragment extends Fragment implements scoplan.camera.OnImageCa
87
90
  private OrientationEventListener orientationEventListener;
88
91
  private CameraCaptureSession cameraCaptureSessions;
89
92
  private CaptureRequest.Builder captureRequestBuilder;
90
- private String cameraId;
93
+ private String cameraId = null;
91
94
  private int CAMERA_REQUEST_PERMISSION = 5000;
92
95
  private Handler mBackgroundHandler;
93
96
  private HandlerThread mBackgroundThread;
@@ -101,17 +104,41 @@ public class CameraFragment extends Fragment implements scoplan.camera.OnImageCa
101
104
  private int currentOrientation = -1;
102
105
  private boolean flashOn = false;
103
106
  private boolean cameraIsOpen = false;
104
-
107
+ private boolean previewReady = false;
105
108
  private SurfaceHolder mSurfaceHolder;
106
109
 
107
110
  private boolean surfaceAvailable = false;
111
+ private boolean surfaceSizeConfigured = false;
112
+ private Size optimalPreviewSize = null;
108
113
  private LinearLayout cameraFrameLayout;
114
+ private boolean callbackAdded = false;
115
+
116
+ @Override
117
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
118
+ super.onViewCreated(view, savedInstanceState);
119
+ if (!callbackAdded) {
120
+ mSurfaceHolder.addCallback(surfaceHolderCallBack);
121
+ callbackAdded = true;
122
+ }
123
+
124
+ // Apply window insets to root view to handle system bars
125
+ androidx.core.view.ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> {
126
+ androidx.core.graphics.Insets insets = windowInsets.getInsets(androidx.core.view.WindowInsetsCompat.Type.systemBars());
127
+
128
+ // Apply padding to root view
129
+ v.setPadding(insets.left, insets.top, insets.right, insets.bottom);
130
+
131
+ return androidx.core.view.WindowInsetsCompat.CONSUMED;
132
+ });
133
+ }
109
134
 
110
135
  private final SurfaceHolder.Callback surfaceHolderCallBack = new SurfaceHolder.Callback() {
111
136
  @Override
112
137
  public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
113
138
  surfaceAvailable = true;
114
- openCamera();
139
+ if(!cameraIsOpen) {
140
+ openCamera();
141
+ }
115
142
  }
116
143
 
117
144
  @Override
@@ -121,8 +148,8 @@ public class CameraFragment extends Fragment implements scoplan.camera.OnImageCa
121
148
 
122
149
  @Override
123
150
  public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
124
- //TODO close
125
151
  surfaceAvailable = false;
152
+ surfaceSizeConfigured = false;
126
153
  release();
127
154
  }
128
155
  };
@@ -132,19 +159,24 @@ public class CameraFragment extends Fragment implements scoplan.camera.OnImageCa
132
159
  public void onOpened(@NonNull CameraDevice camera) {
133
160
  cameraDevice = camera;
134
161
  cameraIsOpen = true;
135
- createCameraPreview();
162
+ // Configure surface size first; surfaceChanged will trigger createCameraPreview
163
+ configureSurfaceSize();
136
164
  }
137
165
 
138
166
  @Override
139
- public void onDisconnected(@NonNull CameraDevice cameraDevice) {
140
- cameraDevice.close();
167
+ public void onDisconnected(@NonNull CameraDevice camera) {
141
168
  cameraIsOpen = false;
169
+ previewReady = false;
170
+ surfaceSizeConfigured = false;
142
171
  release();
143
172
  }
144
173
 
145
174
  @Override
146
- public void onError(@NonNull CameraDevice cameraDevice, int i) {
147
- cameraDevice.close();
175
+ public void onError(@NonNull CameraDevice camera, int error) {
176
+ Log.e(SCOPLAN_TAG, "CameraDevice.StateCallback onError: " + error);
177
+ cameraIsOpen = false;
178
+ previewReady = false;
179
+ surfaceSizeConfigured = false;
148
180
  release();
149
181
  }
150
182
  };
@@ -198,13 +230,29 @@ public class CameraFragment extends Fragment implements scoplan.camera.OnImageCa
198
230
  }
199
231
  };
200
232
 
201
- this.fakeR = new FakeR(getContext());
233
+ this.fakeR = new FakeR(requireActivity());
234
+ }
235
+
236
+ private int findStyleResId(Context context, String styleName) {
237
+ // Convert dotted names (AppTheme.NoActionBar) to compiled names (AppTheme_NoActionBar)
238
+ String compiledName = styleName.replace('.', '_');
239
+
240
+ int resId = context.getResources().getIdentifier(
241
+ compiledName,
242
+ "style",
243
+ context.getPackageName()
244
+ );
245
+
246
+ return resId;
202
247
  }
203
248
 
204
249
  @Override
205
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
250
+ public View onCreateView(LayoutInflater outerInflater, ViewGroup container,
206
251
  Bundle savedInstanceState) {
207
252
  // Inflate the layout for this fragment
253
+ int themeId = getActivity().getResources().getIdentifier("AppTheme.fullscreen", "style", getActivity().getPackageName());
254
+ ContextThemeWrapper themeContext = new ContextThemeWrapper(requireActivity(), themeId);
255
+ LayoutInflater inflater = outerInflater.cloneInContext(themeContext);
208
256
  View view = inflater.inflate(this.fakeR.getLayout("fragment_camera"), container, false);
209
257
  camButton = view.findViewById(this.fakeR.getId("button_capture"));
210
258
  zoomBar = view.findViewById(this.fakeR.getId("camera_zoom"));
@@ -217,7 +265,6 @@ public class CameraFragment extends Fragment implements scoplan.camera.OnImageCa
217
265
  drawOn = view.findViewById(this.fakeR.getId("draw_on"));
218
266
  assert surfaceView != null;
219
267
  mSurfaceHolder = surfaceView.getHolder();
220
- mSurfaceHolder.addCallback(surfaceHolderCallBack);
221
268
  souche = view.findViewById(this.fakeR.getId("image_souche"));
222
269
  cameraTopBar = view.findViewById(this.fakeR.getId("cameraTopBar"));
223
270
  cancelBtn = view.findViewById(this.fakeR.getId("cancel"));
@@ -233,51 +280,107 @@ public class CameraFragment extends Fragment implements scoplan.camera.OnImageCa
233
280
  validationButton.setOnClickListener(this);
234
281
  flashBtn.setOnClickListener(this);
235
282
  this.defineViewVisibility();
283
+
236
284
  return view;
237
285
  }
238
286
 
287
+ private void configureSurfaceSize() {
288
+ if (cameraId == null || getActivity() == null) {
289
+ // Can't configure yet, fall back to direct preview
290
+ surfaceSizeConfigured = true;
291
+ createCameraPreview();
292
+ return;
293
+ }
294
+ try {
295
+ CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
296
+ StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
297
+ if (map != null) {
298
+ Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);
299
+ optimalPreviewSize = selectedPreviewSize(outputSizes);
300
+ if (optimalPreviewSize != null && cameraFrameLayout.getWidth() > 0) {
301
+ Size displaySize = calculateSurfaceSize(optimalPreviewSize);
302
+ getActivity().runOnUiThread(() -> {
303
+ if (!isAdded()) return;
304
+ ViewGroup.LayoutParams layoutParams = surfaceView.getLayoutParams();
305
+ layoutParams.width = displaySize.getWidth();
306
+ layoutParams.height = displaySize.getHeight();
307
+ surfaceView.setLayoutParams(layoutParams);
308
+ // Set buffer to camera native resolution
309
+ mSurfaceHolder.setFixedSize(optimalPreviewSize.getWidth(), optimalPreviewSize.getHeight());
310
+ // surfaceChanged will be called → createCameraPreview
311
+ surfaceSizeConfigured = true;
312
+ });
313
+ return;
314
+ }
315
+ }
316
+ } catch (Exception e) {
317
+ Log.e(SCOPLAN_TAG, "Error configuring surface size", e);
318
+ }
319
+ // Fallback: proceed directly
320
+ surfaceSizeConfigured = true;
321
+ createCameraPreview();
322
+ }
323
+
239
324
  private void createCameraPreview() {
240
- if(cameraDevice == null || !surfaceAvailable)
325
+ if(cameraDevice == null || !surfaceAvailable || previewReady || !surfaceSizeConfigured)
241
326
  return;
242
327
  if(!cameraIsOpen) {
243
328
  this.openCamera();
329
+ return;
244
330
  }
331
+ previewReady = true;
245
332
  try{
333
+ // Close any existing session before creating a new one
334
+ closeSession();
335
+
246
336
  Surface surface = mSurfaceHolder.getSurface();
247
337
  captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
248
338
  captureRequestBuilder.addTarget(surface);
249
339
  cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
250
340
  @Override
251
341
  public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
252
- if(cameraDevice == null)
342
+ if(cameraDevice == null) {
343
+ previewReady = false;
253
344
  return;
345
+ }
254
346
  cameraCaptureSessions = cameraCaptureSession;
255
347
  updatePreview();
256
348
  }
257
349
 
258
350
  @Override
259
351
  public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
260
- Sentry.captureMessage(cameraCaptureSession.toString());
352
+ Log.e(SCOPLAN_TAG, "onConfigureFailed for preview session");
353
+ Sentry.captureMessage("onConfigureFailed: " + cameraCaptureSession.toString());
354
+ previewReady = false;
355
+ failedCapture();
261
356
  }
262
- },null);
357
+ }, mBackgroundHandler);
263
358
  } catch (Exception e) {
359
+ Log.e(SCOPLAN_TAG, "Error creating camera preview", e);
264
360
  Sentry.captureException(e);
361
+ previewReady = false;
265
362
  this.failedCapture();
266
363
  }
267
364
  }
268
365
 
269
366
  private void openCamera() {
270
367
  try{
368
+ if (cameraId != null ) {
369
+ return;
370
+ }
271
371
  for (String id : manager.getCameraIdList()) {
272
372
  CameraCharacteristics cameraCharacteristics =
273
373
  manager.getCameraCharacteristics(id);
274
- if (cameraCharacteristics.get(cameraCharacteristics.LENS_FACING) ==
374
+ if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) ==
275
375
  CameraCharacteristics.LENS_FACING_BACK) {
276
376
  cameraId = id;
277
377
  break;
278
378
  }
279
379
  }
280
- cameraId = manager.getCameraIdList()[0];
380
+ // Fallback to first camera only if no back camera was found
381
+ if (cameraId == null) {
382
+ cameraId = manager.getCameraIdList()[0];
383
+ }
281
384
  CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
282
385
  StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
283
386
  assert map != null;
@@ -310,28 +413,9 @@ public class CameraFragment extends Fragment implements scoplan.camera.OnImageCa
310
413
  }
311
414
 
312
415
  private void updatePreview() {
313
- if(cameraDevice == null)
314
- Log.e(SCOPLAN_TAG, "Error camera");
315
- captureRequestBuilder.set(CaptureRequest.CONTROL_MODE,CaptureRequest.CONTROL_MODE_AUTO);
316
- try{
317
- CameraCharacteristics cameraCharacteristics =
318
- manager.getCameraCharacteristics(cameraId);
319
- StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
320
- if(map != null) {
321
- Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);
322
- Size preferredSize = selectedPreviewSize(outputSizes);
323
- if(preferredSize != null) {
324
- ViewGroup.LayoutParams layoutParams = surfaceView.getLayoutParams();
325
- Size surfaceSize = calculateSurfaceSize(preferredSize);
326
- getActivity().runOnUiThread(() -> {
327
- layoutParams.width = surfaceSize.getWidth();
328
- layoutParams.height = surfaceSize.getHeight();
329
- mSurfaceHolder.setFixedSize(surfaceSize.getWidth(), surfaceSize.getHeight());
330
- surfaceView.setLayoutParams(layoutParams);
331
- });
332
- }
333
- }
334
- cameraCaptureSessions.setRepeatingRequest(captureRequestBuilder.build(),null, mBackgroundHandler);
416
+ captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
417
+ try {
418
+ cameraCaptureSessions.setRepeatingRequest(captureRequestBuilder.build(), null, mBackgroundHandler);
335
419
  this.cameraSeekBarListener = new scoplan.camera.CameraSeekBarListener(cameraId, manager, zoomBar, captureRequestBuilder, cameraCaptureSessions, mBackgroundHandler);
336
420
  } catch (Exception e) {
337
421
  Sentry.captureException(e);
@@ -341,23 +425,54 @@ public class CameraFragment extends Fragment implements scoplan.camera.OnImageCa
341
425
 
342
426
  private Size calculateSurfaceSize(Size selectedSize) {
343
427
  int parentWidth = this.cameraFrameLayout.getWidth();
344
- Size correctSize = new Size(selectedSize.getHeight(), selectedSize.getWidth()); // We are in portrait mode
428
+ int parentHeight = this.cameraFrameLayout.getHeight();
429
+
430
+ // Camera sensor is landscape, but we display in portrait → swap W/H
431
+ float previewAspectRatio = (float) selectedSize.getHeight() / selectedSize.getWidth();
345
432
 
346
- float aspectRatio = (float) correctSize.getWidth() / correctSize.getHeight();
433
+ // Center-crop: fill the entire parent, crop overflow (no distortion)
434
+ int surfaceWidth, surfaceHeight;
435
+ int fitWidth = parentWidth;
436
+ int fitHeight = (int) (parentWidth / previewAspectRatio);
437
+
438
+ if (fitHeight >= parentHeight) {
439
+ // Fit by width already fills the height — use it
440
+ surfaceWidth = fitWidth;
441
+ surfaceHeight = fitHeight;
442
+ } else {
443
+ // Fit by height and let width overflow
444
+ surfaceHeight = parentHeight;
445
+ surfaceWidth = (int) (parentHeight * previewAspectRatio);
446
+ }
347
447
 
348
- int height = (int) (parentWidth / aspectRatio);
349
- return new Size(parentWidth, height);
448
+ return new Size(surfaceWidth, surfaceHeight);
350
449
  }
351
450
 
352
451
  private Size selectedPreviewSize(Size[] outputSizes) {
353
- float targetAspectRatio = 1f; // For example, 16:9 aspect ratio
354
- // Choose a suitable size from outputSizes based on the aspect ratio
452
+ // Target 4:3 aspect ratio (landscape sensor: width > height, so 4/3 ≈ 1.333)
453
+ float targetAspectRatio = 4f / 3f;
355
454
  Size selectedSize = null;
455
+ int bestArea = 0;
456
+
356
457
  for (Size size : outputSizes) {
357
458
  float aspectRatio = (float) size.getWidth() / size.getHeight();
358
- if (Math.abs(aspectRatio - targetAspectRatio) < 0.1) {
359
- selectedSize = size;
360
- break;
459
+ if (Math.abs(aspectRatio - targetAspectRatio) < 0.05) {
460
+ // Among matching ratios, pick the largest for best quality
461
+ int area = size.getWidth() * size.getHeight();
462
+ if (area > bestArea) {
463
+ bestArea = area;
464
+ selectedSize = size;
465
+ }
466
+ }
467
+ }
468
+
469
+ // Fallback: if no 4:3, pick the largest size available
470
+ if (selectedSize == null && outputSizes.length > 0) {
471
+ selectedSize = outputSizes[0];
472
+ for (Size size : outputSizes) {
473
+ if (size.getWidth() * size.getHeight() > selectedSize.getWidth() * selectedSize.getHeight()) {
474
+ selectedSize = size;
475
+ }
361
476
  }
362
477
  }
363
478
  return selectedSize;
@@ -367,23 +482,22 @@ public class CameraFragment extends Fragment implements scoplan.camera.OnImageCa
367
482
  public void onResume() {
368
483
  super.onResume();
369
484
  startBackgroundThread();
370
- if(surfaceAvailable)
485
+ if(surfaceAvailable && !cameraIsOpen) {
371
486
  openCamera();
372
- else {
373
- mSurfaceHolder.addCallback(surfaceHolderCallBack);
374
487
  }
375
488
  this.orientationEventListener.enable();
376
489
 
377
490
  Window window = requireActivity().getWindow();
378
491
 
492
+ // Enable edge-to-edge mode but keep system bars visible
493
+ WindowCompat.setDecorFitsSystemWindows(window, false);
494
+
379
495
  WindowInsetsControllerCompat controller =
380
496
  WindowCompat.getInsetsController(window, window.getDecorView());
381
497
 
382
498
  if (controller != null) {
383
- controller.hide(WindowInsets.Type.statusBars());
384
- controller.setSystemBarsBehavior(
385
- WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
386
- );
499
+ // Show status and navigation bars
500
+ controller.show(WindowInsets.Type.systemBars());
387
501
  }
388
502
  }
389
503
 
@@ -393,11 +507,15 @@ public class CameraFragment extends Fragment implements scoplan.camera.OnImageCa
393
507
  super.onPause();
394
508
  this.orientationEventListener.disable();
395
509
  Window window = requireActivity().getWindow();
510
+
511
+ // Restore normal window mode when leaving camera
512
+ WindowCompat.setDecorFitsSystemWindows(window, true);
513
+
396
514
  WindowInsetsControllerCompat controller =
397
515
  WindowCompat.getInsetsController(window, window.getDecorView());
398
516
 
399
517
  if (controller != null) {
400
- controller.show(WindowInsets.Type.statusBars());
518
+ controller.show(WindowInsets.Type.systemBars());
401
519
  }
402
520
  release();
403
521
  }
@@ -408,23 +526,44 @@ public class CameraFragment extends Fragment implements scoplan.camera.OnImageCa
408
526
  release();
409
527
  }
410
528
 
529
+ private void closeSession() {
530
+ if(cameraCaptureSessions != null) {
531
+ try {
532
+ cameraCaptureSessions.close();
533
+ } catch (Exception e) {
534
+ Log.e(SCOPLAN_TAG, "Error closing capture session", e);
535
+ }
536
+ cameraCaptureSessions = null;
537
+ }
538
+ }
539
+
411
540
  private void release() {
541
+ closeSession();
412
542
  if(cameraDevice != null) {
413
- cameraDevice.close();
543
+ try {
544
+ cameraDevice.close();
545
+ } catch (Exception e) {
546
+ Log.e(SCOPLAN_TAG, "Error closing camera device", e);
547
+ }
414
548
  cameraIsOpen = false;
549
+ previewReady = false;
415
550
  cameraDevice = null;
551
+ cameraId = null;
416
552
  }
417
553
  }
418
554
 
419
555
  private void stopBackgroundThread() {
556
+ if (mBackgroundThread == null) {
557
+ return;
558
+ }
420
559
  mBackgroundThread.quitSafely();
421
560
  try{
422
561
  mBackgroundThread.join();
423
- mBackgroundThread= null;
424
- mBackgroundHandler = null;
425
562
  } catch (InterruptedException e) {
426
- e.printStackTrace();
563
+ Log.e(SCOPLAN_TAG, "Error stopping background thread", e);
427
564
  }
565
+ mBackgroundThread = null;
566
+ mBackgroundHandler = null;
428
567
  }
429
568
 
430
569
  private void startBackgroundThread() {
@@ -442,15 +581,18 @@ public class CameraFragment extends Fragment implements scoplan.camera.OnImageCa
442
581
  }
443
582
 
444
583
  private void takePicture() {
584
+ if(getContext() == null) {
585
+ return;
586
+ }
445
587
  if(this.pictureCount >= this.photoLimit){
446
588
  Toast toast = Toast.makeText(this.getContext(), "La prise de photos est limitée à " + this.photoLimit + " par envoi", Toast.LENGTH_SHORT);
447
589
  toast.show();
448
590
  return;
449
591
  }
450
- if(this.mBackgroundThread == null || !this.mBackgroundThread.isInterrupted() || !this.mBackgroundThread.isAlive()) {
592
+ if(this.mBackgroundThread == null || !this.mBackgroundThread.isAlive()) {
451
593
  this.startBackgroundThread();
452
594
  }
453
- if(cameraDevice == null) {
595
+ if(cameraDevice == null || cameraId == null) {
454
596
  return;
455
597
  }
456
598
  this.pictureCount++;
@@ -459,9 +601,12 @@ public class CameraFragment extends Fragment implements scoplan.camera.OnImageCa
459
601
  try {
460
602
  CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
461
603
  Size[] jpegSizes = null;
462
- if(characteristics != null)
463
- jpegSizes = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
464
- .getOutputSizes(ImageFormat.JPEG);
604
+ if(characteristics != null) {
605
+ StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
606
+ if (map != null) {
607
+ jpegSizes = map.getOutputSizes(ImageFormat.JPEG);
608
+ }
609
+ }
465
610
  //Capture image with custom size
466
611
  int width = 640;
467
612
  int height = 480;
@@ -470,7 +615,7 @@ public class CameraFragment extends Fragment implements scoplan.camera.OnImageCa
470
615
  width = jpegSizes[0].getWidth();
471
616
  height = jpegSizes[0].getHeight();
472
617
  }
473
- final ImageReader reader = ImageReader.newInstance(width, height, ImageFormat.JPEG,1);
618
+ final ImageReader reader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 1);
474
619
  List<Surface> outputSurface = new ArrayList<>(2);
475
620
  outputSurface.add(reader.getSurface());
476
621
  outputSurface.add(mSurfaceHolder.getSurface());
@@ -480,7 +625,7 @@ public class CameraFragment extends Fragment implements scoplan.camera.OnImageCa
480
625
  captureBuilder.set(CaptureRequest.FLASH_MODE, flashOn ? CaptureRequest.FLASH_MODE_TORCH : CaptureRequest.FLASH_MODE_OFF);
481
626
  String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
482
627
  String fileName = "IMG_"+ timeStamp + ".jpg";
483
- File file = new File( this.getContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName);
628
+ File file = new File(this.getContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName);
484
629
  ImageReader.OnImageAvailableListener readerListener = new scoplan.camera.ImageCameraAvailableListener(file, reader, this, currentOrientation);
485
630
 
486
631
  reader.setOnImageAvailableListener(readerListener, mBackgroundHandler);
@@ -488,14 +633,18 @@ public class CameraFragment extends Fragment implements scoplan.camera.OnImageCa
488
633
  @Override
489
634
  public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
490
635
  super.onCaptureCompleted(session, request, result);
636
+ reader.close();
637
+ previewReady = false;
491
638
  createCameraPreview();
492
639
  }
493
640
 
494
641
  @Override
495
642
  public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
496
643
  super.onCaptureFailed(session, request, failure);
644
+ reader.close();
497
645
  Sentry.captureMessage("Failed capture _" + failure.getReason());
498
646
  Log.e(SCOPLAN_TAG, "Error FAILED Capture " + failure.getReason());
647
+ pictureCount--;
499
648
  failedCapture();
500
649
  }
501
650
  };
@@ -508,43 +657,59 @@ public class CameraFragment extends Fragment implements scoplan.camera.OnImageCa
508
657
  } catch (Exception e) {
509
658
  Log.e(SCOPLAN_TAG, "Error - " + e.getMessage());
510
659
  Sentry.captureException(e);
660
+ reader.close();
661
+ pictureCount--;
511
662
  failedCapture();
512
663
  }
513
664
  }
514
665
 
515
666
  @Override
516
667
  public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
517
- Log.e(SCOPLAN_TAG, "Error FAILED Configuration ");
518
- Sentry.captureMessage("Failed configure cameraCaptureSession");
668
+ Log.e(SCOPLAN_TAG, "Error FAILED Configuration for capture session");
669
+ Sentry.captureMessage("Failed configure capture session");
670
+ reader.close();
671
+ pictureCount--;
519
672
  failedCapture();
520
673
  }
521
674
  }, mBackgroundHandler);
522
675
 
523
676
  } catch (Exception e) {
677
+ Log.e(SCOPLAN_TAG, "Error in takePicture", e);
524
678
  Sentry.captureException(e);
525
- throw new RuntimeException(e);
679
+ pictureCount--;
680
+ failedCapture();
526
681
  }
527
682
  }
528
683
 
529
684
  @Override
530
685
  public void onImageCapture(File file, Bitmap bitmap) {
531
686
  pictures.add(file.getAbsolutePath());
532
- getActivity().runOnUiThread(() -> {
533
- souche.setImageBitmap(bitmap);
534
- defineViewVisibility();
535
- });
687
+ if (getActivity() != null && isAdded()) {
688
+ getActivity().runOnUiThread(() -> {
689
+ if (isAdded()) {
690
+ souche.setImageBitmap(bitmap);
691
+ defineViewVisibility();
692
+ }
693
+ });
694
+ }
536
695
  }
537
696
 
538
697
  @Override
539
698
  public void onImageBuildFailed(Exception e) {
540
699
  Sentry.captureException(e);
700
+ pictureCount--;
541
701
  failedCapture();
542
702
  }
543
703
 
544
704
  public void failedCapture() {
545
- Toast toast = Toast.makeText(getContext(), "Oups! une erreur pendant la capture. Veuillez rééssayer.", Toast.LENGTH_LONG);
546
- toast.show();
705
+ if (getActivity() == null || !isAdded()) {
706
+ return;
707
+ }
547
708
  getActivity().runOnUiThread(() -> {
709
+ if (!isAdded() || getContext() == null) {
710
+ return;
711
+ }
712
+ Toast.makeText(getContext(), "Oups! une erreur pendant la capture. Veuillez rééssayer.", Toast.LENGTH_LONG).show();
548
713
  defineViewVisibility();
549
714
  });
550
715
  }
@@ -29,33 +29,43 @@ public class ImageCameraAvailableListener implements ImageReader.OnImageAvailabl
29
29
  Image image = null;
30
30
  try{
31
31
  image = reader.acquireLatestImage();
32
+ if (image == null) {
33
+ this.imageCaptureListener.onImageBuildFailed(new IOException("Failed to acquire image"));
34
+ return;
35
+ }
32
36
  ByteBuffer buffer = image.getPlanes()[0].getBuffer();
33
37
  byte[] bytes = new byte[buffer.capacity()];
34
38
  buffer.get(bytes);
35
39
  this.imageCaptureListener.onImageCapture(file, save(bytes));
36
40
  }
37
- catch (IOException e)
41
+ catch (Exception e)
38
42
  {
39
43
  e.printStackTrace();
40
44
  this.imageCaptureListener.onImageBuildFailed(e);
41
45
  }
42
46
  finally {
43
- {
44
- if(image != null)
45
- image.close();
46
- }
47
+ if(image != null)
48
+ image.close();
47
49
  }
48
50
  }
49
51
 
50
52
  private Bitmap save(byte[] bytes) throws IOException {
51
53
  OutputStream outputStream = null;
54
+ Bitmap bmp = null;
52
55
  try{
53
56
  outputStream = new FileOutputStream(file);
54
- Bitmap bmp = BitmapFactory.decodeByteArray(bytes,0, bytes.length);
57
+ bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
58
+ if (bmp == null) {
59
+ throw new IOException("Failed to decode image bytes");
60
+ }
55
61
  Matrix matrix = new Matrix();
56
62
  matrix.setRotate(this.rotation);
57
63
  Bitmap rotatedBitmap = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), matrix, true);
58
64
  rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 65, outputStream);
65
+ // Recycle original bitmap only if rotation created a new one
66
+ if (rotatedBitmap != bmp) {
67
+ bmp.recycle();
68
+ }
59
69
  return rotatedBitmap;
60
70
  } finally {
61
71
  if(outputStream != null)
@@ -13,6 +13,11 @@ import android.widget.FrameLayout;
13
13
  import android.widget.RelativeLayout;
14
14
  import android.widget.Toast;
15
15
 
16
+ import androidx.core.graphics.Insets;
17
+ import androidx.core.view.ViewCompat;
18
+ import androidx.core.view.WindowCompat;
19
+ import androidx.core.view.WindowInsetsCompat;
20
+
16
21
  import com.dsphotoeditor.sdk.activity.DsPhotoEditorActivity;
17
22
 
18
23
  import java.io.File;
@@ -28,6 +33,10 @@ public class PhotoEditorActivity extends DsPhotoEditorActivity {
28
33
  int themeId = context.getResources().getIdentifier("AppTheme.NoActionBar", "style", context.getPackageName());
29
34
  setTheme(themeId);
30
35
  super.onCreate(savedInstanceState);
36
+
37
+ // Enable edge-to-edge and handle system bars
38
+ WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
39
+
31
40
  this.fakeR = new FakeR(this);
32
41
  this.e = this.getIntent().getData();
33
42
  if (this.e == null) {
@@ -36,6 +45,16 @@ public class PhotoEditorActivity extends DsPhotoEditorActivity {
36
45
  } else {
37
46
  this.createBottomBar(savedInstanceState);
38
47
  }
48
+
49
+ // Apply window insets to root layout
50
+ View rootView = findViewById(fakeR.getId("ds_photo_editor_root_layout"));
51
+ if (rootView != null) {
52
+ ViewCompat.setOnApplyWindowInsetsListener(rootView, (v, windowInsets) -> {
53
+ Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
54
+ v.setPadding(insets.left, insets.top, insets.right, insets.bottom);
55
+ return WindowInsetsCompat.CONSUMED;
56
+ });
57
+ }
39
58
  }
40
59
 
41
60
  @SuppressLint("ResourceType")
@@ -1,6 +1,10 @@
1
1
  package scoplan.camera;
2
2
 
3
3
  import androidx.core.content.ContextCompat;
4
+ import androidx.core.graphics.Insets;
5
+ import androidx.core.view.ViewCompat;
6
+ import androidx.core.view.WindowCompat;
7
+ import androidx.core.view.WindowInsetsCompat;
4
8
 
5
9
  import android.app.AlertDialog;
6
10
  import android.content.Context;
@@ -53,6 +57,18 @@ public class PhotoEditorMesureCustomActivity extends DsPhotoEditorTextActivity i
53
57
  super.onCreate(var1);
54
58
  this.fakeR = new FakeR(this);
55
59
  this.setContentView(com.dsphotoeditor.sdk.R.layout.activity_ds_photo_editor_sticker_text);
60
+
61
+ WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
62
+
63
+ View rootView = findViewById(com.dsphotoeditor.sdk.R.id.ds_photo_editor_text_sticker_root_layout);
64
+ if (rootView != null) {
65
+ ViewCompat.setOnApplyWindowInsetsListener(rootView, (v, windowInsets) -> {
66
+ Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
67
+ v.setPadding(insets.left, insets.top, insets.right, insets.bottom);
68
+ return WindowInsetsCompat.CONSUMED;
69
+ });
70
+ }
71
+
56
72
  if (original != null && !original.isRecycled()) {
57
73
  this.a();
58
74
  this.b();
@@ -1,5 +1,6 @@
1
1
  package scoplan.camera;
2
2
 
3
+ import android.content.Context;
3
4
  import android.content.pm.ActivityInfo;
4
5
  import android.widget.FrameLayout;
5
6
 
@@ -38,20 +39,21 @@ public class ScoplanCamera extends CordovaPlugin implements CameraEventListener
38
39
 
39
40
  private void takePictures(int limit, int currentCount) {
40
41
  cordova.getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
41
- CameraEventListener cameraEventListener = this;
42
+ scoplan.camera.CameraEventListener cameraEventListener = this;
42
43
  cordova.getActivity().runOnUiThread(new Runnable() {
43
44
  @Override
44
45
  public void run() {
45
46
  containerView = (FrameLayout)cordova.getActivity().findViewById(containerViewId);
46
47
  if(containerView == null){
47
- containerView = new FrameLayout(cordova.getActivity().getApplicationContext());
48
+ Context context = cordova.getActivity();
49
+ containerView = new FrameLayout(context);
48
50
  containerView.setId(containerViewId);
49
51
  FrameLayout.LayoutParams containerLayoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
50
52
  cordova.getActivity().addContentView(containerView, containerLayoutParams);
51
53
  //add the fragment to the container
52
54
  }
53
55
  containerView.setClickable(true);
54
- cameraFragment = new CameraFragment();
56
+ cameraFragment = new scoplan.camera.CameraFragment();
55
57
  if(currentCount > 0){
56
58
  cameraFragment.setCurrentCount(currentCount);
57
59
  }
@@ -2,7 +2,7 @@
2
2
  <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
3
  android:layout_width="match_parent"
4
4
  android:layout_height="match_parent"
5
- android:background="@drawable/shape_rectangle">
5
+ android:background="#000000">
6
6
  <LinearLayout
7
7
  android:id="@+id/camera_frame_layout"
8
8
  android:layout_width="match_parent"