Skip to content

Commit 3f6b08b

Browse files
Merge branch 'main' into Return-Camera-Image-from-CameraView.CaptureImage()
2 parents 3a1abfc + a438997 commit 3f6b08b

File tree

15 files changed

+163
-72
lines changed

15 files changed

+163
-72
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<NuGetAuditMode>all</NuGetAuditMode>
1717

1818
<!-- MAUI Specific -->
19-
<MauiPackageVersion>9.0.70</MauiPackageVersion>
19+
<MauiPackageVersion>9.0.80</MauiPackageVersion>
2020
<NextMauiPackageVersion>10.0.0</NextMauiPackageVersion>
2121
<MauiStrictXamlCompilation>true</MauiStrictXamlCompilation>
2222
<SkipValidateMauiImplicitPackageReferences>true</SkipValidateMauiImplicitPackageReferences>

samples/CommunityToolkit.Maui.Sample/Platforms/Windows/Package.appxmanifest

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757

5858
<Capabilities>
5959
<rescap:Capability Name="runFullTrust" />
60+
<DeviceCapability Name="microphone"/>
6061
</Capabilities>
6162

6263
</Package>

samples/CommunityToolkit.Maui.Sample/ViewModels/Essentials/SpeechToTextViewModel.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@
88

99
namespace CommunityToolkit.Maui.Sample.ViewModels.Essentials;
1010

11-
public partial class SpeechToTextViewModel : BaseViewModel
11+
public partial class SpeechToTextViewModel : BaseViewModel, IAsyncDisposable
1212
{
1313
const string defaultLanguage = "en-US";
14-
const string defaultLanguage_android = "en";
15-
const string defaultLanguage_tizen = "en_US";
1614

1715
readonly ITextToSpeech textToSpeech;
1816
readonly ISpeechToText speechToText;
@@ -55,7 +53,7 @@ async Task SetLocales(CancellationToken token)
5553
Locales.Add(locale);
5654
}
5755

58-
CurrentLocale = Locales.FirstOrDefault(x => x.Language is defaultLanguage or defaultLanguage_android or defaultLanguage_tizen) ?? Locales.FirstOrDefault();
56+
CurrentLocale = Locales.FirstOrDefault();
5957
}
6058

6159
[RelayCommand]
@@ -148,4 +146,9 @@ void HandleLocalesCollectionChanged(object? sender, NotifyCollectionChangedEvent
148146
{
149147
OnPropertyChanged(nameof(CurrentLocale));
150148
}
149+
150+
public async ValueTask DisposeAsync()
151+
{
152+
await speechToText.DisposeAsync();
153+
}
151154
}

src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/OfflineSpeechToTextImplementation.windows.cs

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@ public SpeechToTextState CurrentState
2828
public ValueTask DisposeAsync()
2929
{
3030
InternalStopListening();
31-
32-
offlineSpeechRecognizer?.Dispose();
33-
offlineSpeechRecognizer = null;
3431
return ValueTask.CompletedTask;
3532
}
3633

@@ -39,7 +36,6 @@ Task InternalStartListening(SpeechToTextOptions options, CancellationToken token
3936
Initialize(options);
4037

4138
offlineSpeechRecognizer.AudioStateChanged += OfflineSpeechRecognizer_StateChanged;
42-
offlineSpeechRecognizer.LoadGrammar(new DictationGrammar());
4339

4440
offlineSpeechRecognizer.InitialSilenceTimeout = TimeSpan.MaxValue;
4541
offlineSpeechRecognizer.BabbleTimeout = TimeSpan.MaxValue;
@@ -48,7 +44,12 @@ Task InternalStartListening(SpeechToTextOptions options, CancellationToken token
4844

4945
offlineSpeechRecognizer.RecognizeCompleted += OnRecognizeCompleted;
5046
offlineSpeechRecognizer.SpeechRecognized += OnSpeechRecognized;
51-
offlineSpeechRecognizer.RecognizeAsync(RecognizeMode.Multiple);
47+
48+
if (offlineSpeechRecognizer.AudioState == AudioState.Stopped)
49+
{
50+
offlineSpeechRecognizer.RecognizeAsync(RecognizeMode.Multiple);
51+
}
52+
5253
return Task.CompletedTask;
5354
}
5455

@@ -81,19 +82,26 @@ void InternalStopListening()
8182
{
8283
try
8384
{
84-
if (offlineSpeechRecognizer is not null)
85+
if (offlineSpeechRecognizer is not null && offlineSpeechRecognizer.AudioState != AudioState.Stopped)
8586
{
8687
offlineSpeechRecognizer.RecognizeAsyncStop();
87-
88-
offlineSpeechRecognizer.AudioStateChanged -= OfflineSpeechRecognizer_StateChanged;
89-
offlineSpeechRecognizer.RecognizeCompleted -= OnRecognizeCompleted;
90-
offlineSpeechRecognizer.SpeechRecognized -= OnSpeechRecognized;
9188
}
9289
}
9390
catch
9491
{
9592
// ignored. Recording may be already stopped
9693
}
94+
finally
95+
{
96+
if (offlineSpeechRecognizer is not null)
97+
{
98+
offlineSpeechRecognizer.AudioStateChanged -= OfflineSpeechRecognizer_StateChanged;
99+
offlineSpeechRecognizer.RecognizeCompleted -= OnRecognizeCompleted;
100+
offlineSpeechRecognizer.SpeechRecognized -= OnSpeechRecognized;
101+
offlineSpeechRecognizer?.Dispose();
102+
offlineSpeechRecognizer = null;
103+
}
104+
}
97105
}
98106

99107
[MemberNotNull(nameof(recognitionText), nameof(offlineSpeechRecognizer), nameof(speechToTextOptions))]
@@ -102,6 +110,7 @@ void Initialize(SpeechToTextOptions options)
102110
speechToTextOptions = options;
103111
recognitionText = string.Empty;
104112
offlineSpeechRecognizer = new SpeechRecognitionEngine(options.Culture);
113+
offlineSpeechRecognizer.LoadGrammarAsync(new DictationGrammar());
105114
}
106115

107116
void OfflineSpeechRecognizer_StateChanged(object? sender, AudioStateChangedEventArgs e)

src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/SpeechToTextImplementation.windows.cs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1+
using System.Diagnostics;
12
using System.Diagnostics.CodeAnalysis;
2-
using System.Globalization;
3-
using System.Speech.Recognition;
43
using Microsoft.Maui.ApplicationModel;
54
using Windows.Globalization;
65
using Windows.Media.SpeechRecognition;
@@ -35,8 +34,6 @@ public SpeechToTextState CurrentState
3534
public async ValueTask DisposeAsync()
3635
{
3736
await StopRecording(CancellationToken.None);
38-
speechRecognizer?.Dispose();
39-
speechRecognizer = null;
4037
}
4138

4239
async Task InternalStartListeningAsync(SpeechToTextOptions options, CancellationToken cancellationToken)
@@ -48,7 +45,10 @@ async Task InternalStartListeningAsync(SpeechToTextOptions options, Cancellation
4845
speechRecognizer.ContinuousRecognitionSession.Completed += OnCompleted;
4946
try
5047
{
51-
await speechRecognizer.ContinuousRecognitionSession.StartAsync().AsTask(cancellationToken);
48+
if (speechRecognizer.State == SpeechRecognizerState.Idle)
49+
{
50+
await speechRecognizer.ContinuousRecognitionSession.StartAsync().AsTask(cancellationToken);
51+
}
5252
}
5353
catch (Exception ex) when ((uint)ex.HResult is privacyStatementDeclinedCode)
5454
{
@@ -88,12 +88,8 @@ async Task StopRecording(CancellationToken cancellationToken)
8888
{
8989
try
9090
{
91-
if (speechRecognizer is not null)
91+
if (speechRecognizer is not null && speechRecognizer.State != SpeechRecognizerState.Idle)
9292
{
93-
speechRecognizer.StateChanged -= SpeechRecognizer_StateChanged;
94-
speechRecognizer.ContinuousRecognitionSession.ResultGenerated -= ResultGenerated;
95-
speechRecognizer.ContinuousRecognitionSession.Completed -= OnCompleted;
96-
9793
cancellationToken.ThrowIfCancellationRequested();
9894
await speechRecognizer.ContinuousRecognitionSession.StopAsync().AsTask(cancellationToken);
9995
}
@@ -102,6 +98,17 @@ async Task StopRecording(CancellationToken cancellationToken)
10298
{
10399
// ignored. Recording may be already stopped
104100
}
101+
finally
102+
{
103+
if (speechRecognizer is not null)
104+
{
105+
speechRecognizer.StateChanged -= SpeechRecognizer_StateChanged;
106+
speechRecognizer.ContinuousRecognitionSession.ResultGenerated -= ResultGenerated;
107+
speechRecognizer.ContinuousRecognitionSession.Completed -= OnCompleted;
108+
speechRecognizer?.Dispose();
109+
speechRecognizer = null;
110+
}
111+
}
105112
}
106113

107114
[MemberNotNull(nameof(recognitionText), nameof(speechRecognizer), nameof(speechToTextOptions))]

src/CommunityToolkit.Maui.Core/Platform/StatusBar/StatusBar.android.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ static void PlatformSetStyle(StatusBarStyle style)
8383
static void SetStatusBarAppearance(Activity activity, bool isLightStatusBars)
8484
{
8585
var window = activity.GetCurrentWindow();
86-
var windowController = WindowCompat.GetInsetsController(window, window.DecorView);
87-
windowController.AppearanceLightStatusBars = isLightStatusBars;
86+
if (WindowCompat.GetInsetsController(window, window.DecorView) is WindowInsetsControllerCompat windowController)
87+
{
88+
windowController.AppearanceLightStatusBars = isLightStatusBars;
89+
}
8890
}
8991
}

src/CommunityToolkit.Maui.MediaElement/Services/MediaControlsService.android.cs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ sealed partial class MediaControlsService : Service
2222
bool isDisposed;
2323

2424
PlayerNotificationManager? playerNotificationManager;
25-
NotificationCompat.Builder? notification;
25+
NotificationCompat.Builder? notificationBuilder;
2626

2727
public event EventHandler TaskRemoved
2828
{
@@ -77,10 +77,10 @@ public override void OnRebind(Intent? intent)
7777
StartForegroundServices();
7878
}
7979

80-
[MemberNotNull(nameof(NotificationManager), nameof(notification))]
80+
[MemberNotNull(nameof(NotificationManager), nameof(notificationBuilder))]
8181
public void UpdateNotifications(in MediaSession session, in PlatformMediaElement mediaElement)
8282
{
83-
ArgumentNullException.ThrowIfNull(notification);
83+
ArgumentNullException.ThrowIfNull(notificationBuilder);
8484
ArgumentNullException.ThrowIfNull(NotificationManager);
8585

8686
var style = new MediaStyleNotificationHelper.MediaStyle(session);
@@ -89,8 +89,8 @@ public void UpdateNotifications(in MediaSession session, in PlatformMediaElement
8989
SetLegacyNotifications(session, mediaElement);
9090
}
9191

92-
notification.SetStyle(style);
93-
NotificationManagerCompat.From(Platform.AppContext).Notify(1, notification.Build());
92+
notificationBuilder.SetStyle(style);
93+
NotificationManagerCompat.From(Platform.AppContext)?.Notify(1, notificationBuilder.Build());
9494
}
9595

9696
[MemberNotNull(nameof(playerNotificationManager))]
@@ -152,26 +152,29 @@ static void CreateNotificationChannel(in NotificationManager notificationMnaMana
152152
notificationMnaManager.CreateNotificationChannel(channel);
153153
}
154154

155-
[MemberNotNull(nameof(notification), nameof(NotificationManager))]
155+
[MemberNotNull(nameof(notificationBuilder), nameof(NotificationManager))]
156156
void StartForegroundServices()
157157
{
158158
NotificationManager ??= GetSystemService(NotificationService) as NotificationManager ?? throw new InvalidOperationException($"{nameof(NotificationManager)} cannot be null");
159-
notification ??= new NotificationCompat.Builder(Platform.AppContext, "1");
159+
notificationBuilder ??= new NotificationCompat.Builder(Platform.AppContext, "1");
160160

161-
notification.SetSmallIcon(Resource.Drawable.media3_notification_small_icon);
162-
notification.SetAutoCancel(false);
163-
notification.SetForegroundServiceBehavior(NotificationCompat.ForegroundServiceImmediate);
164-
notification.SetVisibility(NotificationCompat.VisibilityPublic);
161+
notificationBuilder.SetSmallIcon(Resource.Drawable.media3_notification_small_icon);
162+
notificationBuilder.SetAutoCancel(false);
163+
notificationBuilder.SetForegroundServiceBehavior(NotificationCompat.ForegroundServiceImmediate);
164+
notificationBuilder.SetVisibility(NotificationCompat.VisibilityPublic);
165165

166166
CreateNotificationChannel(NotificationManager);
167167

168168
if (OperatingSystem.IsAndroidVersionAtLeast(29))
169169
{
170-
StartForeground(1, notification.Build(), ForegroundService.TypeMediaPlayback);
170+
if (notificationBuilder.Build() is Notification notification)
171+
{
172+
StartForeground(1, notification, ForegroundService.TypeMediaPlayback);
173+
}
171174
}
172175
else
173176
{
174-
StartForeground(1, notification.Build());
177+
StartForeground(1, notificationBuilder.Build());
175178
}
176179
}
177180
}

src/CommunityToolkit.Maui.MediaElement/Views/MauiMediaElement.android.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,12 @@ void SetSystemBarsVisibility()
173173
| SystemUiFlags.Fullscreen
174174
| SystemUiFlags.Immersive;
175175
}
176-
177-
windowInsetsControllerCompat.Hide(barTypes);
178-
windowInsetsControllerCompat.SystemBarsBehavior = WindowInsetsControllerCompat.BehaviorShowTransientBarsBySwipe;
176+
177+
if(windowInsetsControllerCompat is not null)
178+
{
179+
windowInsetsControllerCompat.Hide(barTypes);
180+
windowInsetsControllerCompat.SystemBarsBehavior = WindowInsetsControllerCompat.BehaviorShowTransientBarsBySwipe;
181+
}
179182

180183
}
181184
else
@@ -192,8 +195,12 @@ void SetSystemBarsVisibility()
192195
currentWindow.DecorView.SystemUiFlags = (SystemUiFlags)defaultSystemUiVisibility;
193196
}
194197

195-
windowInsetsControllerCompat.Show(barTypes);
196-
windowInsetsControllerCompat.SystemBarsBehavior = WindowInsetsControllerCompat.BehaviorDefault;
198+
if(windowInsetsControllerCompat is not null)
199+
{
200+
windowInsetsControllerCompat.Show(barTypes);
201+
windowInsetsControllerCompat.SystemBarsBehavior = WindowInsetsControllerCompat.BehaviorDefault;
202+
}
203+
197204
WindowCompat.SetDecorFitsSystemWindows(currentWindow, true);
198205
}
199206
}

src/CommunityToolkit.Maui.UnitTests/CommunityToolkit.Maui.UnitTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<ItemGroup>
1313
<PackageReference Include="FluentAssertions" Version="8.3.0" />
1414
<PackageReference Include="FluentAssertions.Analyzers" Version="0.34.1" />
15+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
1516
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
1617
<PackageReference Include="xunit.v3" Version="2.0.2" />
1718
<PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage" Version="17.14.2" />

src/CommunityToolkit.Maui.UnitTests/Extensions/PopupExtensionsTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,6 +1430,35 @@ public async Task ClosePopupAsyncT_ShouldClosePopupUsingPageAndReturnResult()
14301430
Assert.Equal(expectedResult, popupResult.Result);
14311431
Assert.False(popupResult.WasDismissedByTappingOutsideOfPopup);
14321432
}
1433+
1434+
[Fact(Timeout = (int)TestDuration.Short)]
1435+
public async Task ShowPopupAsync_TaskShouldCompleteWhenCloseAsyncIsCalled()
1436+
{
1437+
// Arrange
1438+
const int expectedResult = 2;
1439+
Task<IPopupResult> showPopupAsyncTask;
1440+
1441+
if (Application.Current?.Windows[0].Page is not Page page)
1442+
{
1443+
throw new InvalidOperationException("Page cannot be null");
1444+
}
1445+
1446+
// Act
1447+
showPopupAsyncTask = page.ShowPopupAsync(new MockPopup(), token: TestContext.Current.CancellationToken);
1448+
1449+
// Assert
1450+
Assert.Single(page.Navigation.ModalStack);
1451+
Assert.IsType<PopupPage>(page.Navigation.ModalStack[0]);
1452+
1453+
// Act
1454+
var popupResult = await page.ClosePopupAsync(expectedResult, TestContext.Current.CancellationToken);
1455+
await showPopupAsyncTask;
1456+
1457+
// Assert
1458+
Assert.Empty(page.Navigation.ModalStack);
1459+
Assert.Equal(expectedResult, popupResult.Result);
1460+
Assert.False(popupResult.WasDismissedByTappingOutsideOfPopup);
1461+
}
14331462
}
14341463

14351464
sealed class ViewWithIQueryAttributable : Button, IQueryAttributable

0 commit comments

Comments
 (0)