From 99d6b8ae054491aeb0fa0c16eb859d46a217c4ef Mon Sep 17 00:00:00 2001 From: Vladislav Antonyuk Date: Fri, 7 Nov 2025 16:14:17 +0200 Subject: [PATCH 01/10] Remove permissions from the library --- .../Views/CameraView/CameraViewPage.xaml.cs | 19 +++++++++++++++ .../Essentials/FileSaverViewModel.cs | 9 ++++++++ .../Essentials/FolderPickerViewModel.cs | 9 ++++++++ .../OfflineSpeechToTextViewModel.cs | 9 +++++++- .../Essentials/SpeechToTextViewModel.cs | 9 +++++++- .../CameraManager.shared.cs | 23 ------------------- .../Handlers/CameraViewHandler.shared.cs | 1 - .../FileSaverImplementation.android.cs | 11 +-------- .../FileSaverImplementation.tizen.cs | 6 ----- .../FolderPickerImplementation.android.cs | 9 -------- .../FolderPickerImplementation.tizen.cs | 6 ----- ...fflineSpeechToTextImplementation.shared.cs | 13 ----------- ...SharedSpeechToTextImplementation.macios.cs | 6 ----- .../SpeechToTextImplementation.shared.cs | 12 ---------- 14 files changed, 54 insertions(+), 88 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs index 38a987a1c5..752c4ba5eb 100644 --- a/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs +++ b/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs @@ -28,7 +28,20 @@ public CameraViewPage(CameraViewViewModel viewModel, IFileSystem fileSystem, IFi protected override async void OnAppearing() { base.OnAppearing(); + var cameraRequest = await Permissions.RequestAsync(); + var microphoneRequest = await Permissions.RequestAsync(); + if (cameraRequest is not PermissionStatus.Granted) + { + Trace.TraceInformation("Camera permission is not granted."); + return; + } + if (microphoneRequest is not PermissionStatus.Granted) + { + Trace.TraceInformation("Microphone permission is not granted."); + return; + } + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(3)); await BindingContext.RefreshCamerasCommand.ExecuteAsync(cancellationTokenSource.Token); } @@ -124,6 +137,12 @@ async void SaveVideo(object? sender, EventArgs e) } else { + var status = await Permissions.RequestAsync(); + if (status is not PermissionStatus.Granted) + { + throw new PermissionException("Storage permission is not granted."); + } + await fileSaver.SaveAsync("recording.mp4", videoRecordingStream); await videoRecordingStream.DisposeAsync(); videoRecordingStream = Stream.Null; diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs index ec47a3c448..764453a3bb 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs @@ -14,6 +14,7 @@ public partial class FileSaverViewModel(IFileSaver fileSaver) : BaseViewModel [RelayCommand] async Task SaveFile(CancellationToken cancellationToken) { + await RequestPermissions(); using var stream = new MemoryStream(Encoding.Default.GetBytes("Hello from the Community Toolkit!")); try { @@ -32,6 +33,7 @@ async Task SaveFile(CancellationToken cancellationToken) [RelayCommand] async Task SaveFileStatic(CancellationToken cancellationToken) { + await RequestPermissions(); using var stream = new MemoryStream(Encoding.Default.GetBytes("Hello from the Community Toolkit!")); var fileSaveResult = await FileSaver.SaveAsync("DCIM", "test.txt", stream, cancellationToken); if (fileSaveResult.IsSuccessful) @@ -47,6 +49,7 @@ async Task SaveFileStatic(CancellationToken cancellationToken) [RelayCommand] async Task SaveFileInstance(CancellationToken cancellationToken) { + await RequestPermissions(); using var client = new HttpClient(); const string communityToolkitNuGetUrl = "https://www.nuget.org/api/v2/package/CommunityToolkit.Maui/5.0.0"; @@ -67,4 +70,10 @@ async Task SaveFileInstance(CancellationToken cancellationToken) await Toast.Make($"File is not saved, {ex.Message}").Show(cancellationToken); } } + + async Task RequestPermissions() + { + await Permissions.RequestAsync(); + await Permissions.RequestAsync(); + } } \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs index fe6ff69a73..0306f05279 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs @@ -17,6 +17,7 @@ public FolderPickerViewModel(IFolderPicker folderPicker) [RelayCommand] async Task PickFolder(CancellationToken cancellationToken) { + await RequestPermissions(); var folderPickerResult = await folderPicker.PickAsync(cancellationToken); if (folderPickerResult.IsSuccessful) { @@ -31,6 +32,7 @@ async Task PickFolder(CancellationToken cancellationToken) [RelayCommand] async Task PickFolderStatic(CancellationToken cancellationToken) { + await RequestPermissions(); var folderResult = await FolderPicker.PickAsync("DCIM", cancellationToken); if (folderResult.IsSuccessful) { @@ -46,6 +48,7 @@ async Task PickFolderStatic(CancellationToken cancellationToken) [RelayCommand] async Task PickFolderInstance(CancellationToken cancellationToken) { + await RequestPermissions(); var folderPickerInstance = new FolderPickerImplementation(); try { @@ -62,4 +65,10 @@ async Task PickFolderInstance(CancellationToken cancellationToken) await Toast.Make($"Folder is not picked, {e.Message}").Show(cancellationToken); } } + + async Task RequestPermissions() + { + await Permissions.RequestAsync(); + await Permissions.RequestAsync(); + } } \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs index e02718a8cc..535bed3d69 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs @@ -27,7 +27,7 @@ public OfflineSpeechToTextViewModel() [RelayCommand] async Task StartListen() { - var isGranted = await speechToText.RequestPermissions(CancellationToken.None); + var isGranted = await RequestPermissions(); if (!isGranted) { await Toast.Make("Permission not granted").Show(CancellationToken.None); @@ -74,4 +74,11 @@ void HandleSpeechToTextStateChanged(object? sender, SpeechToTextStateChangedEven { OnPropertyChanged(nameof(State)); } + + async Task RequestPermissions() + { + var microphoneGranted = await Permissions.RequestAsync(); + var recognitionGranted = await SpeechToText.RequestPermissions(CancellationToken.None); + return microphoneGranted == PermissionStatus.Granted && recognitionGranted; + } } \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs index 7b40e09e5b..239561faee 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs @@ -85,7 +85,7 @@ async Task StartListen() CanStartListenExecute = false; CanStopListenExecute = true; - var isGranted = await speechToText.RequestPermissions(CancellationToken.None); + var isGranted = await RequestPermissions(); if (!isGranted) { await Toast.Make("Permission not granted").Show(CancellationToken.None); @@ -151,4 +151,11 @@ public async ValueTask DisposeAsync() { await speechToText.DisposeAsync(); } + + async Task RequestPermissions() + { + var microphoneGranted = await Permissions.RequestAsync(); + var recognitionGranted = await SpeechToText.RequestPermissions(CancellationToken.None); + return microphoneGranted == PermissionStatus.Granted && recognitionGranted; + } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Camera/CameraManager.shared.cs b/src/CommunityToolkit.Maui.Camera/CameraManager.shared.cs index fd16ef81a8..4542a7fa8c 100644 --- a/src/CommunityToolkit.Maui.Camera/CameraManager.shared.cs +++ b/src/CommunityToolkit.Maui.Camera/CameraManager.shared.cs @@ -24,29 +24,6 @@ partial class CameraManager( internal bool IsInitialized { get; private set; } - /// - /// Whether the user has granted the required permissions through the use of the API. - /// - /// Returns true if permission has been granted, false otherwise. - public async Task ArePermissionsGranted() - { - var cameraRequest = await Permissions.RequestAsync(); - var microphoneRequest = await Permissions.RequestAsync(); - if (cameraRequest is not PermissionStatus.Granted) - { - Trace.TraceInformation("Camera permission is not granted."); - return false; - } - - if (microphoneRequest is not PermissionStatus.Granted) - { - Trace.TraceInformation("Microphone permission is not granted."); - return false; - } - - return true; - } - /// /// Connects to the camera. /// diff --git a/src/CommunityToolkit.Maui.Camera/Handlers/CameraViewHandler.shared.cs b/src/CommunityToolkit.Maui.Camera/Handlers/CameraViewHandler.shared.cs index 9f4b12f36c..8de44f03b8 100644 --- a/src/CommunityToolkit.Maui.Camera/Handlers/CameraViewHandler.shared.cs +++ b/src/CommunityToolkit.Maui.Camera/Handlers/CameraViewHandler.shared.cs @@ -88,7 +88,6 @@ protected override async void ConnectHandler(NativePlatformCameraPreviewView pla { base.ConnectHandler(platformView); - await CameraManager.ArePermissionsGranted(); await CameraManager.ConnectCamera(CancellationToken.None); await cameraProvider.RefreshAvailableCameras(CancellationToken.None); } diff --git a/src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.android.cs b/src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.android.cs index 80de8e2073..a75d2c0665 100644 --- a/src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.android.cs +++ b/src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.android.cs @@ -23,16 +23,7 @@ static async Task InternalSaveAsync(string initialPath, string fileName, } AndroidUri? filePath = null; - - if (!OperatingSystem.IsAndroidVersionAtLeast(33)) - { - var status = await Permissions.RequestAsync().WaitAsync(cancellationToken).ConfigureAwait(false); - if (status is not PermissionStatus.Granted) - { - throw new PermissionException("Storage permission is not granted."); - } - } - + if (Android.OS.Environment.ExternalStorageDirectory is not null) { initialPath = initialPath.Replace(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, string.Empty, StringComparison.InvariantCulture); diff --git a/src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.tizen.cs b/src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.tizen.cs index 76d182a070..9032284d62 100644 --- a/src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.tizen.cs +++ b/src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.tizen.cs @@ -7,12 +7,6 @@ public sealed partial class FileSaverImplementation : IFileSaver { async Task InternalSaveAsync(string initialPath, string fileName, Stream stream, IProgress? progress, CancellationToken cancellationToken) { - var status = await Permissions.RequestAsync().WaitAsync(cancellationToken); - if (status is not PermissionStatus.Granted) - { - throw new PermissionException("Storage permission is not granted."); - } - using var dialog = new FileFolderDialog(true, initialPath, fileName: fileName); var path = await dialog.Open().WaitAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/CommunityToolkit.Maui.Core/Essentials/FolderPicker/FolderPickerImplementation.android.cs b/src/CommunityToolkit.Maui.Core/Essentials/FolderPicker/FolderPickerImplementation.android.cs index c606d6cf68..961ed6c8b7 100644 --- a/src/CommunityToolkit.Maui.Core/Essentials/FolderPicker/FolderPickerImplementation.android.cs +++ b/src/CommunityToolkit.Maui.Core/Essentials/FolderPicker/FolderPickerImplementation.android.cs @@ -24,15 +24,6 @@ static async Task InternalPickAsync(string initialPath, CancellationToke Folder? folder = null; - if (!OperatingSystem.IsAndroidVersionAtLeast(33)) - { - var statusRead = await Permissions.RequestAsync().WaitAsync(cancellationToken).ConfigureAwait(false); - if (statusRead is not PermissionStatus.Granted) - { - throw new PermissionException("Storage permission is not granted."); - } - } - if (Android.OS.Environment.ExternalStorageDirectory is not null) { initialPath = initialPath.Replace(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, string.Empty, StringComparison.InvariantCulture); diff --git a/src/CommunityToolkit.Maui.Core/Essentials/FolderPicker/FolderPickerImplementation.tizen.cs b/src/CommunityToolkit.Maui.Core/Essentials/FolderPicker/FolderPickerImplementation.tizen.cs index 24a72f613f..fb29ed75f1 100644 --- a/src/CommunityToolkit.Maui.Core/Essentials/FolderPicker/FolderPickerImplementation.tizen.cs +++ b/src/CommunityToolkit.Maui.Core/Essentials/FolderPicker/FolderPickerImplementation.tizen.cs @@ -8,12 +8,6 @@ public sealed partial class FolderPickerImplementation : IFolderPicker { async Task InternalPickAsync(string initialPath, CancellationToken cancellationToken) { - var status = await Permissions.RequestAsync().WaitAsync(cancellationToken); - if (status is not PermissionStatus.Granted) - { - throw new PermissionException("Storage permission is not granted."); - } - using var dialog = new FileFolderDialog(false, initialPath); var path = await dialog.Open().WaitAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/OfflineSpeechToTextImplementation.shared.cs b/src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/OfflineSpeechToTextImplementation.shared.cs index a998fcb3bb..e28d506e41 100644 --- a/src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/OfflineSpeechToTextImplementation.shared.cs +++ b/src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/OfflineSpeechToTextImplementation.shared.cs @@ -35,13 +35,6 @@ public event EventHandler StateChanged public async Task StartListenAsync(SpeechToTextOptions options, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); - - var isPermissionGranted = await IsSpeechPermissionAuthorized(cancellationToken).ConfigureAwait(false); - if (!isPermissionGranted) - { - throw new PermissionException($"{nameof(Permissions)}.{nameof(Permissions.Microphone)} Not Granted"); - } - await InternalStartListening(options, cancellationToken); } @@ -75,11 +68,5 @@ public async Task RequestPermissions(CancellationToken cancellationToken = var status = await Permissions.RequestAsync().WaitAsync(cancellationToken).ConfigureAwait(false); return status is PermissionStatus.Granted; } - - static async Task IsSpeechPermissionAuthorized(CancellationToken cancellationToken) - { - var status = await Permissions.CheckStatusAsync().WaitAsync(cancellationToken).ConfigureAwait(false); - return status is PermissionStatus.Granted; - } #endif } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/SharedSpeechToTextImplementation.macios.cs b/src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/SharedSpeechToTextImplementation.macios.cs index 9871bea8f9..da55d39f47 100644 --- a/src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/SharedSpeechToTextImplementation.macios.cs +++ b/src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/SharedSpeechToTextImplementation.macios.cs @@ -41,12 +41,6 @@ public Task RequestPermissions(CancellationToken cancellationToken = defau return taskResult.Task.WaitAsync(cancellationToken); } - static Task IsSpeechPermissionAuthorized(CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - return Task.FromResult(SFSpeechRecognizer.AuthorizationStatus is SFSpeechRecognizerAuthorizationStatus.Authorized); - } - static void InitializeAvAudioSession(out AVAudioSession sharedAvAudioSession) { sharedAvAudioSession = AVAudioSession.SharedInstance(); diff --git a/src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/SpeechToTextImplementation.shared.cs b/src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/SpeechToTextImplementation.shared.cs index 5d01647326..95cd305546 100644 --- a/src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/SpeechToTextImplementation.shared.cs +++ b/src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/SpeechToTextImplementation.shared.cs @@ -36,12 +36,6 @@ public async Task StartListenAsync(SpeechToTextOptions options, CancellationToke { cancellationToken.ThrowIfCancellationRequested(); - var isPermissionGranted = await IsSpeechPermissionAuthorized(cancellationToken).ConfigureAwait(false); - if (!isPermissionGranted) - { - throw new PermissionException($"{nameof(Permissions)}.{nameof(Permissions.Microphone)} Not Granted"); - } - await InternalStartListeningAsync(options, cancellationToken).ConfigureAwait(false); } @@ -70,11 +64,5 @@ public async Task RequestPermissions(CancellationToken cancellationToken = var status = await Permissions.RequestAsync().WaitAsync(cancellationToken).ConfigureAwait(false); return status is PermissionStatus.Granted; } - - static async Task IsSpeechPermissionAuthorized(CancellationToken cancellationToken) - { - var status = await Permissions.CheckStatusAsync().WaitAsync(cancellationToken).ConfigureAwait(false); - return status is PermissionStatus.Granted; - } #endif } \ No newline at end of file From 969ccdf6b1612877a7ceb1f370de5419d7fcb06d Mon Sep 17 00:00:00 2001 From: Vladislav Antonyuk Date: Fri, 7 Nov 2025 16:17:48 +0200 Subject: [PATCH 02/10] Fiz permission --- .../Pages/Views/CameraView/CameraViewPage.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs index 752c4ba5eb..6f78f65aaa 100644 --- a/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs +++ b/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs @@ -137,7 +137,7 @@ async void SaveVideo(object? sender, EventArgs e) } else { - var status = await Permissions.RequestAsync(); + var status = await Permissions.RequestAsync(); if (status is not PermissionStatus.Granted) { throw new PermissionException("Storage permission is not granted."); From ec22d71f5c48d80846d1324056e07439b6b369e6 Mon Sep 17 00:00:00 2001 From: Vladislav Antonyuk Date: Fri, 7 Nov 2025 16:33:00 +0200 Subject: [PATCH 03/10] better handle errors --- .../Views/CameraView/CameraViewPage.xaml.cs | 7 +++-- .../Essentials/FileSaverViewModel.cs | 31 +++++++++++++++---- .../Essentials/FolderPickerViewModel.cs | 31 +++++++++++++++---- .../OfflineSpeechToTextViewModel.cs | 2 +- .../Essentials/SpeechToTextViewModel.cs | 2 +- .../Essentials/SpeechToTextTests.cs | 2 +- 6 files changed, 57 insertions(+), 18 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs index 6f78f65aaa..53f2ca0d95 100644 --- a/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs +++ b/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs @@ -32,13 +32,13 @@ protected override async void OnAppearing() var microphoneRequest = await Permissions.RequestAsync(); if (cameraRequest is not PermissionStatus.Granted) { - Trace.TraceInformation("Camera permission is not granted."); + await Shell.Current.CurrentPage.DisplayAlert("Camera permission is not granted.", "Please grant the permission to use this feature.", "OK"); return; } if (microphoneRequest is not PermissionStatus.Granted) { - Trace.TraceInformation("Microphone permission is not granted."); + await Shell.Current.CurrentPage.DisplayAlert("Microphone permission is not granted.", "Please grant the permission to use this feature.", "OK"); return; } @@ -140,7 +140,8 @@ async void SaveVideo(object? sender, EventArgs e) var status = await Permissions.RequestAsync(); if (status is not PermissionStatus.Granted) { - throw new PermissionException("Storage permission is not granted."); + await Shell.Current.CurrentPage.DisplayAlert("Storage permission is not granted.", "Please grant the permission to use this feature.", "OK"); + return; } await fileSaver.SaveAsync("recording.mp4", videoRecordingStream); diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs index 764453a3bb..116e2112da 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs @@ -14,7 +14,11 @@ public partial class FileSaverViewModel(IFileSaver fileSaver) : BaseViewModel [RelayCommand] async Task SaveFile(CancellationToken cancellationToken) { - await RequestPermissions(); + if (!await RequestPermissions()) + { + return; + } + using var stream = new MemoryStream(Encoding.Default.GetBytes("Hello from the Community Toolkit!")); try { @@ -33,7 +37,11 @@ async Task SaveFile(CancellationToken cancellationToken) [RelayCommand] async Task SaveFileStatic(CancellationToken cancellationToken) { - await RequestPermissions(); + if (!await RequestPermissions()) + { + return; + } + using var stream = new MemoryStream(Encoding.Default.GetBytes("Hello from the Community Toolkit!")); var fileSaveResult = await FileSaver.SaveAsync("DCIM", "test.txt", stream, cancellationToken); if (fileSaveResult.IsSuccessful) @@ -49,7 +57,11 @@ async Task SaveFileStatic(CancellationToken cancellationToken) [RelayCommand] async Task SaveFileInstance(CancellationToken cancellationToken) { - await RequestPermissions(); + if (!await RequestPermissions()) + { + return; + } + using var client = new HttpClient(); const string communityToolkitNuGetUrl = "https://www.nuget.org/api/v2/package/CommunityToolkit.Maui/5.0.0"; @@ -71,9 +83,16 @@ async Task SaveFileInstance(CancellationToken cancellationToken) } } - async Task RequestPermissions() + async Task RequestPermissions() { - await Permissions.RequestAsync(); - await Permissions.RequestAsync(); + var read = await Permissions.RequestAsync(); + var write = await Permissions.RequestAsync(); + var areGranted = read == PermissionStatus.Granted && write == PermissionStatus.Granted; + if (!areGranted) + { + await Shell.Current.CurrentPage.DisplayAlert("Storage permission is not granted.", "Please grant the permission to use this feature.", "OK"); + } + + return areGranted; } } \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs index 0306f05279..8c4f48cc25 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs @@ -17,7 +17,11 @@ public FolderPickerViewModel(IFolderPicker folderPicker) [RelayCommand] async Task PickFolder(CancellationToken cancellationToken) { - await RequestPermissions(); + if (!await RequestPermissions()) + { + return; + } + var folderPickerResult = await folderPicker.PickAsync(cancellationToken); if (folderPickerResult.IsSuccessful) { @@ -32,7 +36,11 @@ async Task PickFolder(CancellationToken cancellationToken) [RelayCommand] async Task PickFolderStatic(CancellationToken cancellationToken) { - await RequestPermissions(); + if (!await RequestPermissions()) + { + return; + } + var folderResult = await FolderPicker.PickAsync("DCIM", cancellationToken); if (folderResult.IsSuccessful) { @@ -48,7 +56,11 @@ async Task PickFolderStatic(CancellationToken cancellationToken) [RelayCommand] async Task PickFolderInstance(CancellationToken cancellationToken) { - await RequestPermissions(); + if (!await RequestPermissions()) + { + return; + } + var folderPickerInstance = new FolderPickerImplementation(); try { @@ -66,9 +78,16 @@ async Task PickFolderInstance(CancellationToken cancellationToken) } } - async Task RequestPermissions() + async Task RequestPermissions() { - await Permissions.RequestAsync(); - await Permissions.RequestAsync(); + var read = await Permissions.RequestAsync(); + var write = await Permissions.RequestAsync(); + var areGranted = read == PermissionStatus.Granted && write == PermissionStatus.Granted; + if (!areGranted) + { + await Shell.Current.CurrentPage.DisplayAlert("Storage permission is not granted.", "Please grant the permission to use this feature.", "OK"); + } + + return areGranted; } } \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs index 535bed3d69..c52505f219 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs @@ -78,7 +78,7 @@ void HandleSpeechToTextStateChanged(object? sender, SpeechToTextStateChangedEven async Task RequestPermissions() { var microphoneGranted = await Permissions.RequestAsync(); - var recognitionGranted = await SpeechToText.RequestPermissions(CancellationToken.None); + var recognitionGranted = await speechToText.RequestPermissions(CancellationToken.None); return microphoneGranted == PermissionStatus.Granted && recognitionGranted; } } \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs index 239561faee..5432c29746 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs @@ -155,7 +155,7 @@ public async ValueTask DisposeAsync() async Task RequestPermissions() { var microphoneGranted = await Permissions.RequestAsync(); - var recognitionGranted = await SpeechToText.RequestPermissions(CancellationToken.None); + var recognitionGranted = await speechToText.RequestPermissions(CancellationToken.None); return microphoneGranted == PermissionStatus.Granted && recognitionGranted; } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.UnitTests/Essentials/SpeechToTextTests.cs b/src/CommunityToolkit.Maui.UnitTests/Essentials/SpeechToTextTests.cs index 8e2ffd11d1..66a99dd8c9 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Essentials/SpeechToTextTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Essentials/SpeechToTextTests.cs @@ -21,7 +21,7 @@ public void SpeechToTextTestsSetDefaultUpdatesInstance() public async Task StartListenAsyncFailsOnNet() { SpeechToText.SetDefault(new SpeechToTextImplementation()); - await Assert.ThrowsAsync(() => SpeechToText.StartListenAsync(new SpeechToTextOptions { Culture = CultureInfo.CurrentCulture }, TestContext.Current.CancellationToken)); + await Assert.ThrowsAsync(() => SpeechToText.StartListenAsync(new SpeechToTextOptions { Culture = CultureInfo.CurrentCulture }, TestContext.Current.CancellationToken)); } [Fact(Timeout = (int)TestDuration.Long)] From 8238e0c257f7ac520a07a6da15fb813a2aef20d9 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+TheCodeTraveler@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:17:13 -0800 Subject: [PATCH 04/10] Update Formatting --- .../Essentials/FileSaverViewModel.cs | 28 +++++++------- .../Essentials/FolderPickerViewModel.cs | 38 +++++++++---------- .../OfflineSpeechToTextViewModel.cs | 18 +++++---- .../Essentials/SpeechToTextViewModel.cs | 28 +++++++------- 4 files changed, 58 insertions(+), 54 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs index 116e2112da..12549aa95d 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs @@ -10,6 +10,21 @@ public partial class FileSaverViewModel(IFileSaver fileSaver) : BaseViewModel { [ObservableProperty] public partial double Progress { get; set; } + + static async Task RequestPermissions() + { + var readPermissionStatus = await Permissions.RequestAsync(); + var writePermissionStatus = await Permissions.RequestAsync(); + + if (readPermissionStatus is PermissionStatus.Granted + && writePermissionStatus is PermissionStatus.Granted) + { + return true; + } + + await Shell.Current.CurrentPage.DisplayAlertAsync("Storage permission is not granted.", "Please grant the permission to use this feature.", "OK"); + return false; + } [RelayCommand] async Task SaveFile(CancellationToken cancellationToken) @@ -82,17 +97,4 @@ async Task SaveFileInstance(CancellationToken cancellationToken) await Toast.Make($"File is not saved, {ex.Message}").Show(cancellationToken); } } - - async Task RequestPermissions() - { - var read = await Permissions.RequestAsync(); - var write = await Permissions.RequestAsync(); - var areGranted = read == PermissionStatus.Granted && write == PermissionStatus.Granted; - if (!areGranted) - { - await Shell.Current.CurrentPage.DisplayAlert("Storage permission is not granted.", "Please grant the permission to use this feature.", "OK"); - } - - return areGranted; - } } \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs index 8c4f48cc25..afdfedc9f9 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs @@ -5,13 +5,24 @@ namespace CommunityToolkit.Maui.Sample.ViewModels.Essentials; -public partial class FolderPickerViewModel : BaseViewModel +public partial class FolderPickerViewModel(IFolderPicker folderPicker) : BaseViewModel { - readonly IFolderPicker folderPicker; + readonly IFolderPicker folderPicker = folderPicker; - public FolderPickerViewModel(IFolderPicker folderPicker) + static async Task RequestPermissions() { - this.folderPicker = folderPicker; + var readPermissionStatus = await Permissions.RequestAsync(); + var writePermissionStatus = await Permissions.RequestAsync(); + + if (readPermissionStatus is PermissionStatus.Granted + && writePermissionStatus is PermissionStatus.Granted) + { + return true; + } + + await Shell.Current.CurrentPage.DisplayAlertAsync("Storage permission is not granted.", "Please grant the permission to use this feature.", "OK"); + + return false; } [RelayCommand] @@ -21,7 +32,7 @@ async Task PickFolder(CancellationToken cancellationToken) { return; } - + var folderPickerResult = await folderPicker.PickAsync(cancellationToken); if (folderPickerResult.IsSuccessful) { @@ -40,7 +51,7 @@ async Task PickFolderStatic(CancellationToken cancellationToken) { return; } - + var folderResult = await FolderPicker.PickAsync("DCIM", cancellationToken); if (folderResult.IsSuccessful) { @@ -60,7 +71,7 @@ async Task PickFolderInstance(CancellationToken cancellationToken) { return; } - + var folderPickerInstance = new FolderPickerImplementation(); try { @@ -77,17 +88,4 @@ async Task PickFolderInstance(CancellationToken cancellationToken) await Toast.Make($"Folder is not picked, {e.Message}").Show(cancellationToken); } } - - async Task RequestPermissions() - { - var read = await Permissions.RequestAsync(); - var write = await Permissions.RequestAsync(); - var areGranted = read == PermissionStatus.Granted && write == PermissionStatus.Granted; - if (!areGranted) - { - await Shell.Current.CurrentPage.DisplayAlert("Storage permission is not granted.", "Please grant the permission to use this feature.", "OK"); - } - - return areGranted; - } } \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs index c52505f219..d85e62ed92 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs @@ -23,11 +23,20 @@ public OfflineSpeechToTextViewModel() [ObservableProperty] public partial string? RecognitionText { get; set; } = "Welcome to .NET MAUI Community Toolkit!"; + + static async Task RequestPermissions(ISpeechToText speechToText) + { + var microphonePermissionStatus = await Permissions.RequestAsync(); + var isSpeechToTextRequestPermissionsGranted = await speechToText.RequestPermissions(CancellationToken.None); + + return microphonePermissionStatus is PermissionStatus.Granted + && isSpeechToTextRequestPermissionsGranted; + } [RelayCommand] async Task StartListen() { - var isGranted = await RequestPermissions(); + var isGranted = await RequestPermissions(speechToText); if (!isGranted) { await Toast.Make("Permission not granted").Show(CancellationToken.None); @@ -74,11 +83,4 @@ void HandleSpeechToTextStateChanged(object? sender, SpeechToTextStateChangedEven { OnPropertyChanged(nameof(State)); } - - async Task RequestPermissions() - { - var microphoneGranted = await Permissions.RequestAsync(); - var recognitionGranted = await speechToText.RequestPermissions(CancellationToken.None); - return microphoneGranted == PermissionStatus.Granted && recognitionGranted; - } } \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs index 5432c29746..4e18a8301e 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs @@ -40,6 +40,20 @@ public SpeechToTextViewModel(ITextToSpeech textToSpeech, [FromKeyedServices("Onl [ObservableProperty, NotifyCanExecuteChangedFor(nameof(StopListenCommand))] public partial bool CanStopListenExecute { get; set; } = false; + + public async ValueTask DisposeAsync() + { + await speechToText.DisposeAsync(); + } + + static async Task RequestPermissions(ISpeechToText speechToText) + { + var microphonePermissionStatus = await Permissions.RequestAsync(); + var isSpeechToTextPermissionsGranted = await speechToText.RequestPermissions(CancellationToken.None); + + return microphonePermissionStatus is PermissionStatus.Granted + && isSpeechToTextPermissionsGranted; + } [RelayCommand] async Task SetLocales(CancellationToken token) @@ -85,7 +99,7 @@ async Task StartListen() CanStartListenExecute = false; CanStopListenExecute = true; - var isGranted = await RequestPermissions(); + var isGranted = await RequestPermissions(speechToText); if (!isGranted) { await Toast.Make("Permission not granted").Show(CancellationToken.None); @@ -146,16 +160,4 @@ void HandleLocalesCollectionChanged(object? sender, NotifyCollectionChangedEvent { OnPropertyChanged(nameof(CurrentLocale)); } - - public async ValueTask DisposeAsync() - { - await speechToText.DisposeAsync(); - } - - async Task RequestPermissions() - { - var microphoneGranted = await Permissions.RequestAsync(); - var recognitionGranted = await speechToText.RequestPermissions(CancellationToken.None); - return microphoneGranted == PermissionStatus.Granted && recognitionGranted; - } } \ No newline at end of file From 44c92c22d3ea67f444bfcc87269084e71cb39091 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+TheCodeTraveler@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:18:03 -0800 Subject: [PATCH 05/10] Use `ThrowsAnyAsync` --- .../Layouts/StateContainerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommunityToolkit.Maui.UnitTests/Layouts/StateContainerTests.cs b/src/CommunityToolkit.Maui.UnitTests/Layouts/StateContainerTests.cs index 883e7a87ed..5e6a01c9b8 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Layouts/StateContainerTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Layouts/StateContainerTests.cs @@ -255,7 +255,7 @@ public async Task StateContainer_FuncAnimation_Timeout() var cancelledTokenSource = new CancellationTokenSource(TimeSpan.FromMicroseconds(1)); await Task.Delay(10, TestContext.Current.CancellationToken); - await Assert.ThrowsAsync(() => StateContainer.ChangeStateWithAnimation(layout, StateKey.Error, null, CustomAnimation, cancelledTokenSource.Token)); + await Assert.ThrowsAnyAsync(() => StateContainer.ChangeStateWithAnimation(layout, StateKey.Error, null, CustomAnimation, cancelledTokenSource.Token)); static Task CustomAnimation(VisualElement element, CancellationToken token) => element.RotateToAsync(0.75, 1000).WaitAsync(token); } From 6470b42865141f9240fa52acb6a8275fb30343dd Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+TheCodeTraveler@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:18:59 -0800 Subject: [PATCH 06/10] `dotnet format` --- .../Pages/Views/MediaElement/MediaElementPage.xaml.cs | 2 +- .../Converters/ConvertersGalleryViewModel.cs | 2 +- .../ViewModels/Essentials/FileSaverViewModel.cs | 10 +++++----- .../ViewModels/Essentials/FolderPickerViewModel.cs | 2 +- .../Essentials/OfflineSpeechToTextViewModel.cs | 8 ++++---- .../ViewModels/Essentials/SpeechToTextViewModel.cs | 10 +++++----- .../FileSaver/FileSaverImplementation.android.cs | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs index 9f46379171..a3c25c7f37 100644 --- a/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs +++ b/samples/CommunityToolkit.Maui.Sample/Pages/Views/MediaElement/MediaElementPage.xaml.cs @@ -288,4 +288,4 @@ async void DisplayPopup(object? sender, EventArgs? e) popupMediaElement.Stop(); popupMediaElement.Source = null; } -} +} \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Converters/ConvertersGalleryViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Converters/ConvertersGalleryViewModel.cs index aaf8e43e2e..febf791102 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Converters/ConvertersGalleryViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Converters/ConvertersGalleryViewModel.cs @@ -38,4 +38,4 @@ public partial class ConvertersGalleryViewModel() : BaseGalleryViewModel( SectionModel.Create(nameof(MultiMathExpressionConverter), "A converter that allows users to calculate multiple math expressions at runtime."), SectionModel.Create(nameof(TextCaseConverter), "A converter that allows users to convert the casing of an incoming string type binding. The Type property is used to define what kind of casing will be applied to the string."), SectionModel.Create(nameof(VariableMultiValueConverter), "A converter that allows you to combine multiple boolean bindings into a single binding."), -]); +]); \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs index 12549aa95d..09116b381a 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs @@ -10,14 +10,14 @@ public partial class FileSaverViewModel(IFileSaver fileSaver) : BaseViewModel { [ObservableProperty] public partial double Progress { get; set; } - + static async Task RequestPermissions() { var readPermissionStatus = await Permissions.RequestAsync(); var writePermissionStatus = await Permissions.RequestAsync(); if (readPermissionStatus is PermissionStatus.Granted - && writePermissionStatus is PermissionStatus.Granted) + && writePermissionStatus is PermissionStatus.Granted) { return true; } @@ -33,7 +33,7 @@ async Task SaveFile(CancellationToken cancellationToken) { return; } - + using var stream = new MemoryStream(Encoding.Default.GetBytes("Hello from the Community Toolkit!")); try { @@ -56,7 +56,7 @@ async Task SaveFileStatic(CancellationToken cancellationToken) { return; } - + using var stream = new MemoryStream(Encoding.Default.GetBytes("Hello from the Community Toolkit!")); var fileSaveResult = await FileSaver.SaveAsync("DCIM", "test.txt", stream, cancellationToken); if (fileSaveResult.IsSuccessful) @@ -76,7 +76,7 @@ async Task SaveFileInstance(CancellationToken cancellationToken) { return; } - + using var client = new HttpClient(); const string communityToolkitNuGetUrl = "https://www.nuget.org/api/v2/package/CommunityToolkit.Maui/5.0.0"; diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs index afdfedc9f9..2ab29e05e5 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs @@ -15,7 +15,7 @@ static async Task RequestPermissions() var writePermissionStatus = await Permissions.RequestAsync(); if (readPermissionStatus is PermissionStatus.Granted - && writePermissionStatus is PermissionStatus.Granted) + && writePermissionStatus is PermissionStatus.Granted) { return true; } diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs index d85e62ed92..b24d8b607c 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs @@ -23,14 +23,14 @@ public OfflineSpeechToTextViewModel() [ObservableProperty] public partial string? RecognitionText { get; set; } = "Welcome to .NET MAUI Community Toolkit!"; - + static async Task RequestPermissions(ISpeechToText speechToText) { var microphonePermissionStatus = await Permissions.RequestAsync(); var isSpeechToTextRequestPermissionsGranted = await speechToText.RequestPermissions(CancellationToken.None); - - return microphonePermissionStatus is PermissionStatus.Granted - && isSpeechToTextRequestPermissionsGranted; + + return microphonePermissionStatus is PermissionStatus.Granted + && isSpeechToTextRequestPermissionsGranted; } [RelayCommand] diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs index 4e18a8301e..603d393502 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs @@ -40,19 +40,19 @@ public SpeechToTextViewModel(ITextToSpeech textToSpeech, [FromKeyedServices("Onl [ObservableProperty, NotifyCanExecuteChangedFor(nameof(StopListenCommand))] public partial bool CanStopListenExecute { get; set; } = false; - + public async ValueTask DisposeAsync() { await speechToText.DisposeAsync(); } - + static async Task RequestPermissions(ISpeechToText speechToText) { var microphonePermissionStatus = await Permissions.RequestAsync(); var isSpeechToTextPermissionsGranted = await speechToText.RequestPermissions(CancellationToken.None); - - return microphonePermissionStatus is PermissionStatus.Granted - && isSpeechToTextPermissionsGranted; + + return microphonePermissionStatus is PermissionStatus.Granted + && isSpeechToTextPermissionsGranted; } [RelayCommand] diff --git a/src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.android.cs b/src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.android.cs index a75d2c0665..fb5b3658d6 100644 --- a/src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.android.cs +++ b/src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.android.cs @@ -23,7 +23,7 @@ static async Task InternalSaveAsync(string initialPath, string fileName, } AndroidUri? filePath = null; - + if (Android.OS.Environment.ExternalStorageDirectory is not null) { initialPath = initialPath.Replace(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, string.Empty, StringComparison.InvariantCulture); From 3784d77e7ca530e5467b4d3c80cd6535d84541c9 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+TheCodeTraveler@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:25:31 -0800 Subject: [PATCH 07/10] Update CameraViewPage.xaml.cs --- .../Pages/Views/CameraView/CameraViewPage.xaml.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs index 552bdbf3f9..541bea8416 100644 --- a/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs +++ b/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs @@ -28,17 +28,19 @@ public CameraViewPage(CameraViewViewModel viewModel, IFileSystem fileSystem, IFi protected override async void OnAppearing() { base.OnAppearing(); - var cameraRequest = await Permissions.RequestAsync(); - var microphoneRequest = await Permissions.RequestAsync(); - if (cameraRequest is not PermissionStatus.Granted) + + var cameraPermissionsRequest = await Permissions.RequestAsync(); + var microphonePermissionsRequest = await Permissions.RequestAsync(); + + if (cameraPermissionsRequest is not PermissionStatus.Granted) { - await Shell.Current.CurrentPage.DisplayAlert("Camera permission is not granted.", "Please grant the permission to use this feature.", "OK"); + await Shell.Current.CurrentPage.DisplayAlertAsync("Camera permission is not granted.", "Please grant the permission to use this feature.", "OK"); return; } - if (microphoneRequest is not PermissionStatus.Granted) + if (microphonePermissionsRequest is not PermissionStatus.Granted) { - await Shell.Current.CurrentPage.DisplayAlert("Microphone permission is not granted.", "Please grant the permission to use this feature.", "OK"); + await Shell.Current.CurrentPage.DisplayAlertAsync("Microphone permission is not granted.", "Please grant the permission to use this feature.", "OK"); return; } From 772f1de4e3b19c04e805f04717d20c83ff02f83a Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+TheCodeTraveler@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:26:07 -0800 Subject: [PATCH 08/10] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Pages/Views/CameraView/CameraViewPage.xaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs index 541bea8416..7dee92e068 100644 --- a/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs +++ b/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs @@ -43,7 +43,7 @@ protected override async void OnAppearing() await Shell.Current.CurrentPage.DisplayAlertAsync("Microphone permission is not granted.", "Please grant the permission to use this feature.", "OK"); return; } - + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(3)); await BindingContext.RefreshCamerasCommand.ExecuteAsync(cancellationTokenSource.Token); } @@ -145,7 +145,7 @@ async void SaveVideo(object? sender, EventArgs? e) await Shell.Current.CurrentPage.DisplayAlert("Storage permission is not granted.", "Please grant the permission to use this feature.", "OK"); return; } - + await fileSaver.SaveAsync("recording.mp4", videoRecordingStream); await videoRecordingStream.DisposeAsync(); videoRecordingStream = Stream.Null; From b7317d65493f0d1aafeb0c4d8ed2d79787d962eb Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+TheCodeTraveler@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:28:23 -0800 Subject: [PATCH 09/10] Update Method Naming --- .../CommunityToolkit.Maui.Sample.sln | 24 +++++++++++++++++++ .../Essentials/FileSaverViewModel.cs | 8 +++---- .../Essentials/FolderPickerViewModel.cs | 8 +++---- .../OfflineSpeechToTextViewModel.cs | 4 ++-- .../Essentials/SpeechToTextViewModel.cs | 4 ++-- 5 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 samples/CommunityToolkit.Maui.Sample/CommunityToolkit.Maui.Sample.sln diff --git a/samples/CommunityToolkit.Maui.Sample/CommunityToolkit.Maui.Sample.sln b/samples/CommunityToolkit.Maui.Sample/CommunityToolkit.Maui.Sample.sln new file mode 100644 index 0000000000..c8be8bec34 --- /dev/null +++ b/samples/CommunityToolkit.Maui.Sample/CommunityToolkit.Maui.Sample.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Maui.Sample", "CommunityToolkit.Maui.Sample.csproj", "{12577C4E-4903-8295-7D67-1A236A8D3CCE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {12577C4E-4903-8295-7D67-1A236A8D3CCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {12577C4E-4903-8295-7D67-1A236A8D3CCE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {12577C4E-4903-8295-7D67-1A236A8D3CCE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {12577C4E-4903-8295-7D67-1A236A8D3CCE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F6523D66-E13E-403D-AC13-A6619CF6A26C} + EndGlobalSection +EndGlobal diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs index 09116b381a..df4e8c2754 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FileSaverViewModel.cs @@ -11,7 +11,7 @@ public partial class FileSaverViewModel(IFileSaver fileSaver) : BaseViewModel [ObservableProperty] public partial double Progress { get; set; } - static async Task RequestPermissions() + static async Task ArePermissionsGranted() { var readPermissionStatus = await Permissions.RequestAsync(); var writePermissionStatus = await Permissions.RequestAsync(); @@ -29,7 +29,7 @@ static async Task RequestPermissions() [RelayCommand] async Task SaveFile(CancellationToken cancellationToken) { - if (!await RequestPermissions()) + if (!await ArePermissionsGranted()) { return; } @@ -52,7 +52,7 @@ async Task SaveFile(CancellationToken cancellationToken) [RelayCommand] async Task SaveFileStatic(CancellationToken cancellationToken) { - if (!await RequestPermissions()) + if (!await ArePermissionsGranted()) { return; } @@ -72,7 +72,7 @@ async Task SaveFileStatic(CancellationToken cancellationToken) [RelayCommand] async Task SaveFileInstance(CancellationToken cancellationToken) { - if (!await RequestPermissions()) + if (!await ArePermissionsGranted()) { return; } diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs index 2ab29e05e5..bbf4be84cd 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/FolderPickerViewModel.cs @@ -9,7 +9,7 @@ public partial class FolderPickerViewModel(IFolderPicker folderPicker) : BaseVie { readonly IFolderPicker folderPicker = folderPicker; - static async Task RequestPermissions() + static async Task ArePermissionsGranted() { var readPermissionStatus = await Permissions.RequestAsync(); var writePermissionStatus = await Permissions.RequestAsync(); @@ -28,7 +28,7 @@ static async Task RequestPermissions() [RelayCommand] async Task PickFolder(CancellationToken cancellationToken) { - if (!await RequestPermissions()) + if (!await ArePermissionsGranted()) { return; } @@ -47,7 +47,7 @@ async Task PickFolder(CancellationToken cancellationToken) [RelayCommand] async Task PickFolderStatic(CancellationToken cancellationToken) { - if (!await RequestPermissions()) + if (!await ArePermissionsGranted()) { return; } @@ -67,7 +67,7 @@ async Task PickFolderStatic(CancellationToken cancellationToken) [RelayCommand] async Task PickFolderInstance(CancellationToken cancellationToken) { - if (!await RequestPermissions()) + if (!await ArePermissionsGranted()) { return; } diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs index b24d8b607c..3af767e40a 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/OfflineSpeechToTextViewModel.cs @@ -24,7 +24,7 @@ public OfflineSpeechToTextViewModel() [ObservableProperty] public partial string? RecognitionText { get; set; } = "Welcome to .NET MAUI Community Toolkit!"; - static async Task RequestPermissions(ISpeechToText speechToText) + static async Task ArePermissionsGranted(ISpeechToText speechToText) { var microphonePermissionStatus = await Permissions.RequestAsync(); var isSpeechToTextRequestPermissionsGranted = await speechToText.RequestPermissions(CancellationToken.None); @@ -36,7 +36,7 @@ static async Task RequestPermissions(ISpeechToText speechToText) [RelayCommand] async Task StartListen() { - var isGranted = await RequestPermissions(speechToText); + var isGranted = await ArePermissionsGranted(speechToText); if (!isGranted) { await Toast.Make("Permission not granted").Show(CancellationToken.None); diff --git a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs index 603d393502..c37405784f 100644 --- a/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs +++ b/samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs @@ -46,7 +46,7 @@ public async ValueTask DisposeAsync() await speechToText.DisposeAsync(); } - static async Task RequestPermissions(ISpeechToText speechToText) + static async Task ArePermissionsGranted(ISpeechToText speechToText) { var microphonePermissionStatus = await Permissions.RequestAsync(); var isSpeechToTextPermissionsGranted = await speechToText.RequestPermissions(CancellationToken.None); @@ -99,7 +99,7 @@ async Task StartListen() CanStartListenExecute = false; CanStopListenExecute = true; - var isGranted = await RequestPermissions(speechToText); + var isGranted = await ArePermissionsGranted(speechToText); if (!isGranted) { await Toast.Make("Permission not granted").Show(CancellationToken.None); From 48fd3d23f5b70f5322f195f71d52c0639a35e80e Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+TheCodeTraveler@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:29:34 -0800 Subject: [PATCH 10/10] Delete CommunityToolkit.Maui.Sample.sln --- .../CommunityToolkit.Maui.Sample.sln | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 samples/CommunityToolkit.Maui.Sample/CommunityToolkit.Maui.Sample.sln diff --git a/samples/CommunityToolkit.Maui.Sample/CommunityToolkit.Maui.Sample.sln b/samples/CommunityToolkit.Maui.Sample/CommunityToolkit.Maui.Sample.sln deleted file mode 100644 index c8be8bec34..0000000000 --- a/samples/CommunityToolkit.Maui.Sample/CommunityToolkit.Maui.Sample.sln +++ /dev/null @@ -1,24 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.2.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Maui.Sample", "CommunityToolkit.Maui.Sample.csproj", "{12577C4E-4903-8295-7D67-1A236A8D3CCE}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {12577C4E-4903-8295-7D67-1A236A8D3CCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {12577C4E-4903-8295-7D67-1A236A8D3CCE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {12577C4E-4903-8295-7D67-1A236A8D3CCE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {12577C4E-4903-8295-7D67-1A236A8D3CCE}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {F6523D66-E13E-403D-AC13-A6619CF6A26C} - EndGlobalSection -EndGlobal