diff --git a/android/app/src/main/kotlin/com/dronex/rec/recording/RecordingCameraController.kt b/android/app/src/main/kotlin/com/dronex/rec/recording/RecordingCameraController.kt index e4b8cf8..44c1408 100644 --- a/android/app/src/main/kotlin/com/dronex/rec/recording/RecordingCameraController.kt +++ b/android/app/src/main/kotlin/com/dronex/rec/recording/RecordingCameraController.kt @@ -32,6 +32,7 @@ class RecordingCameraController( private var camera: Camera? = null private var mainCameraId: String? = null private var ultraWideCameraId: String? = null + private var ultraWideZoomRatio: Float = DEFAULT_ULTRA_WIDE_ZOOM_RATIO private var currentLensMode: LensMode = LensMode.MAIN private var activeRecording: Recording? = null private var boundLifecycleOwner: LifecycleOwner? = null @@ -224,11 +225,16 @@ class RecordingCameraController( fun zoomCapabilitiesMap(): Map { val zoomState = camera?.cameraInfo?.zoomState?.value - val minZoom = if (hasUltraWideCamera()) 0.5f else (zoomState?.minZoomRatio ?: 1f) + val minZoom = + if (hasUltraWideCamera()) { + ultraWideZoomRatio + } else { + zoomState?.minZoomRatio ?: 1f + } val maxZoom = zoomState?.maxZoomRatio ?: 3f val zoom = if (currentLensMode == LensMode.ULTRA_WIDE) { - 0.5f + ultraWideZoomRatio } else { (zoomState?.zoomRatio ?: currentZoomRatio).coerceIn(minZoom, maxZoom) } @@ -248,7 +254,7 @@ class RecordingCameraController( if (boundCamera == null) { val clamped = if (ratio < 1.0 && hasUltraWideCamera()) { - 0.5f + ultraWideZoomRatio } else { ratio.toFloat().coerceAtLeast(1f) } @@ -299,6 +305,7 @@ class RecordingCameraController( boundLifecycleOwner = null currentLensMode = LensMode.MAIN currentZoomRatio = 1f + ultraWideZoomRatio = DEFAULT_ULTRA_WIDE_ZOOM_RATIO updateStatus(RecordingStatus(RecordingState.IDLE)) } @@ -315,7 +322,7 @@ class RecordingCameraController( private fun applyCurrentZoom() { val boundCamera = camera ?: return if (currentLensMode == LensMode.ULTRA_WIDE) { - currentZoomRatio = 0.5f + currentZoomRatio = ultraWideZoomRatio boundCamera.cameraControl.setZoomRatio(1f) return } @@ -334,12 +341,18 @@ class RecordingCameraController( if (mainCameraId == null) { mainCameraId = cameraIdForSelector(provider, CameraSelector.DEFAULT_BACK_CAMERA) } - ultraWideCameraId = findUltraWideCameraId(provider, mainCameraId) - if (ultraWideCameraId == null && currentLensMode == LensMode.ULTRA_WIDE) { + val ultraWideCamera = findUltraWideCamera(provider, mainCameraId) + ultraWideCameraId = ultraWideCamera?.cameraId + ultraWideZoomRatio = ultraWideCamera?.zoomRatio ?: DEFAULT_ULTRA_WIDE_ZOOM_RATIO + if (ultraWideCamera == null && currentLensMode == LensMode.ULTRA_WIDE) { currentLensMode = LensMode.MAIN currentZoomRatio = 1f } - Log.d(TAG, "mainCameraId=$mainCameraId ultraWideCameraId=$ultraWideCameraId") + Log.d( + TAG, + "mainCameraId=$mainCameraId ultraWideCameraId=$ultraWideCameraId " + + "ultraWideZoomRatio=$ultraWideZoomRatio", + ) } private fun cameraIdForSelector( @@ -355,10 +368,10 @@ class RecordingCameraController( } } - private fun findUltraWideCameraId( + private fun findUltraWideCamera( provider: ProcessCameraProvider, excludedCameraId: String?, - ): String? { + ): UltraWideCamera? { val manager = appContext.getSystemService(Context.CAMERA_SERVICE) as CameraManager val candidates = manager.cameraIdList @@ -373,13 +386,17 @@ class RecordingCameraController( val mainProfile = excludedCameraId?.let { backCameraProfile(manager, it) } val widest = candidates.firstOrNull() ?: return null if (mainProfile == null) { - return widest.cameraId + return UltraWideCamera(widest.cameraId, DEFAULT_ULTRA_WIDE_ZOOM_RATIO) } val meaningfullyWider = widest.horizontalFov > mainProfile.horizontalFov * ULTRA_WIDE_FOV_FACTOR || widest.minFocalLength < mainProfile.minFocalLength * ULTRA_WIDE_FOCAL_FACTOR - return if (meaningfullyWider) widest.cameraId else null + if (!meaningfullyWider) { + return null + } + + return UltraWideCamera(widest.cameraId, DEFAULT_ULTRA_WIDE_ZOOM_RATIO) } private fun backCameraProfile( @@ -457,7 +474,7 @@ class RecordingCameraController( return } if (currentLensMode == LensMode.ULTRA_WIDE) { - currentZoomRatio = 0.5f + currentZoomRatio = ultraWideZoomRatio onComplete(true, zoomCapabilitiesMap(), null) return } @@ -473,7 +490,7 @@ class RecordingCameraController( } try { currentLensMode = LensMode.ULTRA_WIDE - currentZoomRatio = 0.5f + currentZoomRatio = ultraWideZoomRatio bindUseCases(provider, lifecycleOwner, selectorForCameraId(ultraWideId)) applyCurrentZoom() onComplete(true, zoomCapabilitiesMap(), null) @@ -539,8 +556,14 @@ class RecordingCameraController( val horizontalFov: Double, ) + private data class UltraWideCamera( + val cameraId: String, + val zoomRatio: Float, + ) + companion object { private const val TAG = "RecordingCamera" + private const val DEFAULT_ULTRA_WIDE_ZOOM_RATIO = 0.6f private const val ULTRA_WIDE_FOV_FACTOR = 1.08 private const val ULTRA_WIDE_FOCAL_FACTOR = 0.92 } diff --git a/lib/features/recording/widgets/widget_recording_hud.dart b/lib/features/recording/widgets/widget_recording_hud.dart index 26c0b63..10d25af 100644 --- a/lib/features/recording/widgets/widget_recording_hud.dart +++ b/lib/features/recording/widgets/widget_recording_hud.dart @@ -234,7 +234,7 @@ class _ZoomPresetControl extends StatelessWidget { for (final preset in availablePresets) _ZoomPresetButton( displayRatio: preset, - requestRatio: _requestRatioFor(preset), + requestRatio: preset, selected: _isPresetSelected(preset), enabled: !_wouldSwitchPhysicalCamera(preset), onSelected: onSelected, @@ -259,13 +259,6 @@ class _ZoomPresetControl extends StatelessWidget { return (zoomRatio - preset).abs() < 0.05; } - double _requestRatioFor(double preset) { - if (preset < 1.0) { - return minZoomRatio <= 0.5 ? 0.5 : preset; - } - return preset; - } - bool _wouldSwitchPhysicalCamera(double preset) { if (!isRecording) { return false; diff --git a/test/features/recording/view_model_recording_test.dart b/test/features/recording/view_model_recording_test.dart index c568b60..6b52de9 100644 --- a/test/features/recording/view_model_recording_test.dart +++ b/test/features/recording/view_model_recording_test.dart @@ -77,7 +77,7 @@ void main() { }); test( - 'passes native ultra-wide ratio when camera capabilities allow it', + 'clamps legacy 0.5x request to 0.6x ultra-wide ratio', () async { final calls = []; TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger @@ -86,8 +86,8 @@ void main() { (call) async { calls.add(call); return { - 'zoomRatio': 0.5, - 'minZoomRatio': 0.5, + 'zoomRatio': 0.6, + 'minZoomRatio': 0.6, 'maxZoomRatio': 3.0, }; }, @@ -101,17 +101,17 @@ void main() { .copyWith( session: const RecordingSessionState( zoomRatio: 1.0, - minZoomRatio: 0.5, + minZoomRatio: 0.6, maxZoomRatio: 3.0, ), ); await notifier.setZoomRatio(0.5); - expect(calls.single.arguments, {'zoomRatio': 0.5}); + expect(calls.single.arguments, {'zoomRatio': 0.6}); final session = container.read(recordingViewModelProvider).session; - expect(session.zoomRatio, 0.5); - expect(session.minZoomRatio, 0.5); + expect(session.zoomRatio, 0.6); + expect(session.minZoomRatio, 0.6); expect(session.maxZoomRatio, 3.0); }, ); diff --git a/test/features/recording/widget_recording_hud_test.dart b/test/features/recording/widget_recording_hud_test.dart index 97aad5f..382e9bb 100644 --- a/test/features/recording/widget_recording_hud_test.dart +++ b/test/features/recording/widget_recording_hud_test.dart @@ -54,7 +54,7 @@ void main() { expect(find.text('3x'), findsNothing); }); - testWidgets('shows 0.6x when 0.5x camera capability supports it', ( + testWidgets('shows 0.6x when ultra-wide camera capability is below 0.6', ( tester, ) async { await pumpHud(tester, minZoomRatio: 0.5); @@ -75,7 +75,7 @@ void main() { expect(find.text('1x'), findsOneWidget); }); - testWidgets('marks current 0.5x zoom ratio as selected on 0.6x UI', ( + testWidgets('marks current ultra-wide zoom ratio as selected on 0.6x UI', ( tester, ) async { await pumpHud(tester, zoomRatio: 0.5, minZoomRatio: 0.5); @@ -102,7 +102,7 @@ void main() { expect(find.text('1x'), findsNothing); }); - testWidgets('tapping 0.6x reports 0.5 when camera supports 0.5x', ( + testWidgets('tapping 0.6x reports 0.6 when camera capability is below 0.6', ( tester, ) async { double? selected; @@ -115,7 +115,7 @@ void main() { await tester.tap(find.text('0.6x')); await tester.pump(); - expect(selected, 0.5); + expect(selected, 0.6); }); testWidgets('tapping 0.6x reports 0.6 when camera only supports 0.6x', ( @@ -148,7 +148,7 @@ void main() { expect(mainButton.enabled, isFalse); }); - testWidgets('disables main zoom presets while recording on 0.5x', ( + testWidgets('disables main zoom presets while recording on ultra-wide', ( tester, ) async { await pumpHud(tester, zoomRatio: 0.5, minZoomRatio: 0.5, isRecording: true);