11// HA DeskLink - Home Assistant Companion App
22// Copyright (C) 2026 Fabian Kirchweger
3- //
4- // This program is free software: you can redistribute it and/or modify
5- // it under the terms of the GNU General Public License v3 as published by
6- // the Free Software Foundation.
73using Avalonia ;
84using Avalonia . Controls ;
5+ using Avalonia . Input ;
96using Avalonia . Layout ;
107using Avalonia . Media ;
118using Avalonia . Threading ;
@@ -17,10 +14,11 @@ namespace HaDeskLink.Views;
1714
1815/// <summary>
1916/// Modern floating notification popup for HA push notifications.
17+ /// Auto-dismisses after 8s, supports action buttons.
2018/// </summary>
2119public class NotificationPopup : Window
2220{
23- private readonly DispatcherTimer ? _autoCloseTimer ;
21+ private DispatcherTimer ? _autoCloseTimer ;
2422 private bool _isClosing ;
2523
2624 private static readonly IBrush PanelBrush = new SolidColorBrush ( Color . FromArgb ( 255 , 22 , 33 , 62 ) ) ;
@@ -30,31 +28,29 @@ public class NotificationPopup : Window
3028
3129 public NotificationPopup ( string title , string message , List < NotificationActionInfo > ? actions = null )
3230 {
33- SystemBackground = Brushes . Transparent ;
34- TransparentClientArea = true ;
35- ExtendClientAreaToDecorationsHint = true ;
3631 CanResize = false ;
3732 ShowInTaskbar = false ;
3833 Topmost = true ;
3934 Width = 380 ;
4035 SizeToContent = SizeToContent . Height ;
4136 WindowStartupLocation = WindowStartupLocation . Manual ;
37+ Background = new SolidColorBrush ( Color . FromArgb ( 255 , 26 , 26 , 46 ) ) ;
4238
4339 actions ??= new List < NotificationActionInfo > ( ) ;
4440
4541 var accentBar = new Border { Background = HaBlueBrush , CornerRadius = new CornerRadius ( 12 , 0 , 0 , 12 ) } ;
4642 Grid . SetColumn ( accentBar , 0 ) ;
4743
48- var contentStack = new StackPanel { Margin = new Thickness ( 16 , 14 , 14 , 14 ) , Spacing = 8 , Children = BuildContentChildren ( title , message , actions ) } ;
44+ var contentStack = new StackPanel { Margin = new Thickness ( 16 , 14 , 14 , 14 ) , Spacing = 8 } ;
4945 Grid . SetColumn ( contentStack , 1 ) ;
46+ BuildContentChildren ( contentStack , title , message , actions ) ;
5047
5148 var card = new Border
5249 {
5350 Background = PanelBrush ,
5451 CornerRadius = new CornerRadius ( 12 ) ,
5552 ClipToBounds = true ,
5653 Margin = new Thickness ( 8 ) ,
57- BoxShadow = new BoxShadows ( new BoxShadow { Color = Color . FromArgb ( 80 , 0 , 0 , 0 ) , Blur = 16 , OffsetX = 0 , OffsetY = 4 } ) ,
5854 Child = new Grid
5955 {
6056 ColumnDefinitions = ColumnDefinitions . Parse ( "4,*" ) ,
@@ -65,28 +61,28 @@ public NotificationPopup(string title, string message, List<NotificationActionIn
6561 Content = card ;
6662
6763 _autoCloseTimer = new DispatcherTimer { Interval = TimeSpan . FromSeconds ( 8 ) } ;
68- _autoCloseTimer . Tick += ( s , e ) => { _autoCloseTimer . Stop ( ) ; CloseAnimated ( ) ; } ;
64+ _autoCloseTimer . Tick += ( s , e ) => { _autoCloseTimer ! . Stop ( ) ; CloseAnimated ( ) ; } ;
6965 _autoCloseTimer . Start ( ) ;
7066
71- PointerEnter += ( s , e ) => _autoCloseTimer ? . Stop ( ) ;
72- PointerLeave += ( s , e ) => { if ( ! _isClosing ) _autoCloseTimer ? . Start ( ) ; } ;
67+ PointerEntered += ( s , e ) => _autoCloseTimer ? . Stop ( ) ;
68+ PointerExited += ( s , e ) => { if ( ! _isClosing ) _autoCloseTimer ? . Start ( ) ; } ;
7369 }
7470
75- private List < Control > BuildContentChildren ( string title , string message , List < NotificationActionInfo > actions )
71+ private void BuildContentChildren ( StackPanel stack , string title , string message , List < NotificationActionInfo > actions )
7672 {
77- var children = new List < Control > ( ) ;
78-
7973 var titleText = new TextBlock { Text = title , FontSize = 15 , FontWeight = FontWeight . Bold , Foreground = Brushes . White , VerticalAlignment = VerticalAlignment . Center , TextWrapping = TextWrapping . Wrap } ;
8074 Grid . SetColumn ( titleText , 0 ) ;
8175 var closeBtn = new Button { Content = "✕" , FontSize = 14 , Background = Brushes . Transparent , Foreground = GrayBrush , Padding = new Thickness ( 4 , 2 ) , CornerRadius = new CornerRadius ( 4 ) , VerticalAlignment = VerticalAlignment . Top , HorizontalAlignment = HorizontalAlignment . Right } ;
8276 Grid . SetColumn ( closeBtn , 1 ) ;
8377 closeBtn . Click += ( s , e ) => CloseAnimated ( ) ;
8478
85- var headerGrid = new Grid { ColumnDefinitions = ColumnDefinitions . Parse ( "*,Auto" ) , Children = { titleText , closeBtn } } ;
86- children . Add ( headerGrid ) ;
79+ var headerGrid = new Grid { ColumnDefinitions = ColumnDefinitions . Parse ( "*,Auto" ) } ;
80+ headerGrid . Children . Add ( titleText ) ;
81+ headerGrid . Children . Add ( closeBtn ) ;
82+ stack . Children . Add ( headerGrid ) ;
8783
88- children . Add ( new TextBlock { Text = message , FontSize = 13 , Foreground = new SolidColorBrush ( Color . FromArgb ( 255 , 200 , 200 , 215 ) ) , TextWrapping = TextWrapping . Wrap , MaxLines = 5 } ) ;
89- children . Add ( new TextBlock { Text = DateTime . Now . ToString ( "HH:mm" ) , FontSize = 11 , Foreground = GrayBrush , HorizontalAlignment = HorizontalAlignment . Right } ) ;
84+ stack . Children . Add ( new TextBlock { Text = message , FontSize = 13 , Foreground = new SolidColorBrush ( Color . FromArgb ( 255 , 200 , 200 , 215 ) ) , TextWrapping = TextWrapping . Wrap , MaxLines = 5 } ) ;
85+ stack . Children . Add ( new TextBlock { Text = DateTime . Now . ToString ( "HH:mm" ) , FontSize = 11 , Foreground = GrayBrush , HorizontalAlignment = HorizontalAlignment . Right } ) ;
9086
9187 if ( actions . Count > 0 )
9288 {
@@ -97,9 +93,8 @@ private List<Control> BuildContentChildren(string title, string message, List<No
9793 btn . Click += ( s , e ) => { action . OnAction ? . Invoke ( ) ; CloseAnimated ( ) ; } ;
9894 btnPanel . Children . Add ( btn ) ;
9995 }
100- children . Add ( btnPanel ) ;
96+ stack . Children . Add ( btnPanel ) ;
10197 }
102- return children ;
10398 }
10499
105100 public void PositionTopRight ( double offsetX = 20 , double offsetY = 20 )
0 commit comments