Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

chore: add connect/disconnect notifications on Coder Connect#140

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
ibetitsmike wants to merge5 commits intomain
base:main
Choose a base branch
Loading
frommike/40-connect-notification
Open
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 38 additions & 8 deletionsApp/Services/UserNotifier.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Coder.Desktop.App.Views;
using Microsoft.Extensions.Logging;
using Microsoft.Windows.AppNotifications;
using Microsoft.Windows.AppNotifications.Builder;
Expand All@@ -20,17 +21,26 @@ public interface IUserNotifier : INotificationHandler, IAsyncDisposable
public void UnregisterHandler(string name);

public Task ShowErrorNotification(string title, string message, CancellationToken ct = default);
public Task ShowActionNotification(string title, string message, string handlerName, IDictionary<string, string>? args = null, CancellationToken ct = default);
public Task ShowActionNotification(string title, string message, string? handlerName, IDictionary<string, string>? args = null, CancellationToken ct = default);
}

public class UserNotifier(ILogger<UserNotifier> logger, IDispatcherQueueManager dispatcherQueueManager) : IUserNotifier
public class UserNotifier : IUserNotifier
{
private const string CoderNotificationHandler = "CoderNotificationHandler";

private readonly AppNotificationManager _notificationManager = AppNotificationManager.Default;
private readonly ILogger<UserNotifier> _logger;
private readonly IDispatcherQueueManager _dispatcherQueueManager;

private ConcurrentDictionary<string, INotificationHandler> Handlers { get; } = new();

public UserNotifier(ILogger<UserNotifier> logger, IDispatcherQueueManager dispatcherQueueManager)
{
_logger = logger;
_dispatcherQueueManager = dispatcherQueueManager;
Handlers.TryAdd(nameof(DefaultNotificationHandler), new DefaultNotificationHandler());
}

public ValueTask DisposeAsync()
{
return ValueTask.CompletedTask;
Expand DownExpand Up@@ -61,10 +71,18 @@ public Task ShowErrorNotification(string title, string message, CancellationToke
return Task.CompletedTask;
}

public Task ShowActionNotification(string title, string message, string handlerName, IDictionary<string, string>? args = null, CancellationToken ct = default)
public Task ShowActionNotification(string title, string message, string? handlerName, IDictionary<string, string>? args = null, CancellationToken ct = default)
{
if (!Handlers.TryGetValue(handlerName, out _))
throw new InvalidOperationException($"No action handler with the name '{handlerName}' is registered.");
if (handlerName == null)
{
// Use default handler if no handler name is provided
handlerName = nameof(DefaultNotificationHandler);
}
else
{
if (!Handlers.TryGetValue(handlerName, out _))
throw new InvalidOperationException($"No action handler with the name '{handlerName}' is registered. Use null for default");
}

var builder = new AppNotificationBuilder()
.AddText(title)
Expand All@@ -90,20 +108,32 @@ public void HandleNotificationActivation(IDictionary<string, string> args)

if (!Handlers.TryGetValue(handlerName, out var handler))
{
logger.LogWarning("no action handler '{HandlerName}' found for notification activation, ignoring", handlerName);
_logger.LogWarning("no action handler '{HandlerName}' found for notification activation, ignoring", handlerName);
return;
}

dispatcherQueueManager.RunInUiThread(() =>
_dispatcherQueueManager.RunInUiThread(() =>
{
try
{
handler.HandleNotificationActivation(args);
}
catch (Exception ex)
{
logger.LogWarning(ex, "could not handle activation for notification with handler '{HandlerName}", handlerName);
_logger.LogWarning(ex, "could not handle activation for notification with handler '{HandlerName}", handlerName);
}
});
}
}

public class DefaultNotificationHandler : INotificationHandler
{
public void HandleNotificationActivation(IDictionary<string, string> _)
{
var app = (App)Microsoft.UI.Xaml.Application.Current;
if (app != null && app.TrayWindow != null)
{
app.TrayWindow.Tray_Open();
}
}
}
58 changes: 56 additions & 2 deletionsApp/Views/TrayWindow.xaml.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,9 @@
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Windows.Graphics;
using Windows.System;
Expand All@@ -33,17 +35,23 @@ public sealed partial class TrayWindow : Window
private int _lastWindowHeight;
private Storyboard? _currentSb;

private VpnLifecycle prevVpnLifecycle = VpnLifecycle.Stopped;
private RpcLifecycle prevRpcLifecycle = RpcLifecycle.Disconnected;

private readonly IRpcController _rpcController;
private readonly ICredentialManager _credentialManager;
private readonly ISyncSessionController _syncSessionController;
private readonly IUpdateController _updateController;
private readonly IUserNotifier _userNotifier;
private readonly TrayWindowLoadingPage _loadingPage;
private readonly TrayWindowDisconnectedPage _disconnectedPage;
private readonly TrayWindowLoginRequiredPage _loginRequiredPage;
private readonly TrayWindowMainPage _mainPage;

public TrayWindow(IRpcController rpcController, ICredentialManager credentialManager,
public TrayWindow(
IRpcController rpcController, ICredentialManager credentialManager,
ISyncSessionController syncSessionController, IUpdateController updateController,
IUserNotifier userNotifier,
TrayWindowLoadingPage loadingPage,
TrayWindowDisconnectedPage disconnectedPage, TrayWindowLoginRequiredPage loginRequiredPage,
TrayWindowMainPage mainPage)
Expand All@@ -52,6 +60,7 @@ public TrayWindow(IRpcController rpcController, ICredentialManager credentialMan
_credentialManager = credentialManager;
_syncSessionController = syncSessionController;
_updateController = updateController;
_userNotifier = userNotifier;
_loadingPage = loadingPage;
_disconnectedPage = disconnectedPage;
_loginRequiredPage = loginRequiredPage;
Expand DownExpand Up@@ -142,9 +151,54 @@ private void SetPageByState(RpcModel rpcModel, CredentialModel credentialModel,
}
}

private void NotifyUser(RpcModel rpcModel)
{
// This method is called when the state changes, but we don't want to notify
// the user if the state hasn't changed.
var isRpcLifecycleChanged = rpcModel.RpcLifecycle == RpcLifecycle.Disconnected && prevRpcLifecycle != rpcModel.RpcLifecycle;
var isVpnLifecycleChanged = (rpcModel.VpnLifecycle == VpnLifecycle.Started || rpcModel.VpnLifecycle == VpnLifecycle.Stopped) && prevVpnLifecycle != rpcModel.VpnLifecycle;

if (!isRpcLifecycleChanged && !isVpnLifecycleChanged)
{
return;
}
var message = string.Empty;
// Compose the message based on the lifecycle changes
if (isRpcLifecycleChanged)
message += rpcModel.RpcLifecycle switch
{
RpcLifecycle.Disconnected => "Disconnected from Coder background service.",
_ => "" // This will never be hit.
};

if (message.Length > 0 && isVpnLifecycleChanged)
message += " ";

if (isVpnLifecycleChanged)
message += rpcModel.VpnLifecycle switch
{
VpnLifecycle.Started => "Coder Connect started.",
VpnLifecycle.Stopped => "Coder Connect stopped.",
_ => "" // This will never be hit.
};

// Save state for the next notification check
prevRpcLifecycle = rpcModel.RpcLifecycle;
prevVpnLifecycle = rpcModel.VpnLifecycle;

if (_aw.IsVisible)
{
return; // No need to notify if the window is not visible.
}

// Trigger notification
_userNotifier.ShowActionNotification(message, string.Empty, null, null, CancellationToken.None);
}

private void RpcController_StateChanged(object? _, RpcModel model)
{
SetPageByState(model, _credentialManager.GetCachedCredentials(), _syncSessionController.GetState());
NotifyUser(model);
}

private void CredentialManager_CredentialsChanged(object? _, CredentialModel model)
Expand DownExpand Up@@ -297,7 +351,7 @@ private void Window_Activated(object sender, WindowActivatedEventArgs e)
}

[RelayCommand]
private void Tray_Open()
public void Tray_Open()
{
MoveResizeAndActivate();
}
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp