diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml b/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml index e517746091..7dacdbf27e 100644 --- a/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml +++ b/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml @@ -5,12 +5,11 @@ xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit" xmlns:viewModels="clr-namespace:CommunityToolkit.Maui.Sample.ViewModels.Views" Title="CameraView" - Unloaded="OnUnloaded" x:Class="CommunityToolkit.Maui.Sample.Pages.Views.CameraViewPage" x:TypeArguments="viewModels:CameraViewViewModel" x:DataType="viewModels:CameraViewViewModel"> - + - + - + @@ -61,6 +60,12 @@ + + { readonly string imagePath; - int pageCount; public CameraViewPage(CameraViewViewModel viewModel, IFileSystem fileSystem) : base(viewModel) { @@ -16,30 +14,20 @@ public CameraViewPage(CameraViewViewModel viewModel, IFileSystem fileSystem) : b imagePath = Path.Combine(fileSystem.CacheDirectory, "camera-view-image.jpg"); Camera.MediaCaptured += OnMediaCaptured; - - Loaded += (s, e) => - { - pageCount = Navigation.NavigationStack.Count; - }; } protected override async void OnAppearing() { base.OnAppearing(); - var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(3)); - await BindingContext.RefreshCamerasCommand.ExecuteAsync(cancellationTokenSource.Token); + await BindingContext.InitializeAsync(); } - // https://github.com/dotnet/maui/issues/16697 - // https://github.com/dotnet/maui/issues/15833 protected override void OnNavigatedFrom(NavigatedFromEventArgs args) { base.OnNavigatedFrom(args); - Debug.WriteLine($"< < OnNavigatedFrom {pageCount} {Navigation.NavigationStack.Count}"); - - if (Navigation.NavigationStack.Count < pageCount) + if (!Shell.Current.Navigation.NavigationStack.Contains(this)) { Cleanup(); } @@ -57,12 +45,6 @@ async void OnImageTapped(object? sender, TappedEventArgs args) void Cleanup() { Camera.MediaCaptured -= OnMediaCaptured; - Camera.Handler?.DisconnectHandler(); - } - - void OnUnloaded(object? sender, EventArgs e) - { - //Cleanup(); } void OnMediaCaptured(object? sender, MediaCapturedEventArgs e) @@ -75,7 +57,7 @@ void OnMediaCaptured(object? sender, MediaCapturedEventArgs e) { // workaround for https://github.com/dotnet/maui/issues/13858 #if ANDROID - image.Source = ImageSource.FromStream(() => File.OpenRead(imagePath)); + image.Source = ImageSource.FromStream(() => File.OpenRead(imagePath)); #else image.Source = ImageSource.FromFile(imagePath); #endif diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Views/CameraView/CameraViewViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Views/CameraView/CameraViewViewModel.cs index 055637f821..bdd6854357 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Views/CameraView/CameraViewViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Views/CameraView/CameraViewViewModel.cs @@ -1,5 +1,5 @@ -using CommunityToolkit.Maui.Core; -using CommunityToolkit.Maui.Core.Primitives; +using System.Collections.ObjectModel; +using CommunityToolkit.Maui.Core; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; @@ -9,10 +9,12 @@ public partial class CameraViewViewModel(ICameraProvider cameraProvider) : BaseV { readonly ICameraProvider cameraProvider = cameraProvider; - public IReadOnlyList Cameras => cameraProvider.AvailableCameras ?? []; + bool isInitialized = false; public CancellationToken Token => CancellationToken.None; + public ObservableCollection Cameras { get; } = []; + public ICollection FlashModes { get; } = Enum.GetValues(); [ObservableProperty] @@ -42,6 +44,22 @@ public partial class CameraViewViewModel(ICameraProvider cameraProvider) : BaseV [ObservableProperty] public partial string ResolutionText { get; set; } = string.Empty; + public async ValueTask InitializeAsync() + { + if (isInitialized) + { + return; + } + + await cameraProvider.InitializeAsync(CancellationToken.None); + foreach (var camera in cameraProvider.AvailableCameras ?? []) + { + Cameras.Add(camera); + } + + isInitialized = true; + } + [RelayCommand] async Task RefreshCameras(CancellationToken token) => await cameraProvider.RefreshAvailableCameras(token); @@ -60,6 +78,22 @@ partial void OnSelectedResolutionChanged(Size value) UpdateResolutionText(); } + partial void OnSelectedCameraChanged(CameraInfo? oldValue, CameraInfo? newValue) + { + UpdateCameraInfoText(); + } + + void UpdateCameraInfoText() + { + if (SelectedCamera is null) + { + return; + } + CameraNameText = $"{SelectedCamera.Name}"; + ZoomRangeText = $"Min Zoom: {SelectedCamera.MinimumZoomFactor}, Max Zoom: {SelectedCamera.MaximumZoomFactor}"; + UpdateFlashModeText(); + } + void UpdateFlashModeText() { if (SelectedCamera is null) diff --git a/src/CommunityToolkit.Maui.Camera/CameraManager.android.cs b/src/CommunityToolkit.Maui.Camera/CameraManager.android.cs index c642ca4218..4f7eaddf48 100644 --- a/src/CommunityToolkit.Maui.Camera/CameraManager.android.cs +++ b/src/CommunityToolkit.Maui.Camera/CameraManager.android.cs @@ -129,6 +129,7 @@ protected virtual void Dispose(bool disposing) previewView?.Dispose(); previewView = null; + processCameraProvider?.UnbindAll(); processCameraProvider?.Dispose(); processCameraProvider = null; @@ -158,16 +159,6 @@ protected virtual async partial Task PlatformConnectCamera(CancellationToken tok { processCameraProvider = (ProcessCameraProvider)(cameraProviderFuture.Get() ?? throw new CameraException($"Unable to retrieve {nameof(ProcessCameraProvider)}")); - if (cameraProvider.AvailableCameras is null) - { - await cameraProvider.RefreshAvailableCameras(token); - - if (cameraProvider.AvailableCameras is null) - { - throw new CameraException("Unable to refresh available cameras"); - } - } - await StartUseCase(token); cameraProviderTCS.SetResult(); @@ -200,22 +191,14 @@ protected async Task StartUseCase(CancellationToken token) await StartCameraPreview(token); } - protected virtual async partial Task PlatformStartCameraPreview(CancellationToken token) + protected virtual partial Task PlatformStartCameraPreview(CancellationToken token) { if (previewView is null || processCameraProvider is null || cameraPreview is null || imageCapture is null) { - return; + return Task.CompletedTask; } - if (cameraView.SelectedCamera is null) - { - if (cameraProvider.AvailableCameras is null) - { - await cameraProvider.RefreshAvailableCameras(token); - } - - cameraView.SelectedCamera = cameraProvider.AvailableCameras?.FirstOrDefault() ?? throw new CameraException("No camera available on device"); - } + cameraView.SelectedCamera ??= cameraProvider.AvailableCameras?.FirstOrDefault() ?? throw new CameraException("No camera available on device"); var cameraSelector = cameraView.SelectedCamera.CameraSelector ?? throw new CameraException($"Unable to retrieve {nameof(CameraSelector)}"); @@ -231,6 +214,7 @@ protected virtual async partial Task PlatformStartCameraPreview(CancellationToke IsInitialized = true; OnLoaded.Invoke(); + return Task.CompletedTask; } protected virtual partial void PlatformStopCameraPreview() diff --git a/src/CommunityToolkit.Maui.Camera/CameraManager.macios.cs b/src/CommunityToolkit.Maui.Camera/CameraManager.macios.cs index 17941d0cf7..178e0c621d 100644 --- a/src/CommunityToolkit.Maui.Camera/CameraManager.macios.cs +++ b/src/CommunityToolkit.Maui.Camera/CameraManager.macios.cs @@ -77,24 +77,18 @@ public partial void UpdateZoom(float zoomLevel) captureDevice.UnlockForConfiguration(); } - public async partial ValueTask UpdateCaptureResolution(Size resolution, CancellationToken token) + public partial ValueTask UpdateCaptureResolution(Size resolution, CancellationToken token) { - if (captureDevice is null) + if (captureDevice is null || cameraView.SelectedCamera is null) { - return; + return ValueTask.CompletedTask; } captureDevice.LockForConfiguration(out NSError? error); if (error is not null) { Trace.WriteLine(error); - return; - } - - if (cameraView.SelectedCamera is null) - { - await cameraProvider.RefreshAvailableCameras(token); - cameraView.SelectedCamera = cameraProvider.AvailableCameras?.FirstOrDefault() ?? throw new CameraException("No camera available on device"); + return ValueTask.CompletedTask; } var filteredFormatList = cameraView.SelectedCamera.SupportedFormats.Where(f => @@ -116,20 +110,11 @@ public async partial ValueTask UpdateCaptureResolution(Size resolution, Cancella } captureDevice.UnlockForConfiguration(); + return ValueTask.CompletedTask; } protected virtual async partial Task PlatformConnectCamera(CancellationToken token) { - if (cameraProvider.AvailableCameras is null) - { - await cameraProvider.RefreshAvailableCameras(token); - - if (cameraProvider.AvailableCameras is null) - { - throw new CameraException("Unable to refresh cameras"); - } - } - await PlatformStartCameraPreview(token); } @@ -148,11 +133,7 @@ protected virtual async partial Task PlatformStartCameraPreview(CancellationToke input.Dispose(); } - if (cameraView.SelectedCamera is null) - { - await cameraProvider.RefreshAvailableCameras(token); - cameraView.SelectedCamera = cameraProvider.AvailableCameras?.FirstOrDefault() ?? throw new CameraException("No camera available on device"); - } + cameraView.SelectedCamera ??= cameraProvider.AvailableCameras?.FirstOrDefault() ?? throw new CameraException("No camera available on device"); captureDevice = cameraView.SelectedCamera.CaptureDevice ?? throw new CameraException($"No Camera found"); captureInput = new AVCaptureDeviceInput(captureDevice, out _); diff --git a/src/CommunityToolkit.Maui.Camera/CameraManager.shared.cs b/src/CommunityToolkit.Maui.Camera/CameraManager.shared.cs index d7703d2572..4678183bcb 100644 --- a/src/CommunityToolkit.Maui.Camera/CameraManager.shared.cs +++ b/src/CommunityToolkit.Maui.Camera/CameraManager.shared.cs @@ -33,7 +33,18 @@ public async Task ArePermissionsGranted() /// Connects to the camera. /// /// A that can be awaited. - public Task ConnectCamera(CancellationToken token) => PlatformConnectCamera(token); + public async Task ConnectCamera(CancellationToken token) + { + if (await ArePermissionsGranted() is false) + { + throw new PermissionException("Camera permissions not granted"); + } + + await cameraProvider.InitializeAsync(token); + cameraView.SelectedCamera ??= cameraProvider.AvailableCameras?.FirstOrDefault() ?? throw new CameraException("No camera available on device"); + + await PlatformConnectCamera(token); + } /// /// Disconnects from the camera. diff --git a/src/CommunityToolkit.Maui.Camera/CameraManager.windows.cs b/src/CommunityToolkit.Maui.Camera/CameraManager.windows.cs index 5edf33fc77..07bf1a2780 100644 --- a/src/CommunityToolkit.Maui.Camera/CameraManager.windows.cs +++ b/src/CommunityToolkit.Maui.Camera/CameraManager.windows.cs @@ -119,16 +119,6 @@ protected virtual void Dispose(bool disposing) protected virtual async partial Task PlatformConnectCamera(CancellationToken token) { - if (cameraProvider.AvailableCameras is null) - { - await cameraProvider.RefreshAvailableCameras(token); - - if (cameraProvider.AvailableCameras is null) - { - throw new CameraException("Unable to refresh cameras"); - } - } - await StartCameraPreview(token); } @@ -139,13 +129,9 @@ protected virtual async partial Task PlatformStartCameraPreview(CancellationToke return; } - mediaCapture = new MediaCapture(); + cameraView.SelectedCamera ??= cameraProvider.AvailableCameras?.FirstOrDefault() ?? throw new CameraException("No camera available on device"); - if (cameraView.SelectedCamera is null) - { - await cameraProvider.RefreshAvailableCameras(token); - cameraView.SelectedCamera = cameraProvider.AvailableCameras?.FirstOrDefault() ?? throw new CameraException("No camera available on device"); - } + mediaCapture = new MediaCapture(); await mediaCapture.InitializeCameraForCameraView(cameraView.SelectedCamera.DeviceId, token); @@ -180,22 +166,22 @@ protected virtual partial void PlatformStopCameraPreview() protected async Task PlatformUpdateResolution(Size resolution, CancellationToken token) { - if (!IsInitialized || mediaCapture is null) + if (!IsInitialized || mediaCapture is null || cameraView.SelectedCamera is null) { return; } - if (cameraView.SelectedCamera is null) + if (mediaCapture.VideoDeviceController.Id != cameraView.SelectedCamera.DeviceId) { - await cameraProvider.RefreshAvailableCameras(token); - cameraView.SelectedCamera = cameraProvider.AvailableCameras?.FirstOrDefault() ?? throw new CameraException("No camera available on device"); + return; } var filteredPropertiesList = cameraView.SelectedCamera.ImageEncodingProperties.Where(p => p.Width <= resolution.Width && p.Height <= resolution.Height).ToList(); - filteredPropertiesList = filteredPropertiesList.Count is not 0 - ? filteredPropertiesList - : [.. cameraView.SelectedCamera.ImageEncodingProperties.OrderByDescending(p => p.Width * p.Height)]; + if (filteredPropertiesList.Count is 0) + { + filteredPropertiesList = [.. cameraView.SelectedCamera.ImageEncodingProperties.OrderByDescending(p => p.Width * p.Height)]; + } if (filteredPropertiesList.Count is not 0) { diff --git a/src/CommunityToolkit.Maui.Camera/Handlers/CameraViewHandler.shared.cs b/src/CommunityToolkit.Maui.Camera/Handlers/CameraViewHandler.shared.cs index 9f4b12f36c..646731179b 100644 --- a/src/CommunityToolkit.Maui.Camera/Handlers/CameraViewHandler.shared.cs +++ b/src/CommunityToolkit.Maui.Camera/Handlers/CameraViewHandler.shared.cs @@ -87,10 +87,7 @@ void Init(ICameraView view) protected override async void ConnectHandler(NativePlatformCameraPreviewView platformView) { base.ConnectHandler(platformView); - - await CameraManager.ArePermissionsGranted(); await CameraManager.ConnectCamera(CancellationToken.None); - await cameraProvider.RefreshAvailableCameras(CancellationToken.None); } /// diff --git a/src/CommunityToolkit.Maui.Camera/Interfaces/ICameraProvider.shared.cs b/src/CommunityToolkit.Maui.Camera/Interfaces/ICameraProvider.shared.cs index 3f0090e2ec..dff17dd2aa 100644 --- a/src/CommunityToolkit.Maui.Camera/Interfaces/ICameraProvider.shared.cs +++ b/src/CommunityToolkit.Maui.Camera/Interfaces/ICameraProvider.shared.cs @@ -11,15 +11,33 @@ public interface ICameraProvider /// Cameras available on device /// /// - /// List is initialized using + /// List is initialized using , and can be refreshed using /// IReadOnlyList? AvailableCameras { get; } /// - /// Assigns with the cameras available on device + /// Gets a value indicating whether the camera provider has been successfully initialized. /// + bool IsInitialized { get; } + + /// + /// Refreshes the on device, regardless of the current initialization state. + /// + /// + /// This ensures the list is up to date if available cameras have changed after initialization. + /// /// /// [MemberNotNull(nameof(AvailableCameras))] - ValueTask RefreshAvailableCameras(CancellationToken token); + Task RefreshAvailableCameras(CancellationToken token); + + /// + /// Initialize the camera provider by refreshing the . + /// This performs a one-time discovery of available cameras. Subsequent calls are no-ops unless initialization failed previously. + /// + /// + /// To force a refresh after the provider is initialized, call . + /// + ValueTask InitializeAsync(CancellationToken token); + } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.android.cs b/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.android.cs index f908040cd0..ad25030de9 100644 --- a/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.android.cs +++ b/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.android.cs @@ -16,7 +16,7 @@ partial class CameraProvider { readonly Context context = Android.App.Application.Context; - public async partial ValueTask RefreshAvailableCameras(CancellationToken token) + private async partial ValueTask PlatformRefreshAvailableCameras(CancellationToken token) { var cameraProviderFuture = ProcessCameraProvider.GetInstance(context); diff --git a/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.macios.cs b/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.macios.cs index 1a93631309..5a9a9bad6b 100644 --- a/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.macios.cs +++ b/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.macios.cs @@ -9,7 +9,7 @@ partial class CameraProvider { static readonly AVCaptureDeviceType[] captureDevices = InitializeCaptureDevices(); - public partial ValueTask RefreshAvailableCameras(CancellationToken token) + private partial ValueTask PlatformRefreshAvailableCameras(CancellationToken token) { var discoverySession = AVCaptureDeviceDiscoverySession.Create(captureDevices, AVMediaTypes.Video, AVCaptureDevicePosition.Unspecified); var availableCameras = new List(); diff --git a/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.net.cs b/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.net.cs index d4c7daf353..57a58ae47d 100644 --- a/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.net.cs +++ b/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.net.cs @@ -2,5 +2,5 @@ namespace CommunityToolkit.Maui.Core; partial class CameraProvider { - public partial ValueTask RefreshAvailableCameras(CancellationToken token) => throw new NotSupportedException(); + private partial ValueTask PlatformRefreshAvailableCameras(CancellationToken token) => throw new NotSupportedException(); } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.shared.cs b/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.shared.cs index adf24f9846..f1dc9da8c6 100644 --- a/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.shared.cs +++ b/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.shared.cs @@ -3,11 +3,81 @@ /// /// Implementation that provides the ability to discover cameras that are attached to the current device. /// -partial class CameraProvider : ICameraProvider +partial class CameraProvider : ICameraProvider, IDisposable { + readonly SemaphoreSlim refreshAvailableCamerasSemaphore = new(1, 1); + Task? refreshAvailableCamerasTask; + + ~CameraProvider() + { + Dispose(false); + } + + /// + public bool IsInitialized => refreshAvailableCamerasTask?.IsCompletedSuccessfully is true; + /// public IReadOnlyList? AvailableCameras { get; private set; } + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + Task GetRefreshTask(CancellationToken token) + { + if (refreshAvailableCamerasTask is null || refreshAvailableCamerasTask.IsCompleted) + { + refreshAvailableCamerasTask = PlatformRefreshAvailableCameras(token).AsTask(); + } + + return refreshAvailableCamerasTask; + } + + /// + public async ValueTask InitializeAsync(CancellationToken token) + { + await refreshAvailableCamerasSemaphore.WaitAsync(token); + + try + { + if (!IsInitialized) + { + await GetRefreshTask(token); + } + } + finally + { + refreshAvailableCamerasSemaphore.Release(); + } + } + /// - public partial ValueTask RefreshAvailableCameras(CancellationToken token); + public async Task RefreshAvailableCameras(CancellationToken token) + { + await refreshAvailableCamerasSemaphore.WaitAsync(token); + + try + { + await GetRefreshTask(token); + } + finally + { + refreshAvailableCamerasSemaphore.Release(); + } + } + + void Dispose(bool disposing) + { + if (disposing) + { + refreshAvailableCamerasSemaphore.Dispose(); + + refreshAvailableCamerasTask?.Dispose(); + refreshAvailableCamerasTask = null; + } + } + + private partial ValueTask PlatformRefreshAvailableCameras(CancellationToken token); } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.tizen.cs b/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.tizen.cs index 03140011e6..5ae4c99da8 100644 --- a/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.tizen.cs +++ b/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.tizen.cs @@ -2,5 +2,5 @@ partial class CameraProvider { - public partial ValueTask RefreshAvailableCameras(CancellationToken token) => throw new NotSupportedException(); + private partial ValueTask PlatformRefreshAvailableCameras(CancellationToken token) => throw new NotSupportedException(); } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.windows.cs b/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.windows.cs index b25c54b4d8..f7c24cf785 100644 --- a/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.windows.cs +++ b/src/CommunityToolkit.Maui.Camera/Providers/CameraProvider.windows.cs @@ -9,17 +9,17 @@ namespace CommunityToolkit.Maui.Core; partial class CameraProvider { - public async partial ValueTask RefreshAvailableCameras(CancellationToken token) + private async partial ValueTask PlatformRefreshAvailableCameras(CancellationToken token) { var deviceInfoCollection = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture).AsTask(token); var mediaFrameSourceGroup = await MediaFrameSourceGroup.FindAllAsync().AsTask(token); var videoCaptureSourceGroup = mediaFrameSourceGroup.Where(sourceGroup => deviceInfoCollection.Any(deviceInfo => deviceInfo.Id == sourceGroup.Id)).ToList(); - var mediaCapture = new MediaCapture(); var availableCameras = new List(); foreach (var sourceGroup in videoCaptureSourceGroup) { + var mediaCapture = new MediaCapture(); await mediaCapture.InitializeCameraForCameraView(sourceGroup.Id, token); CameraPosition position = CameraPosition.Unknown; @@ -63,9 +63,11 @@ public async partial ValueTask RefreshAvailableCameras(CancellationToken token) imageEncodingPropertiesList); availableCameras.Add(cameraInfo); + + mediaCapture.Dispose(); } AvailableCameras = availableCameras; } -} \ No newline at end of file +} diff --git a/src/CommunityToolkit.Maui.Camera/Views/CameraView.shared.cs b/src/CommunityToolkit.Maui.Camera/Views/CameraView.shared.cs index edbd26460d..2a4735dcce 100644 --- a/src/CommunityToolkit.Maui.Camera/Views/CameraView.shared.cs +++ b/src/CommunityToolkit.Maui.Camera/Views/CameraView.shared.cs @@ -204,17 +204,8 @@ public void Dispose() /// public async ValueTask> GetAvailableCameras(CancellationToken token) { - if (CameraProvider.AvailableCameras is null) - { - await CameraProvider.RefreshAvailableCameras(token); - - if (CameraProvider.AvailableCameras is null) - { - throw new CameraException("Unable to refresh available cameras"); - } - } - - return CameraProvider.AvailableCameras; + await CameraProvider.InitializeAsync(token); + return CameraProvider.AvailableCameras ?? throw new CameraException("No camera available on device"); } /// diff --git a/src/CommunityToolkit.Maui.UnitTests/Mocks/MockCameraProvider.cs b/src/CommunityToolkit.Maui.UnitTests/Mocks/MockCameraProvider.cs index aa42ebeabd..8ef976513d 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Mocks/MockCameraProvider.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Mocks/MockCameraProvider.cs @@ -1,5 +1,4 @@ using CommunityToolkit.Maui.Core; -using CommunityToolkit.Maui.Core.Primitives; namespace CommunityToolkit.Maui.UnitTests.Mocks; @@ -7,7 +6,18 @@ public class MockCameraProvider : ICameraProvider { public IReadOnlyList? AvailableCameras { get; private set; } - public ValueTask RefreshAvailableCameras(CancellationToken token) + public bool IsInitialized { get; private set; } + + public async ValueTask InitializeAsync(CancellationToken token) + { + if (!IsInitialized) + { + await RefreshAvailableCameras(token); + IsInitialized = true; + } + } + + public Task RefreshAvailableCameras(CancellationToken token) { AvailableCameras = [ @@ -22,6 +32,6 @@ public ValueTask RefreshAvailableCameras(CancellationToken token) ]) ]; - return ValueTask.CompletedTask; + return Task.CompletedTask; } } \ No newline at end of file