3 Commits
Author SHA1 Message Date
linfeng 6d93c1c8dd 解决安卓端广角无法读取问题 2026-06-30 16:30:24 +08:00
linfeng c9323f26a7 启动图修改 2026-06-30 14:19:02 +08:00
linfeng 7dafd61314 修改图标 2026-06-30 13:57:18 +08:00
27 changed files with 357 additions and 69 deletions
@@ -3,8 +3,13 @@ package com.run.sportsx.recording
import android.content.Context
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
import android.hardware.camera2.CaptureRequest
import android.os.Build
import android.util.Log
import androidx.camera.camera2.interop.Camera2CameraControl
import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.camera2.interop.CaptureRequestOptions
import androidx.camera.camera2.interop.ExperimentalCamera2Interop
import androidx.camera.core.Camera
import androidx.camera.core.CameraSelector
import androidx.camera.core.Preview
@@ -18,8 +23,9 @@ import androidx.camera.video.VideoRecordEvent
import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import kotlin.math.atan
import java.util.concurrent.Executor
import kotlin.math.atan
import kotlin.math.round
class RecordingCameraController(
private val appContext: Context,
@@ -225,15 +231,18 @@ class RecordingCameraController(
fun zoomCapabilitiesMap(): Map<String, Any> {
val zoomState = camera?.cameraInfo?.zoomState?.value
val logicalMin = zoomState?.minZoomRatio ?: 1f
// 兜底两路超广角来源:独立超广角镜头(0.6) 与 逻辑相机原生 <1.0 变焦范围,取更小者。
val cameraXMin = zoomState?.minZoomRatio ?: 1f
val cameraXMax = zoomState?.maxZoomRatio ?: 3f
val camera2Range = mainCameraZoomRatioRange()
val logicalMin = camera2Range?.lower?.let { minOf(cameraXMin, it) } ?: cameraXMin
val logicalMax = camera2Range?.upper?.let { maxOf(cameraXMax, it) } ?: cameraXMax
val minZoom =
if (hasUltraWideCamera()) {
minOf(ultraWideZoomRatio, logicalMin)
} else {
logicalMin
}
val maxZoom = zoomState?.maxZoomRatio ?: 3f
val maxZoom = logicalMax
val zoom =
if (currentLensMode == LensMode.ULTRA_WIDE) {
ultraWideZoomRatio
@@ -243,7 +252,8 @@ class RecordingCameraController(
currentZoomRatio = zoom
Log.d(
TAG,
"zoomCapabilities hasUltraWide=${hasUltraWideCamera()} logicalMin=$logicalMin " +
"zoomCapabilities hasUltraWide=${hasUltraWideCamera()} cameraXMin=$cameraXMin " +
"cameraXMax=$cameraXMax camera2Range=${camera2Range?.description()} " +
"ultraWideZoomRatio=$ultraWideZoomRatio minZoom=$minZoom maxZoom=$maxZoom zoom=$zoom",
)
return mapOf(
@@ -271,9 +281,12 @@ class RecordingCameraController(
}
if (ratio < 1.0 && hasUltraWideCamera()) {
val logicalRange = mainCameraZoomRatioRange()
if (logicalRange == null || !logicalRange.contains(ratio.toFloat())) {
switchToUltraWide(onComplete)
return
}
}
if (currentLensMode == LensMode.ULTRA_WIDE) {
switchToMainAndZoom(ratio, onComplete)
@@ -281,12 +294,21 @@ class RecordingCameraController(
}
val zoomState = boundCamera.cameraInfo.zoomState.value
val minZoom = zoomState?.minZoomRatio ?: 1f
val maxZoom = zoomState?.maxZoomRatio ?: clampedMaxZoom()
val camera2Range = mainCameraZoomRatioRange()
val minZoom = camera2Range?.lower ?: zoomState?.minZoomRatio ?: 1f
val maxZoom = camera2Range?.upper ?: zoomState?.maxZoomRatio ?: clampedMaxZoom()
val nextZoom = ratio.toFloat().coerceIn(minZoom, maxZoom)
currentZoomRatio = nextZoom
val future = boundCamera.cameraControl.setZoomRatio(nextZoom)
val future =
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
camera2Range?.contains(nextZoom) == true
) {
applyCamera2ZoomRatio(boundCamera, nextZoom)
} else {
boundCamera.cameraControl.setZoomRatio(nextZoom)
}
future.addListener(
{
try {
@@ -334,20 +356,30 @@ class RecordingCameraController(
return
}
val zoomState = boundCamera.cameraInfo.zoomState.value
val minZoom = zoomState?.minZoomRatio ?: 1f
val maxZoom = zoomState?.maxZoomRatio ?: clampedMaxZoom()
val camera2Range = mainCameraZoomRatioRange()
val minZoom = camera2Range?.lower ?: zoomState?.minZoomRatio ?: 1f
val maxZoom = camera2Range?.upper ?: zoomState?.maxZoomRatio ?: clampedMaxZoom()
currentZoomRatio = currentZoomRatio.coerceIn(minZoom, maxZoom)
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
camera2Range?.contains(currentZoomRatio) == true
) {
applyCamera2ZoomRatio(boundCamera, currentZoomRatio)
} else {
boundCamera.cameraControl.setZoomRatio(currentZoomRatio)
}
}
private fun clampedMaxZoom(): Float {
return camera?.cameraInfo?.zoomState?.value?.maxZoomRatio ?: 3f
}
private fun discoverBackCameras(provider: ProcessCameraProvider) {
val manager = appContext.getSystemService(Context.CAMERA_SERVICE) as CameraManager
if (mainCameraId == null) {
mainCameraId = cameraIdForSelector(provider, CameraSelector.DEFAULT_BACK_CAMERA)
}
logPublicCameraDiagnostics(provider, manager, mainCameraId)
val ultraWideCamera = findUltraWideCamera(provider, mainCameraId)
ultraWideCameraId = ultraWideCamera?.cameraId
ultraWideZoomRatio = ultraWideCamera?.zoomRatio ?: DEFAULT_ULTRA_WIDE_ZOOM_RATIO
@@ -383,6 +415,13 @@ class RecordingCameraController(
val candidates =
manager.cameraIdList
.mapNotNull { cameraId -> backCameraProfile(manager, cameraId) }
.onEach { profile ->
Log.d(
TAG,
"backCamera ${profile.description()} " +
"bindable=${provider.hasCameraSafely(selectorForCameraId(profile.cameraId))}",
)
}
.filter { it.cameraId != excludedCameraId }
.filter { provider.hasCameraSafely(selectorForCameraId(it.cameraId)) }
.sortedWith(
@@ -391,11 +430,16 @@ class RecordingCameraController(
)
val mainProfile = excludedCameraId?.let { backCameraProfile(manager, it) }
val widest = candidates.firstOrNull() ?: return null
val widest =
candidates.firstOrNull()
?: run {
logPhysicalOnlyUltraWideDiagnostics(manager, mainProfile, excludedCameraId)
return null
}
val candidatesDesc =
candidates.joinToString { "id=${it.cameraId} fov=${it.horizontalFov} focal=${it.minFocalLength}" }
candidates.joinToString { it.description() }
val mainDesc =
mainProfile?.let { "id=${it.cameraId} fov=${it.horizontalFov} focal=${it.minFocalLength}" }
mainProfile?.description()
Log.d(TAG, "ultraWide candidates=[$candidatesDesc] main=$mainDesc")
if (mainProfile == null) {
return UltraWideCamera(widest.cameraId, DEFAULT_ULTRA_WIDE_ZOOM_RATIO)
@@ -410,10 +454,98 @@ class RecordingCameraController(
"(fovFactor=$ULTRA_WIDE_FOV_FACTOR focalFactor=$ULTRA_WIDE_FOCAL_FACTOR)",
)
if (!meaningfullyWider) {
logPhysicalOnlyUltraWideDiagnostics(manager, mainProfile, excludedCameraId)
return null
}
return UltraWideCamera(widest.cameraId, DEFAULT_ULTRA_WIDE_ZOOM_RATIO)
return UltraWideCamera(widest.cameraId, estimateUltraWideZoomRatio(widest, mainProfile))
}
private fun logPublicCameraDiagnostics(
provider: ProcessCameraProvider,
manager: CameraManager,
mainCameraId: String?,
) {
Log.d(TAG, "publicCameraIds=[${manager.cameraIdList.joinToString()}] mainCameraId=$mainCameraId")
manager.cameraIdList.forEach { cameraId ->
try {
val characteristics = manager.getCameraCharacteristics(cameraId)
val bindable = provider.hasCameraSafely(selectorForCameraId(cameraId))
val physicalIds = physicalCameraIds(characteristics)
Log.d(
TAG,
"publicCamera id=$cameraId facing=${lensFacingDescription(characteristics)} " +
"physicalIds=[${physicalIds.joinToString()}] " +
"capabilities=[${capabilitiesDescription(characteristics)}] " +
"zoomRange=${zoomRatioRangeFromCharacteristics(characteristics)?.description()} " +
"focals=${focalLengthsDescription(characteristics)} " +
"sensor=${sensorSizeDescription(characteristics)} bindable=$bindable",
)
if (cameraId == mainCameraId) {
logMainPhysicalCameraDiagnostics(manager, cameraId, physicalIds)
}
} catch (error: Exception) {
Log.w(TAG, "publicCamera diagnostics failed for cameraId=$cameraId", error)
}
}
}
private fun logMainPhysicalCameraDiagnostics(
manager: CameraManager,
mainCameraId: String,
physicalIds: Set<String>,
) {
Log.d(TAG, "mainCamera id=$mainCameraId physicalIds=[${physicalIds.joinToString()}]")
physicalIds.forEach { physicalId ->
try {
val characteristics = manager.getCameraCharacteristics(physicalId)
Log.d(
TAG,
"mainPhysicalCamera id=$physicalId facing=${lensFacingDescription(characteristics)} " +
"zoomRange=${zoomRatioRangeFromCharacteristics(characteristics)?.description()} " +
"focals=${focalLengthsDescription(characteristics)} " +
"sensor=${sensorSizeDescription(characteristics)}",
)
} catch (error: Exception) {
Log.w(TAG, "mainPhysicalCamera diagnostics failed for physicalId=$physicalId", error)
}
}
}
private fun logPhysicalOnlyUltraWideDiagnostics(
manager: CameraManager,
mainProfile: CameraProfile?,
mainCameraId: String?,
) {
if (mainProfile == null || mainCameraId == null) {
return
}
val characteristics =
try {
manager.getCameraCharacteristics(mainCameraId)
} catch (error: Exception) {
Log.w(TAG, "physicalOnlyUltraWide diagnostics failed for mainCameraId=$mainCameraId", error)
return
}
val physicalProfiles =
physicalCameraIds(characteristics)
.mapNotNull { physicalId -> backCameraProfile(manager, physicalId) }
.filter { it.cameraId != mainCameraId }
.sortedBy { it.minFocalLength }
val widestPhysical = physicalProfiles.firstOrNull()
if (widestPhysical == null) {
Log.d(TAG, "physicalOnlyUltraWide none main=${mainProfile.description()}")
return
}
val meaningfullyWider =
widestPhysical.horizontalFov > mainProfile.horizontalFov * ULTRA_WIDE_FOV_FACTOR ||
widestPhysical.minFocalLength < mainProfile.minFocalLength * ULTRA_WIDE_FOCAL_FACTOR
Log.d(
TAG,
"physicalOnlyUltraWide widest=${widestPhysical.description()} " +
"main=${mainProfile.description()} meaningfullyWider=$meaningfullyWider " +
"exposedAsBindableCamera=false action=diagnostic_only",
)
}
private fun backCameraProfile(
@@ -435,13 +567,145 @@ class RecordingCameraController(
val minFocalLength = focalLengths.minOrNull() ?: return null
val horizontalFov =
2.0 * atan((physicalSize.width / (2.0f * minFocalLength)).toDouble())
CameraProfile(cameraId, minFocalLength, horizontalFov)
CameraProfile(
cameraId,
minFocalLength,
horizontalFov,
zoomRatioRangeFromCharacteristics(characteristics),
)
} catch (error: Exception) {
Log.w(TAG, "backCameraProfile failed for cameraId=$cameraId", error)
null
}
}
private fun mainCameraZoomRatioRange(): ZoomRatioRange? {
val cameraId = mainCameraId ?: activeCameraId()
return zoomRatioRangeForCamera(cameraId)
}
private fun activeCameraId(): String? {
val boundCamera = camera ?: return null
return try {
Camera2CameraInfo.from(boundCamera.cameraInfo).cameraId
} catch (error: Exception) {
null
}
}
private fun zoomRatioRangeForCamera(cameraId: String?): ZoomRatioRange? {
if (cameraId == null) return null
return try {
val manager = appContext.getSystemService(Context.CAMERA_SERVICE) as CameraManager
zoomRatioRangeFromCharacteristics(manager.getCameraCharacteristics(cameraId))
} catch (error: Exception) {
Log.w(TAG, "zoomRatioRangeForCamera failed for cameraId=$cameraId", error)
null
}
}
private fun zoomRatioRangeFromCharacteristics(
characteristics: CameraCharacteristics,
): ZoomRatioRange? {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return null
}
val range = characteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE)
?: return null
return ZoomRatioRange(range.lower, range.upper)
}
private fun physicalCameraIds(characteristics: CameraCharacteristics): Set<String> {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
return emptySet()
}
return characteristics.physicalCameraIds
}
private fun lensFacingDescription(characteristics: CameraCharacteristics): String {
return when (val facing = characteristics.get(CameraCharacteristics.LENS_FACING)) {
CameraCharacteristics.LENS_FACING_BACK -> "BACK"
CameraCharacteristics.LENS_FACING_FRONT -> "FRONT"
CameraCharacteristics.LENS_FACING_EXTERNAL -> "EXTERNAL"
null -> "null"
else -> "UNKNOWN($facing)"
}
}
private fun capabilitiesDescription(characteristics: CameraCharacteristics): String {
val capabilities =
characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
?: return "null"
return capabilities.joinToString { capabilityDescription(it) }
}
private fun capabilityDescription(capability: Int): String {
return when (capability) {
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE ->
"BACKWARD_COMPATIBLE"
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR -> "MANUAL_SENSOR"
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING ->
"MANUAL_POST_PROCESSING"
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW -> "RAW"
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING ->
"PRIVATE_REPROCESSING"
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS ->
"READ_SENSOR_SETTINGS"
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE -> "BURST_CAPTURE"
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING ->
"YUV_REPROCESSING"
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT -> "DEPTH_OUTPUT"
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO ->
"CONSTRAINED_HIGH_SPEED_VIDEO"
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING -> "MOTION_TRACKING"
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA ->
"LOGICAL_MULTI_CAMERA"
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME -> "MONOCHROME"
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA -> "SECURE_IMAGE_DATA"
else -> "UNKNOWN($capability)"
}
}
private fun focalLengthsDescription(characteristics: CameraCharacteristics): String {
val focalLengths =
characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)
?: return "null"
return focalLengths.joinToString(prefix = "[", postfix = "]")
}
private fun sensorSizeDescription(characteristics: CameraCharacteristics): String {
val size = characteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE)
?: return "null"
return "${size.width}x${size.height}"
}
private fun estimateUltraWideZoomRatio(
ultraWide: CameraProfile,
main: CameraProfile,
): Float {
if (main.minFocalLength <= 0f) {
return DEFAULT_ULTRA_WIDE_ZOOM_RATIO
}
val rawRatio = ultraWide.minFocalLength / main.minFocalLength
val roundedRatio = round(rawRatio * 10f) / 10f
return roundedRatio.coerceIn(MIN_ULTRA_WIDE_ZOOM_RATIO, MAX_ULTRA_WIDE_ZOOM_RATIO)
}
@androidx.annotation.OptIn(ExperimentalCamera2Interop::class)
private fun applyCamera2ZoomRatio(
boundCamera: Camera,
zoomRatio: Float,
) =
Camera2CameraControl.from(boundCamera.cameraControl)
.setCaptureRequestOptions(
CaptureRequestOptions.Builder()
.setCaptureRequestOption(
CaptureRequest.CONTROL_ZOOM_RATIO,
zoomRatio,
)
.build(),
)
private fun selectorForCurrentLensMode(): CameraSelector {
val cameraId =
if (currentLensMode == LensMode.ULTRA_WIDE) {
@@ -571,7 +835,26 @@ class RecordingCameraController(
val cameraId: String,
val minFocalLength: Float,
val horizontalFov: Double,
)
val zoomRatioRange: ZoomRatioRange?,
) {
fun description(): String {
return "id=$cameraId fov=$horizontalFov focal=$minFocalLength " +
"zoomRange=${zoomRatioRange?.description()}"
}
}
private data class ZoomRatioRange(
val lower: Float,
val upper: Float,
) {
fun contains(ratio: Float): Boolean {
return ratio >= lower && ratio <= upper
}
fun description(): String {
return "$lower..$upper"
}
}
private data class UltraWideCamera(
val cameraId: String,
@@ -581,6 +864,8 @@ class RecordingCameraController(
companion object {
private const val TAG = "RecordingCamera"
private const val DEFAULT_ULTRA_WIDE_ZOOM_RATIO = 0.6f
private const val MIN_ULTRA_WIDE_ZOOM_RATIO = 0.3f
private const val MAX_ULTRA_WIDE_ZOOM_RATIO = 0.99f
// 适度放宽判定宽容度,覆盖更多机型(更小的 FOV/焦距差异也视为超广角)。
private const val ULTRA_WIDE_FOV_FACTOR = 1.04
private const val ULTRA_WIDE_FOCAL_FACTOR = 0.96
Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 976 B

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

After

Width:  |  Height:  |  Size: 256 KiB

@@ -56,7 +56,6 @@ class RecordingHudWidget extends StatelessWidget {
static double get _recordButtonBottom => 63.r;
static double get _overlayInfoLeft => 13.r;
static double get _overlayInfoBottom => 10.r;
static const List<double> _zoomPresets = [0.6, 1.0];
@override
Widget build(BuildContext context) {
@@ -150,7 +149,7 @@ class RecordingHudWidget extends StatelessWidget {
zoomRatio: zoomRatio,
minZoomRatio: minZoomRatio,
maxZoomRatio: maxZoomRatio,
presets: _zoomPresets,
presets: _zoomPresetsForRange(minZoomRatio),
onSelected: onZoomSelected,
),
),
@@ -191,6 +190,13 @@ class RecordingHudWidget extends StatelessWidget {
],
);
}
List<double> _zoomPresetsForRange(double minZoomRatio) {
return [
if (minZoomRatio < 1.0) minZoomRatio,
1.0,
];
}
}
class _ZoomPresetControl extends StatelessWidget {
@@ -215,7 +221,6 @@ class _ZoomPresetControl extends StatelessWidget {
final availablePresets = presets
.where(_isPresetAvailable)
.toList(growable: false);
if (availablePresets.isEmpty) {
return const SizedBox.shrink();
}
@@ -76,9 +76,7 @@ void main() {
expect(session.errorMessage, isNull);
});
test(
'clamps legacy 0.5x request to 0.6x ultra-wide ratio',
() async {
test('passes 0.5x to native when camera capabilities allow it', () async {
final calls = <MethodCall>[];
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
@@ -86,8 +84,8 @@ void main() {
(call) async {
calls.add(call);
return <String, dynamic>{
'zoomRatio': 0.6,
'minZoomRatio': 0.6,
'zoomRatio': 0.5,
'minZoomRatio': 0.5,
'maxZoomRatio': 3.0,
};
},
@@ -101,20 +99,19 @@ void main() {
.copyWith(
session: const RecordingSessionState(
zoomRatio: 1.0,
minZoomRatio: 0.6,
minZoomRatio: 0.5,
maxZoomRatio: 3.0,
),
);
await notifier.setZoomRatio(0.5);
expect(calls.single.arguments, <String, dynamic>{'zoomRatio': 0.6});
expect(calls.single.arguments, <String, dynamic>{'zoomRatio': 0.5});
final session = container.read(recordingViewModelProvider).session;
expect(session.zoomRatio, 0.6);
expect(session.minZoomRatio, 0.6);
expect(session.zoomRatio, 0.5);
expect(session.minZoomRatio, 0.5);
expect(session.maxZoomRatio, 3.0);
},
);
});
test('passes 0.6x to native when camera capabilities allow it', () async {
final calls = <MethodCall>[];
@@ -54,13 +54,13 @@ void main() {
expect(find.text('3x'), findsNothing);
});
testWidgets('shows 0.6x when ultra-wide camera capability is below 0.6', (
testWidgets('shows 0.5x when ultra-wide camera capability is 0.5', (
tester,
) async {
await pumpHud(tester, minZoomRatio: 0.5);
expect(find.text('0.5x'), findsNothing);
expect(find.text('0.6x'), findsOneWidget);
expect(find.text('0.5x'), findsOneWidget);
expect(find.text('0.6x'), findsNothing);
expect(find.text('1x'), findsOneWidget);
expect(find.text('2x'), findsNothing);
expect(find.text('3x'), findsNothing);
@@ -75,13 +75,13 @@ void main() {
expect(find.text('1x'), findsOneWidget);
});
testWidgets('marks current ultra-wide zoom ratio as selected on 0.6x UI', (
testWidgets('marks current 0.5x zoom ratio as selected', (
tester,
) async {
await pumpHud(tester, zoomRatio: 0.5, minZoomRatio: 0.5);
final selectedButton = tester.widget<TextButton>(
find.ancestor(of: find.text('0.6x'), matching: find.byType(TextButton)),
find.ancestor(of: find.text('0.5x'), matching: find.byType(TextButton)),
);
expect(selectedButton.enabled, isFalse);
});
@@ -98,11 +98,12 @@ void main() {
testWidgets('does not expose presets beyond max zoom ratio', (tester) async {
await pumpHud(tester, minZoomRatio: 0.5, maxZoomRatio: 0.55);
expect(find.text('0.5x'), findsOneWidget);
expect(find.text('0.6x'), findsNothing);
expect(find.text('1x'), findsNothing);
});
testWidgets('tapping 0.6x reports 0.6 when camera capability is below 0.6', (
testWidgets('tapping 0.5x reports 0.5 when camera capability is 0.5', (
tester,
) async {
double? selected;
@@ -112,10 +113,10 @@ void main() {
onZoomSelected: (ratio) => selected = ratio,
);
await tester.tap(find.text('0.6x'));
await tester.tap(find.text('0.5x'));
await tester.pump();
expect(selected, 0.6);
expect(selected, 0.5);
});
testWidgets('tapping 0.6x reports 0.6 when camera only supports 0.6x', (
@@ -135,7 +136,7 @@ void main() {
});
testWidgets('disables 0.6x while recording on main camera', (tester) async {
await pumpHud(tester, minZoomRatio: 0.5, isRecording: true);
await pumpHud(tester, minZoomRatio: 0.6, isRecording: true);
final ultraWideButton = tester.widget<TextButton>(
find.ancestor(of: find.text('0.6x'), matching: find.byType(TextButton)),
@@ -154,7 +155,7 @@ void main() {
await pumpHud(tester, zoomRatio: 0.5, minZoomRatio: 0.5, isRecording: true);
final ultraWideButton = tester.widget<TextButton>(
find.ancestor(of: find.text('0.6x'), matching: find.byType(TextButton)),
find.ancestor(of: find.text('0.5x'), matching: find.byType(TextButton)),
);
final mainButton = tester.widget<TextButton>(
find.ancestor(of: find.text('1x'), matching: find.byType(TextButton)),