Skip to content

Commit 2108a43

Browse files
authored
Merge branch 'main' into claude/sprint-1-gui-planning-016jDUBD7C19JEjuLp3PPipw
2 parents 723fdbf + ca9e67b commit 2108a43

22 files changed

Lines changed: 2390 additions & 302 deletions

App.xaml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,21 @@
22
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
33
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
44
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
5-
xmlns:local="clr-namespace:PrettyScreenSHOT">
5+
xmlns:local="clr-namespace:PrettyScreenSHOT"
6+
xmlns:converters="clr-namespace:PrettyScreenSHOT.Converters">
67
<Application.Resources>
78
<ResourceDictionary>
89
<ResourceDictionary.MergedDictionaries>
910
<!-- WPF UI Theme Resources -->
1011
<ui:ThemesDictionary Theme="Dark" />
1112
<ui:ControlsDictionary />
1213

13-
<!-- Custom WPF UI Styles -->
14-
<ResourceDictionary Source="pack://application:,,,/Themes/WpfUiCustom.xaml"/>
14+
<!-- Application Base Theme (includes Colors, Typography, Spacing, and Custom Styles) -->
15+
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml"/>
1516
</ResourceDictionary.MergedDictionaries>
17+
18+
<!-- Global Converters -->
19+
<converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter"/>
1620
</ResourceDictionary>
1721
</Application.Resources>
1822
</Application>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Globalization;
3+
using System.Windows;
4+
using System.Windows.Data;
5+
6+
namespace PrettyScreenSHOT.Converters
7+
{
8+
/// <summary>
9+
/// Converts null or empty values to Visibility
10+
/// </summary>
11+
public class NullToVisibilityConverter : IValueConverter
12+
{
13+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
14+
{
15+
bool isInverse = parameter?.ToString() == "Inverse";
16+
bool isNull = value == null || (value is string str && string.IsNullOrEmpty(str));
17+
18+
if (isInverse)
19+
return isNull ? Visibility.Visible : Visibility.Collapsed;
20+
else
21+
return !isNull ? Visibility.Visible : Visibility.Collapsed;
22+
}
23+
24+
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
25+
{
26+
throw new NotImplementedException();
27+
}
28+
}
29+
}

Helpers/DebugHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public static class DebugHelper
1313
[System.Diagnostics.Conditional("DEBUG")]
1414
public static void ShowMessage(string title, string message)
1515
{
16-
System.Windows.MessageBox.Show(message, title);
16+
MessageBoxHelper.Show(message, title);
1717
}
1818

1919
/// <summary>

Helpers/MessageBoxHelper.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System.Windows;
2+
using Wpf.Ui.Controls;
3+
4+
namespace PrettyScreenSHOT.Helpers
5+
{
6+
/// <summary>
7+
/// Helper for showing Wpf.Ui MessageBoxes with a simplified API
8+
/// </summary>
9+
public static class MessageBoxHelper
10+
{
11+
public static MessageBoxResult Show(
12+
string message,
13+
string title,
14+
MessageBoxButton button = MessageBoxButton.OK)
15+
{
16+
return Application.Current.Dispatcher.Invoke(() =>
17+
{
18+
var messageBox = new Wpf.Ui.Controls.MessageBox
19+
{
20+
Title = title,
21+
Content = message,
22+
ButtonLeftName = GetButtonName(button, true),
23+
ButtonRightName = GetButtonName(button, false)
24+
};
25+
26+
var result = messageBox.ShowDialogAsync().GetAwaiter().GetResult();
27+
return MapToMessageBoxResult(result, button);
28+
});
29+
}
30+
31+
private static string GetButtonName(MessageBoxButton button, bool isLeft)
32+
{
33+
return button switch
34+
{
35+
MessageBoxButton.OK => isLeft ? "OK" : string.Empty,
36+
MessageBoxButton.OKCancel => isLeft ? "OK" : "Cancel",
37+
MessageBoxButton.YesNo => isLeft ? "Yes" : "No",
38+
MessageBoxButton.YesNoCancel => isLeft ? "Yes" : (isLeft ? "No" : "Cancel"),
39+
_ => isLeft ? "OK" : string.Empty
40+
};
41+
}
42+
43+
private static MessageBoxResult MapToMessageBoxResult(
44+
Wpf.Ui.Controls.MessageBoxResult wpfUiResult,
45+
MessageBoxButton buttonType)
46+
{
47+
return buttonType switch
48+
{
49+
MessageBoxButton.OK => MessageBoxResult.OK,
50+
MessageBoxButton.OKCancel => wpfUiResult == Wpf.Ui.Controls.MessageBoxResult.Primary
51+
? MessageBoxResult.OK
52+
: MessageBoxResult.Cancel,
53+
MessageBoxButton.YesNo => wpfUiResult == Wpf.Ui.Controls.MessageBoxResult.Primary
54+
? MessageBoxResult.Yes
55+
: MessageBoxResult.No,
56+
MessageBoxButton.YesNoCancel => wpfUiResult switch
57+
{
58+
Wpf.Ui.Controls.MessageBoxResult.Primary => MessageBoxResult.Yes,
59+
Wpf.Ui.Controls.MessageBoxResult.Secondary => MessageBoxResult.No,
60+
_ => MessageBoxResult.Cancel
61+
},
62+
_ => MessageBoxResult.None
63+
};
64+
}
65+
}
66+
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
using System;
2+
using System.Windows;
3+
using System.Windows.Controls;
4+
using System.Windows.Media;
5+
using System.Windows.Media.Animation;
6+
using Wpf.Ui.Controls;
7+
using PrettyScreenSHOT.Helpers;
8+
9+
namespace PrettyScreenSHOT.Services
10+
{
11+
public enum ToastType
12+
{
13+
Success,
14+
Error,
15+
Warning,
16+
Info
17+
}
18+
19+
public class ToastNotificationService
20+
{
21+
private static ToastNotificationService? instance;
22+
public static ToastNotificationService Instance => instance ??= new ToastNotificationService();
23+
24+
private ToastNotificationService() { }
25+
26+
/// <summary>
27+
/// Shows a toast notification
28+
/// </summary>
29+
public void Show(string title, string message, ToastType type = ToastType.Info, int durationMs = 3000)
30+
{
31+
try
32+
{
33+
// If MainWindow is open, show in-app toast
34+
if (TryShowInAppToast(title, message, type, durationMs))
35+
{
36+
return;
37+
}
38+
39+
// Otherwise, show system tray notification
40+
ShowTrayNotification(title, message, type);
41+
}
42+
catch (Exception ex)
43+
{
44+
DebugHelper.LogError("ToastNotificationService", "Error showing toast", ex);
45+
}
46+
}
47+
48+
private bool TryShowInAppToast(string title, string message, ToastType type, int durationMs)
49+
{
50+
try
51+
{
52+
// Find MainWindow if it's open
53+
foreach (Window window in Application.Current.Windows)
54+
{
55+
if (window is Views.Windows.MainWindow mainWindow)
56+
{
57+
Application.Current.Dispatcher.Invoke(() =>
58+
{
59+
ShowToastInWindow(mainWindow, title, message, type, durationMs);
60+
});
61+
return true;
62+
}
63+
}
64+
}
65+
catch (Exception ex)
66+
{
67+
DebugHelper.LogError("ToastNotificationService", "Error showing in-app toast", ex);
68+
}
69+
70+
return false;
71+
}
72+
73+
private void ShowToastInWindow(Views.Windows.MainWindow mainWindow, string title, string message, ToastType type, int durationMs)
74+
{
75+
// Create toast container if it doesn't exist
76+
var grid = mainWindow.Content as Grid;
77+
if (grid == null) return;
78+
79+
// Create toast card
80+
var toastCard = new Card
81+
{
82+
Padding = new Thickness(16),
83+
Margin = new Thickness(20, 20, 20, 0),
84+
HorizontalAlignment = HorizontalAlignment.Right,
85+
VerticalAlignment = VerticalAlignment.Top,
86+
MaxWidth = 400
87+
};
88+
89+
// Set background color based on type
90+
var (icon, foreground) = GetToastStyle(type);
91+
92+
var stackPanel = new StackPanel
93+
{
94+
Orientation = System.Windows.Controls.Orientation.Horizontal,
95+
Children =
96+
{
97+
new SymbolIcon
98+
{
99+
Symbol = icon,
100+
FontSize = 20,
101+
Margin = new Thickness(0, 0, 12, 0),
102+
Foreground = new SolidColorBrush(foreground)
103+
},
104+
new StackPanel
105+
{
106+
Children =
107+
{
108+
new System.Windows.Controls.TextBlock
109+
{
110+
Text = title,
111+
FontWeight = FontWeights.SemiBold,
112+
FontSize = 14
113+
},
114+
new System.Windows.Controls.TextBlock
115+
{
116+
Text = message,
117+
FontSize = 12,
118+
Opacity = 0.8,
119+
TextWrapping = TextWrapping.Wrap
120+
}
121+
}
122+
}
123+
}
124+
};
125+
126+
toastCard.Content = stackPanel;
127+
128+
// Add to grid
129+
Grid.SetRowSpan(toastCard, grid.RowDefinitions.Count);
130+
Grid.SetColumnSpan(toastCard, grid.ColumnDefinitions.Count);
131+
grid.Children.Add(toastCard);
132+
133+
// Animate in
134+
toastCard.Opacity = 0;
135+
toastCard.RenderTransform = new TranslateTransform(0, -20);
136+
137+
var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(200));
138+
var slideIn = new DoubleAnimation(-20, 0, TimeSpan.FromMilliseconds(200));
139+
140+
toastCard.BeginAnimation(UIElement.OpacityProperty, fadeIn);
141+
((TranslateTransform)toastCard.RenderTransform).BeginAnimation(TranslateTransform.YProperty, slideIn);
142+
143+
// Auto-hide after duration
144+
var timer = new System.Windows.Threading.DispatcherTimer
145+
{
146+
Interval = TimeSpan.FromMilliseconds(durationMs)
147+
};
148+
149+
timer.Tick += (s, e) =>
150+
{
151+
timer.Stop();
152+
153+
// Animate out
154+
var fadeOut = new DoubleAnimation(1, 0, TimeSpan.FromMilliseconds(200));
155+
var slideOut = new DoubleAnimation(0, -20, TimeSpan.FromMilliseconds(200));
156+
157+
fadeOut.Completed += (s2, e2) =>
158+
{
159+
grid.Children.Remove(toastCard);
160+
};
161+
162+
toastCard.BeginAnimation(UIElement.OpacityProperty, fadeOut);
163+
((TranslateTransform)toastCard.RenderTransform).BeginAnimation(TranslateTransform.YProperty, slideOut);
164+
};
165+
166+
timer.Start();
167+
}
168+
169+
private void ShowTrayNotification(string title, string message, ToastType type)
170+
{
171+
var trayIcon = TrayIconManager.Instance;
172+
if (trayIcon != null)
173+
{
174+
var iconType = type switch
175+
{
176+
ToastType.Success => System.Windows.Forms.ToolTipIcon.Info,
177+
ToastType.Error => System.Windows.Forms.ToolTipIcon.Error,
178+
ToastType.Warning => System.Windows.Forms.ToolTipIcon.Warning,
179+
_ => System.Windows.Forms.ToolTipIcon.Info
180+
};
181+
182+
trayIcon.ShowNotification(title, message);
183+
DebugHelper.LogInfo("ToastNotificationService", $"Tray notification: {title} - {message}");
184+
}
185+
}
186+
187+
private (Wpf.Ui.Common.SymbolRegular icon, Color foreground) GetToastStyle(ToastType type)
188+
{
189+
return type switch
190+
{
191+
ToastType.Success => (Wpf.Ui.Common.SymbolRegular.CheckmarkCircle24, Color.FromRgb(16, 185, 129)),
192+
ToastType.Error => (Wpf.Ui.Common.SymbolRegular.DismissCircle24, Color.FromRgb(239, 68, 68)),
193+
ToastType.Warning => (Wpf.Ui.Common.SymbolRegular.Warning24, Color.FromRgb(245, 158, 11)),
194+
_ => (Wpf.Ui.Common.SymbolRegular.Info24, Color.FromRgb(59, 130, 246))
195+
};
196+
}
197+
198+
// Convenience methods
199+
public void ShowSuccess(string message) => Show("Sukces", message, ToastType.Success);
200+
public void ShowError(string message) => Show("Błąd", message, ToastType.Error);
201+
public void ShowWarning(string message) => Show("Ostrzeżenie", message, ToastType.Warning);
202+
public void ShowInfo(string message) => Show("Informacja", message, ToastType.Info);
203+
}
204+
}

Services/TrayIconManager.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ public void Initialize()
3030
private void CreateTrayIcon()
3131
{
3232
contextMenu = new ContextMenuStrip();
33-
33+
34+
var dashboardItem = new ToolStripMenuItem("Dashboard", null, ShowDashboard);
3435
var editItem = new ToolStripMenuItem(LocalizationHelper.GetString("Menu_EditLastScreenshot"), null, EditLastScreenshot);
3536
var historyItem = new ToolStripMenuItem(LocalizationHelper.GetString("Menu_History"), null, ShowHistory);
3637
var uploadHistoryItem = new ToolStripMenuItem("Historia uploadów", null, ShowUploadHistory);
@@ -40,6 +41,7 @@ private void CreateTrayIcon()
4041
var settingsItem = new ToolStripMenuItem(LocalizationHelper.GetString("Menu_Settings"), null, ShowSettings);
4142
var exitItem = new ToolStripMenuItem(LocalizationHelper.GetString("Menu_Exit"), null, ExitApplication);
4243

44+
contextMenu.Items.Add(new ToolStripSeparator());
4345
contextMenu.Items.Add(editItem);
4446
contextMenu.Items.Add(historyItem);
4547
contextMenu.Items.Add(uploadHistoryItem);
@@ -64,7 +66,7 @@ private void CreateTrayIcon()
6466
ContextMenuStrip = contextMenu
6567
};
6668

67-
notifyIcon.MouseDoubleClick += (s, e) => ShowHistory(null, null);
69+
notifyIcon.MouseDoubleClick += (s, e) => ShowDashboard(null, null);
6870
notifyIcon.BalloonTipClicked += (s, e) => EditLastScreenshot(null, null);
6971
}
7072

@@ -153,6 +155,24 @@ private void EditLastScreenshot(object? sender, EventArgs? e)
153155
}
154156
}
155157

158+
private void ShowDashboard(object? sender, EventArgs? e)
159+
{
160+
// Check if MainWindow is already open
161+
foreach (Window window in System.Windows.Application.Current.Windows)
162+
{
163+
if (window is MainWindow mainWindow)
164+
{
165+
mainWindow.Activate();
166+
mainWindow.WindowState = WindowState.Normal;
167+
return;
168+
}
169+
}
170+
171+
// Create new MainWindow
172+
var dashboard = new MainWindow();
173+
dashboard.Show();
174+
}
175+
156176
private void ShowHistory(object? sender, EventArgs? e)
157177
{
158178
var historyWindow = new ScreenshotHistoryWindow();

0 commit comments

Comments
 (0)