Popup auffaellig, bleibt waehrend Klingeln offen, Tray-Icon blinkt bis Anrufliste geoeffnet
This commit is contained in:
parent
4376619459
commit
873c437d98
6 changed files with 115 additions and 40 deletions
|
|
@ -79,13 +79,20 @@ public partial class App : System.Windows.Application
|
|||
var logger = _host.Services.GetRequiredService<ILogger<App>>();
|
||||
var cache = _host.Services.GetRequiredService<ContactCache>();
|
||||
|
||||
CallPopupWindow.Corner = _host.Services.GetRequiredService<IConfiguration>()["Popup:Corner"] ?? "BottomRight";
|
||||
var endedCallIds = new HashSet<string>();
|
||||
|
||||
_trayIconManager = new TrayIconManager(
|
||||
onSyncNow: async () =>
|
||||
{
|
||||
var syncService = _host.Services.GetRequiredService<ContactSyncService>();
|
||||
await syncService.SyncOnceAsync(CancellationToken.None);
|
||||
},
|
||||
onShowHistory: () => CallHistoryWindow.ShowAsync(cache),
|
||||
onShowHistory: () =>
|
||||
{
|
||||
_trayIconManager?.StopAttention();
|
||||
return CallHistoryWindow.ShowAsync(cache);
|
||||
},
|
||||
onExit: () => System.Windows.Application.Current.Shutdown());
|
||||
_trayIconManager.Show();
|
||||
|
||||
|
|
@ -112,6 +119,12 @@ public partial class App : System.Windows.Application
|
|||
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
// Anruf war schon beendet, bevor der Kontakt aufgeloest war: kein Popup mehr noetig
|
||||
if (endedCallIds.Remove(args.CallId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var popup = new CallPopupWindow(contact, args.CallId, args.CalledNumber);
|
||||
popup.Show();
|
||||
});
|
||||
|
|
@ -127,6 +140,10 @@ public partial class App : System.Windows.Application
|
|||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
CallPopupWindow.CloseByCallId(args.CallId);
|
||||
endedCallIds.Add(args.CallId);
|
||||
|
||||
// Klingeln beendet: Tray-Icon blinkt, bis die Anrufliste geoeffnet wird.
|
||||
_trayIconManager?.StartAttention();
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,40 +1,46 @@
|
|||
<Window x:Class="Anrufmonitor.App.CallPopupWindow"
|
||||
<Window x:Class="Anrufmonitor.App.CallPopupWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Anrufmonitor"
|
||||
Width="360" Height="160"
|
||||
Width="380" Height="190"
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
ShowInTaskbar="False"
|
||||
Topmost="True"
|
||||
ShowActivated="False"
|
||||
ResizeMode="NoResize"
|
||||
SizeToContent="Manual">
|
||||
<Border Background="#FF1F2430" CornerRadius="10" BorderBrush="#FF3A4152" BorderThickness="1">
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="10">
|
||||
<Border Background="White" CornerRadius="10" BorderBrush="#FFFF8C00" BorderThickness="4">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="14" ShadowDepth="0" Opacity="0.55" Color="Black" />
|
||||
</Border.Effect>
|
||||
<Grid Margin="16,12,16,12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="Eingehender Anruf" Foreground="#FF8CA0C4" FontSize="12" FontWeight="SemiBold" />
|
||||
<TextBlock Grid.Row="0" Text="📞 Eingehender Anruf" Foreground="#FFE67300" FontSize="13" FontWeight="Bold" />
|
||||
|
||||
<TextBlock Grid.Row="1" x:Name="NameText" Text="Unbekannt" Foreground="White" FontSize="22" FontWeight="Bold"
|
||||
Margin="0,4,0,0" TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Grid.Row="1" x:Name="NameText" Text="Unbekannt" Foreground="#FF111111" FontSize="24" FontWeight="Bold"
|
||||
Margin="0,4,0,0" TextTrimming="CharacterEllipsis" />
|
||||
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" Margin="0,4,0,0">
|
||||
<TextBlock x:Name="CompanyText" Foreground="#FFB6C0D6" FontSize="14" Margin="0,0,8,0" />
|
||||
<TextBlock x:Name="NumberText" Foreground="#FF7C879C" FontSize="14" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" Margin="0,4,0,0">
|
||||
<TextBlock x:Name="CompanyText" Foreground="#FF333333" FontSize="14" Margin="0,0,8,0" />
|
||||
<TextBlock x:Name="NumberText" Foreground="#FF555555" FontSize="14" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="3" x:Name="CalledText" Foreground="#FF8CA0C4" FontSize="12" Margin="0,6,0,0" />
|
||||
<TextBlock Grid.Row="3" x:Name="CalledText" Foreground="#FFE67300" FontSize="13" FontWeight="SemiBold" Margin="0,6,0,0" />
|
||||
|
||||
<Button Content="✕" HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
Width="24" Height="24" Padding="0" Margin="0,-16,-16,0"
|
||||
Background="Transparent" BorderThickness="0" Foreground="#FF7C879C"
|
||||
Click="OnCloseClicked" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<Button Content="✕" HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
Width="24" Height="24" Padding="0" Margin="0,-6,-6,0"
|
||||
Background="Transparent" BorderThickness="0" Foreground="#FF555555"
|
||||
Click="OnCloseClicked" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
|
|
|||
|
|
@ -5,16 +5,19 @@ using Anrufmonitor.Contacts;
|
|||
namespace Anrufmonitor.App;
|
||||
|
||||
/// <summary>
|
||||
/// Toast-artiges Popup unten rechts, zeigt den (per <see cref="ContactLookupService"/>
|
||||
/// aufgeloesten) Anrufer an. Schliesst sich automatisch nach <see cref="AutoCloseAfter"/>
|
||||
/// oder wenn der Anruf beendet wird (<see cref="CloseByCallId"/>, ausgeloest durch
|
||||
/// SIP CANCEL/Ring-Timeout).
|
||||
/// Popup, zeigt den (per <see cref="ContactLookupService"/> aufgeloesten) Anrufer an.
|
||||
/// Bleibt offen, solange es klingelt: geschlossen wird per <see cref="CloseByCallId"/>
|
||||
/// (SIP CANCEL/Ring-Timeout) oder per X. <see cref="AutoCloseAfter"/> ist nur eine
|
||||
/// Sicherung, falls das Ende des Anrufs nie signalisiert wird.
|
||||
/// </summary>
|
||||
public partial class CallPopupWindow : Window
|
||||
{
|
||||
private static readonly TimeSpan AutoCloseAfter = TimeSpan.FromSeconds(20);
|
||||
private static readonly TimeSpan AutoCloseAfter = TimeSpan.FromMinutes(2);
|
||||
private static readonly Dictionary<string, CallPopupWindow> OpenPopups = new();
|
||||
|
||||
/// <summary>Bildschirmecke fuer das Popup (Konfiguration "Popup:Corner"): BottomRight, BottomLeft, TopRight, TopLeft.</summary>
|
||||
public static string Corner { get; set; } = "BottomRight";
|
||||
|
||||
private readonly string _callId;
|
||||
private readonly DispatcherTimer _autoCloseTimer;
|
||||
|
||||
|
|
@ -33,7 +36,7 @@ public partial class CallPopupWindow : Window
|
|||
CompanyText.Visibility = string.IsNullOrWhiteSpace(contact.Company) ? Visibility.Collapsed : Visibility.Visible;
|
||||
NumberText.Text = contact.PhoneNumbers.Count > 0 ? contact.PhoneNumbers[0] : string.Empty;
|
||||
|
||||
PositionBottomRight();
|
||||
PositionOnScreen();
|
||||
|
||||
_autoCloseTimer = new DispatcherTimer { Interval = AutoCloseAfter };
|
||||
_autoCloseTimer.Tick += (_, _) => Close();
|
||||
|
|
@ -57,11 +60,18 @@ public partial class CallPopupWindow : Window
|
|||
}
|
||||
}
|
||||
|
||||
private void PositionBottomRight()
|
||||
private void PositionOnScreen()
|
||||
{
|
||||
var workArea = SystemParameters.WorkArea;
|
||||
Left = workArea.Right - Width - 16;
|
||||
Top = workArea.Bottom - Height - 16;
|
||||
// Mehrere gleichzeitige Popups werden gestapelt statt uebereinander gelegt.
|
||||
var stackIndex = Math.Max(0, OpenPopups.Count - 1);
|
||||
var offset = stackIndex * (Height - 20);
|
||||
|
||||
var area = SystemParameters.WorkArea;
|
||||
var right = Corner.EndsWith("Right", StringComparison.OrdinalIgnoreCase);
|
||||
var bottom = Corner.StartsWith("Bottom", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
Left = right ? area.Right - Width : area.Left;
|
||||
Top = bottom ? area.Bottom - Height - offset : area.Top + offset;
|
||||
}
|
||||
|
||||
private void OnCloseClicked(object sender, RoutedEventArgs e) => Close();
|
||||
|
|
|
|||
|
|
@ -14,11 +14,13 @@ public static class TrayIconFactory
|
|||
public static readonly Color ConnectedColor = Color.FromArgb(255, 76, 175, 80);
|
||||
public static readonly Color DisconnectedColor = Color.FromArgb(255, 229, 57, 53);
|
||||
public static readonly Color UnknownColor = Color.FromArgb(255, 158, 158, 158);
|
||||
public static readonly Color AttentionColor = Color.FromArgb(255, 255, 140, 0);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern bool DestroyIcon(IntPtr handle);
|
||||
|
||||
public static Icon CreateStatusIcon(Color dotColor)
|
||||
/// <param name="alert">true = kompletter Kreis in Warnfarbe (zum Blinken bei neuem Anruf).</param>
|
||||
public static Icon CreateStatusIcon(Color dotColor, bool alert = false)
|
||||
{
|
||||
using var bitmap = new Bitmap(32, 32);
|
||||
using (var g = Graphics.FromImage(bitmap))
|
||||
|
|
@ -26,7 +28,7 @@ public static class TrayIconFactory
|
|||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
g.Clear(Color.Transparent);
|
||||
|
||||
using var baseBrush = new SolidBrush(Color.FromArgb(255, 45, 55, 72));
|
||||
using var baseBrush = new SolidBrush(alert ? AttentionColor : Color.FromArgb(255, 45, 55, 72));
|
||||
g.FillEllipse(baseBrush, 1, 1, 28, 28);
|
||||
|
||||
using var phonePen = new Pen(Color.White, 2.5f) { StartCap = LineCap.Round, EndCap = LineCap.Round };
|
||||
|
|
|
|||
|
|
@ -11,7 +11,11 @@ namespace Anrufmonitor.App;
|
|||
public sealed class TrayIconManager : IDisposable
|
||||
{
|
||||
private readonly NotifyIcon _notifyIcon;
|
||||
private readonly System.Windows.Forms.Timer _flashTimer = new() { Interval = 600 };
|
||||
private Icon? _currentIcon;
|
||||
private Color _statusColor = TrayIconFactory.UnknownColor;
|
||||
private bool _flashing;
|
||||
private bool _flashOn;
|
||||
|
||||
public TrayIconManager(Func<Task> onSyncNow, Func<Task> onShowHistory, System.Action onExit)
|
||||
{
|
||||
|
|
@ -41,19 +45,50 @@ public sealed class TrayIconManager : IDisposable
|
|||
Visible = false,
|
||||
};
|
||||
_notifyIcon.DoubleClick += async (_, _) => await onShowHistory();
|
||||
_flashTimer.Tick += (_, _) =>
|
||||
{
|
||||
_flashOn = !_flashOn;
|
||||
SetIcon(_statusColor, alert: _flashOn);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Laesst das Tray-Icon blinken (neuer Anruf), bis <see cref="StopAttention"/> aufgerufen wird.</summary>
|
||||
public void StartAttention()
|
||||
{
|
||||
if (_flashing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_flashing = true;
|
||||
_flashTimer.Start();
|
||||
}
|
||||
|
||||
public void StopAttention()
|
||||
{
|
||||
if (!_flashing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_flashing = false;
|
||||
_flashOn = false;
|
||||
_flashTimer.Stop();
|
||||
SetIcon(_statusColor, alert: false);
|
||||
}
|
||||
|
||||
public void Show() => _notifyIcon.Visible = true;
|
||||
|
||||
public void SetSipStatus(bool registered)
|
||||
{
|
||||
SetIcon(registered ? TrayIconFactory.ConnectedColor : TrayIconFactory.DisconnectedColor);
|
||||
_statusColor = registered ? TrayIconFactory.ConnectedColor : TrayIconFactory.DisconnectedColor;
|
||||
SetIcon(_statusColor, alert: _flashing && _flashOn);
|
||||
_notifyIcon.Text = registered ? "Anrufmonitor - verbunden" : "Anrufmonitor - SIP getrennt";
|
||||
}
|
||||
|
||||
private void SetIcon(Color dotColor)
|
||||
private void SetIcon(Color dotColor, bool alert = false)
|
||||
{
|
||||
var newIcon = TrayIconFactory.CreateStatusIcon(dotColor);
|
||||
var newIcon = TrayIconFactory.CreateStatusIcon(dotColor, alert);
|
||||
var oldIcon = _currentIcon;
|
||||
|
||||
_notifyIcon.Icon = newIcon;
|
||||
|
|
@ -72,6 +107,8 @@ public sealed class TrayIconManager : IDisposable
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
_flashTimer.Stop();
|
||||
_flashTimer.Dispose();
|
||||
_notifyIcon.Visible = false;
|
||||
_notifyIcon.Dispose();
|
||||
_currentIcon?.Dispose();
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@
|
|||
"_comment": "Reverse-Lookup gegen dasoertliche.de - keine offizielle API, siehe PublicDirectoryLookupProvider.cs"
|
||||
}
|
||||
},
|
||||
"Popup": {
|
||||
"Corner": "BottomRight"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue