Archive completed meeting assistant changes
PR and Push Build/Test / build-and-test (push) Successful in 9m19s

This commit is contained in:
2026-06-26 13:15:47 +02:00
parent 7d16b0cad7
commit aecef30627
72 changed files with 2914 additions and 406 deletions
+34 -4
View File
@@ -6,10 +6,12 @@ namespace MeetingAssistant.Taskbar;
public enum MeetingTaskbarAction
{
EditRules,
OpenSubmenu,
StartRecording,
StopRecording,
AbortRecording,
SwitchProfile,
SelectMicrophone,
Exit
}
@@ -21,19 +23,29 @@ public sealed record MeetingTaskbarMenu(
public sealed record MeetingTaskbarMenuItem(
string Text,
MeetingTaskbarAction Action,
string? ProfileName = null);
string? ProfileName = null,
string? MicrophoneDeviceId = null,
bool IsChecked = false,
IReadOnlyList<MeetingTaskbarMenuItem>? Items = null);
public static class MeetingTaskbarMenuBuilder
{
public static MeetingTaskbarMenu Build(
RecordingStatus status,
IReadOnlyList<LaunchProfile> launchProfiles)
IReadOnlyList<LaunchProfile> launchProfiles,
IReadOnlyList<MicrophoneDevice>? microphones = null,
string? currentMicrophoneDeviceId = null)
{
var items = new List<MeetingTaskbarMenuItem>
{
new("Settings and logs", MeetingTaskbarAction.EditRules)
};
if (microphones is { Count: > 0 })
{
items.Add(BuildMicrophoneMenu(microphones, currentMicrophoneDeviceId));
}
if (status.IsRecording)
{
items.Add(new MeetingTaskbarMenuItem(
@@ -72,6 +84,24 @@ public static class MeetingTaskbarMenuBuilder
items);
}
private static MeetingTaskbarMenuItem BuildMicrophoneMenu(
IReadOnlyList<MicrophoneDevice> microphones,
string? currentMicrophoneDeviceId)
{
var microphoneItems = microphones
.Select(microphone => new MeetingTaskbarMenuItem(
microphone.Name,
MeetingTaskbarAction.SelectMicrophone,
MicrophoneDeviceId: microphone.Id,
IsChecked: string.Equals(microphone.Id, currentMicrophoneDeviceId, StringComparison.Ordinal)))
.ToArray();
return new MeetingTaskbarMenuItem(
"Microphone",
MeetingTaskbarAction.OpenSubmenu,
Items: microphoneItems);
}
private static string BuildTooltip(RecordingStatus status)
{
return status.State switch
@@ -104,8 +134,8 @@ public static class MeetingTaskbarMenuBuilder
public static class MeetingTaskbarExitPolicy
{
public static bool RequiresConfirmation(RecordingStatus status)
public static bool RequiresConfirmation(RecordingProcessState state)
{
return status.State != RecordingProcessState.Idle;
return state != RecordingProcessState.Idle;
}
}
@@ -14,6 +14,8 @@ public sealed class UnoTaskbarIconService : IHostedService, IDisposable
private readonly MeetingRecordingCoordinator coordinator;
private readonly ILaunchProfileOptionsProvider launchProfiles;
private readonly IMicrophoneDeviceProvider microphones;
private readonly MicrophoneDeviceSelection microphoneSelection;
private readonly IWorkflowRulesEditorWindowService rulesEditorWindow;
private readonly IHostApplicationLifetime applicationLifetime;
private readonly ILogger<UnoTaskbarIconService> logger;
@@ -29,12 +31,16 @@ public sealed class UnoTaskbarIconService : IHostedService, IDisposable
public UnoTaskbarIconService(
MeetingRecordingCoordinator coordinator,
ILaunchProfileOptionsProvider launchProfiles,
IMicrophoneDeviceProvider microphones,
MicrophoneDeviceSelection microphoneSelection,
IWorkflowRulesEditorWindowService rulesEditorWindow,
IHostApplicationLifetime applicationLifetime,
ILogger<UnoTaskbarIconService> logger)
{
this.coordinator = coordinator;
this.launchProfiles = launchProfiles;
this.microphones = microphones;
this.microphoneSelection = microphoneSelection;
this.rulesEditorWindow = rulesEditorWindow;
this.applicationLifetime = applicationLifetime;
this.logger = logger;
@@ -167,6 +173,8 @@ public sealed class UnoTaskbarIconService : IHostedService, IDisposable
private MeetingTaskbarMenu BuildMenu()
{
var profiles = launchProfiles.GetProfiles();
var activeOptions = GetActiveProfileOptions(profiles);
var microphoneSnapshot = microphones.GetMicrophoneSnapshot(activeOptions);
var profilesSignature = string.Join(
", ",
profiles.Select(profile => $"{profile.Name}:{profile.Options.Hotkey.Toggle}"));
@@ -178,7 +186,9 @@ public sealed class UnoTaskbarIconService : IHostedService, IDisposable
return MeetingTaskbarMenuBuilder.Build(
coordinator.CurrentStatus,
profiles);
profiles,
microphoneSnapshot.Available,
microphoneSnapshot.Current?.Id);
}
private PopupMenu BuildContextMenu(MeetingTaskbarMenu menu)
@@ -194,14 +204,33 @@ public sealed class UnoTaskbarIconService : IHostedService, IDisposable
}
var menuItem = menu.Items[index];
popupMenu.Items.Add(new PopupMenuItem(
menuItem.Text,
(_, _) => _ = ExecuteMenuItemAsync(menuItem)));
popupMenu.Items.Add(BuildPopupItem(menuItem));
}
return popupMenu;
}
private PopupItem BuildPopupItem(MeetingTaskbarMenuItem menuItem)
{
if (menuItem.Items is { Count: > 0 })
{
var submenu = new PopupSubMenu(menuItem.Text);
foreach (var child in menuItem.Items)
{
submenu.Items.Add(BuildPopupItem(child));
}
return submenu;
}
return new PopupMenuItem(
menuItem.Text,
(_, _) => _ = ExecuteMenuItemAsync(menuItem))
{
Checked = menuItem.IsChecked
};
}
private async Task ExecuteMenuItemAsync(MeetingTaskbarMenuItem item)
{
try
@@ -227,6 +256,13 @@ public sealed class UnoTaskbarIconService : IHostedService, IDisposable
break;
case MeetingTaskbarAction.SwitchProfile:
await coordinator.ToggleAsync(item.ProfileName, CancellationToken.None);
break;
case MeetingTaskbarAction.SelectMicrophone:
if (!string.IsNullOrWhiteSpace(item.MicrophoneDeviceId))
{
microphoneSelection.Select(item.MicrophoneDeviceId);
}
break;
case MeetingTaskbarAction.Exit:
ExitApplication();
@@ -253,13 +289,47 @@ public sealed class UnoTaskbarIconService : IHostedService, IDisposable
{
return string.Join(
"|",
menu.Items.Select(item => $"{item.Action}:{item.ProfileName}:{item.Text}"));
FlattenMenuItems(menu.Items).Select(item =>
$"{item.Action}:{item.ProfileName}:{item.MicrophoneDeviceId}:{item.IsChecked}:{item.Text}"));
}
private static IEnumerable<MeetingTaskbarMenuItem> FlattenMenuItems(
IEnumerable<MeetingTaskbarMenuItem> items)
{
foreach (var item in items)
{
yield return item;
if (item.Items is null)
{
continue;
}
foreach (var child in FlattenMenuItems(item.Items))
{
yield return child;
}
}
}
private MeetingAssistantOptions GetActiveProfileOptions(IReadOnlyList<LaunchProfile> profiles)
{
var activeProfile = coordinator.CurrentStatus.LaunchProfile;
return profiles.FirstOrDefault(profile => string.Equals(
profile.Name,
activeProfile,
StringComparison.OrdinalIgnoreCase))?.Options ??
profiles.FirstOrDefault(profile => string.Equals(
profile.Name,
"default",
StringComparison.OrdinalIgnoreCase))?.Options ??
profiles.FirstOrDefault()?.Options ??
new MeetingAssistantOptions();
}
private void ExitApplication()
{
var status = coordinator.CurrentStatus;
if (MeetingTaskbarExitPolicy.RequiresConfirmation(status) && !ConfirmExitDuringProcessing(status.State))
if (MeetingTaskbarExitPolicy.RequiresConfirmation(status.State) && !ConfirmExitDuringProcessing(status.State))
{
logger.LogInformation("Taskbar exit canceled while meeting state was {State}", status.State);
return;