Skip to content

Commit d96c389

Browse files
authored
Merge pull request #36 from Co0ob1iee/claude/sprint-1-gui-planning-016jDUBD7C19JEjuLp3PPipw
feat: Implement Sprint 3 Part 2 - Toast Notifications and Upload History
2 parents ca9e67b + 2108a43 commit d96c389

8 files changed

Lines changed: 751 additions & 4 deletions

File tree

Services/Screenshot/ScreenshotManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ public ScreenshotItem AddScreenshotWithMetadata(BitmapSource bitmap, string cate
206206
{
207207
System.Windows.Application.Current.Dispatcher.Invoke(() =>
208208
{
209-
TrayIconManager.Instance.ShowNotification(
209+
ToastNotificationManager.Instance.ShowSuccess(
210210
"Upload Complete",
211211
$"Screenshot uploaded to {result.ProviderName}. URL copied to clipboard.");
212212
});
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Windows;
5+
using System.Windows.Controls;
6+
using PrettyScreenSHOT.Views.Controls;
7+
8+
namespace PrettyScreenSHOT.Services
9+
{
10+
public class ToastNotificationManager
11+
{
12+
private static ToastNotificationManager? _instance;
13+
public static ToastNotificationManager Instance => _instance ??= new ToastNotificationManager();
14+
15+
private Grid? toastContainer;
16+
private readonly List<ToastNotification> activeToasts = new();
17+
private const int MaxToasts = 5;
18+
private const int ToastSpacing = 8;
19+
20+
private ToastNotificationManager()
21+
{
22+
}
23+
24+
public void Initialize(Grid container)
25+
{
26+
toastContainer = container;
27+
}
28+
29+
public void ShowSuccess(string title, string message, int durationMs = 4000)
30+
{
31+
ShowToast(title, message, ToastNotification.ToastType.Success, durationMs);
32+
}
33+
34+
public void ShowInfo(string title, string message, int durationMs = 4000)
35+
{
36+
ShowToast(title, message, ToastNotification.ToastType.Info, durationMs);
37+
}
38+
39+
public void ShowWarning(string title, string message, int durationMs = 4000)
40+
{
41+
ShowToast(title, message, ToastNotification.ToastType.Warning, durationMs);
42+
}
43+
44+
public void ShowError(string title, string message, int durationMs = 5000)
45+
{
46+
ShowToast(title, message, ToastNotification.ToastType.Error, durationMs);
47+
}
48+
49+
private void ShowToast(string title, string message, ToastNotification.ToastType type, int durationMs)
50+
{
51+
if (toastContainer == null)
52+
{
53+
// Fallback to window-based toast if container not initialized
54+
ShowToastInWindow(title, message, type, durationMs);
55+
return;
56+
}
57+
58+
Application.Current.Dispatcher.Invoke(() =>
59+
{
60+
// Limit number of toasts
61+
if (activeToasts.Count >= MaxToasts)
62+
{
63+
// Remove oldest toast
64+
var oldest = activeToasts.First();
65+
oldest.Hide();
66+
}
67+
68+
var toast = new ToastNotification();
69+
toast.Closed += (s, e) =>
70+
{
71+
activeToasts.Remove(toast);
72+
toastContainer.Children.Remove(toast);
73+
RepositionToasts();
74+
};
75+
76+
// Position toast
77+
toast.HorizontalAlignment = HorizontalAlignment.Right;
78+
toast.VerticalAlignment = VerticalAlignment.Top;
79+
toast.Margin = new Thickness(0, CalculateTopMargin(), 0, 0);
80+
81+
toastContainer.Children.Add(toast);
82+
activeToasts.Add(toast);
83+
84+
toast.Show(title, message, type, durationMs);
85+
});
86+
}
87+
88+
private double CalculateTopMargin()
89+
{
90+
double margin = 20; // Initial top margin
91+
foreach (var toast in activeToasts)
92+
{
93+
margin += toast.ActualHeight + ToastSpacing;
94+
}
95+
return margin;
96+
}
97+
98+
private void RepositionToasts()
99+
{
100+
double margin = 20;
101+
foreach (var toast in activeToasts)
102+
{
103+
toast.Margin = new Thickness(0, margin, 0, 0);
104+
margin += toast.ActualHeight + ToastSpacing;
105+
}
106+
}
107+
108+
private void ShowToastInWindow(string title, string message, ToastNotification.ToastType type, int durationMs)
109+
{
110+
// Create a standalone window for the toast
111+
var toastWindow = new Window
112+
{
113+
WindowStyle = WindowStyle.None,
114+
AllowsTransparency = true,
115+
Background = System.Windows.Media.Brushes.Transparent,
116+
Topmost = true,
117+
ShowInTaskbar = false,
118+
SizeToContent = SizeToContent.WidthAndHeight,
119+
WindowStartupLocation = WindowStartupLocation.Manual
120+
};
121+
122+
// Position at bottom-right of screen
123+
var workingArea = SystemParameters.WorkArea;
124+
toastWindow.Left = workingArea.Right - 420; // 400px toast + 20px margin
125+
toastWindow.Top = workingArea.Bottom - 200; // Adjust based on toast height
126+
127+
var toast = new ToastNotification();
128+
toast.Closed += (s, e) => toastWindow.Close();
129+
130+
toastWindow.Content = toast;
131+
toastWindow.Show();
132+
133+
toast.Show(title, message, type, durationMs);
134+
}
135+
}
136+
}

Services/TrayIconManager.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,17 @@ private void CreateTrayIcon()
3434
var dashboardItem = new ToolStripMenuItem("Dashboard", null, ShowDashboard);
3535
var editItem = new ToolStripMenuItem(LocalizationHelper.GetString("Menu_EditLastScreenshot"), null, EditLastScreenshot);
3636
var historyItem = new ToolStripMenuItem(LocalizationHelper.GetString("Menu_History"), null, ShowHistory);
37+
var uploadHistoryItem = new ToolStripMenuItem("Historia uploadów", null, ShowUploadHistory);
3738
var scrollCaptureItem = new ToolStripMenuItem("Scroll Capture", null, StartScrollCapture);
3839
var videoCaptureItem = new ToolStripMenuItem("Video Capture", null, StartVideoCapture);
3940
var checkUpdateItem = new ToolStripMenuItem("Check for Updates", null, CheckForUpdates);
4041
var settingsItem = new ToolStripMenuItem(LocalizationHelper.GetString("Menu_Settings"), null, ShowSettings);
4142
var exitItem = new ToolStripMenuItem(LocalizationHelper.GetString("Menu_Exit"), null, ExitApplication);
4243

43-
contextMenu.Items.Add(dashboardItem);
4444
contextMenu.Items.Add(new ToolStripSeparator());
4545
contextMenu.Items.Add(editItem);
4646
contextMenu.Items.Add(historyItem);
47+
contextMenu.Items.Add(uploadHistoryItem);
4748
contextMenu.Items.Add(new ToolStripSeparator());
4849
contextMenu.Items.Add(scrollCaptureItem);
4950
contextMenu.Items.Add(videoCaptureItem);
@@ -178,6 +179,12 @@ private void ShowHistory(object? sender, EventArgs? e)
178179
historyWindow.Show();
179180
}
180181

182+
private void ShowUploadHistory(object? sender, EventArgs? e)
183+
{
184+
var uploadHistoryWindow = new UploadHistoryWindow();
185+
uploadHistoryWindow.Show();
186+
}
187+
181188
private void ShowSettings(object? sender, EventArgs? e)
182189
{
183190
var settingsWindow = new SettingsWindow();
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<UserControl x:Class="PrettyScreenSHOT.Views.Controls.ToastNotification"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
5+
MaxWidth="400">
6+
<Border x:Name="ToastBorder"
7+
Background="#F0F0F0"
8+
CornerRadius="8"
9+
Padding="16,12"
10+
Margin="12"
11+
BorderThickness="1"
12+
BorderBrush="#E0E0E0"
13+
RenderTransformOrigin="1,0">
14+
<Border.Effect>
15+
<DropShadowEffect BlurRadius="20"
16+
ShadowDepth="4"
17+
Opacity="0.3"
18+
Color="#000000"/>
19+
</Border.Effect>
20+
<Border.RenderTransform>
21+
<TranslateTransform x:Name="SlideTransform" X="0" Y="0"/>
22+
</Border.RenderTransform>
23+
24+
<Grid>
25+
<Grid.ColumnDefinitions>
26+
<ColumnDefinition Width="Auto"/>
27+
<ColumnDefinition Width="*"/>
28+
<ColumnDefinition Width="Auto"/>
29+
</Grid.ColumnDefinitions>
30+
31+
<!-- Icon -->
32+
<Border Grid.Column="0"
33+
x:Name="IconBorder"
34+
Width="40"
35+
Height="40"
36+
CornerRadius="20"
37+
Background="#0078D4"
38+
Margin="0,0,12,0"
39+
VerticalAlignment="Top">
40+
<ui:SymbolIcon x:Name="ToastIcon"
41+
Symbol="Checkmark24"
42+
Foreground="White"
43+
FontSize="20"/>
44+
</Border>
45+
46+
<!-- Content -->
47+
<StackPanel Grid.Column="1" VerticalAlignment="Center">
48+
<TextBlock x:Name="TitleText"
49+
Text="Notification Title"
50+
FontSize="14"
51+
FontWeight="SemiBold"
52+
Foreground="#202020"
53+
TextWrapping="Wrap"
54+
Margin="0,0,0,4"/>
55+
<TextBlock x:Name="MessageText"
56+
Text="Notification message goes here"
57+
FontSize="12"
58+
Foreground="#606060"
59+
TextWrapping="Wrap"/>
60+
</StackPanel>
61+
62+
<!-- Close Button -->
63+
<Button Grid.Column="2"
64+
x:Name="CloseButton"
65+
Click="OnCloseClick"
66+
Width="24"
67+
Height="24"
68+
Padding="0"
69+
Background="Transparent"
70+
BorderThickness="0"
71+
VerticalAlignment="Top"
72+
Cursor="Hand"
73+
Margin="8,0,0,0">
74+
<ui:SymbolIcon Symbol="Dismiss24" FontSize="14" Foreground="#808080"/>
75+
<Button.Style>
76+
<Style TargetType="Button">
77+
<Setter Property="Template">
78+
<Setter.Value>
79+
<ControlTemplate TargetType="Button">
80+
<Border Background="{TemplateBinding Background}"
81+
CornerRadius="12"
82+
Padding="{TemplateBinding Padding}">
83+
<ContentPresenter HorizontalAlignment="Center"
84+
VerticalAlignment="Center"/>
85+
</Border>
86+
</ControlTemplate>
87+
</Setter.Value>
88+
</Setter>
89+
<Style.Triggers>
90+
<Trigger Property="IsMouseOver" Value="True">
91+
<Setter Property="Background" Value="#20000000"/>
92+
</Trigger>
93+
</Style.Triggers>
94+
</Style>
95+
</Button.Style>
96+
</Button>
97+
</Grid>
98+
</Border>
99+
</UserControl>
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using System;
2+
using System.Windows;
3+
using System.Windows.Controls;
4+
using System.Windows.Media;
5+
using System.Windows.Media.Animation;
6+
7+
namespace PrettyScreenSHOT.Views.Controls
8+
{
9+
public partial class ToastNotification : UserControl
10+
{
11+
public event EventHandler? Closed;
12+
13+
public enum ToastType
14+
{
15+
Success,
16+
Info,
17+
Warning,
18+
Error
19+
}
20+
21+
public ToastNotification()
22+
{
23+
InitializeComponent();
24+
}
25+
26+
public void Show(string title, string message, ToastType type = ToastType.Success, int durationMs = 4000)
27+
{
28+
TitleText.Text = title;
29+
MessageText.Text = message;
30+
SetToastType(type);
31+
32+
// Slide in animation
33+
var slideIn = new DoubleAnimation
34+
{
35+
From = 400,
36+
To = 0,
37+
Duration = TimeSpan.FromMilliseconds(300),
38+
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
39+
};
40+
41+
var fadeIn = new DoubleAnimation
42+
{
43+
From = 0,
44+
To = 1,
45+
Duration = TimeSpan.FromMilliseconds(300)
46+
};
47+
48+
SlideTransform.BeginAnimation(TranslateTransform.XProperty, slideIn);
49+
this.BeginAnimation(OpacityProperty, fadeIn);
50+
51+
// Auto-hide timer
52+
var hideTimer = new System.Windows.Threading.DispatcherTimer
53+
{
54+
Interval = TimeSpan.FromMilliseconds(durationMs)
55+
};
56+
hideTimer.Tick += (s, e) =>
57+
{
58+
hideTimer.Stop();
59+
Hide();
60+
};
61+
hideTimer.Start();
62+
}
63+
64+
public void Hide()
65+
{
66+
var slideOut = new DoubleAnimation
67+
{
68+
From = 0,
69+
To = 400,
70+
Duration = TimeSpan.FromMilliseconds(250),
71+
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn }
72+
};
73+
74+
var fadeOut = new DoubleAnimation
75+
{
76+
From = 1,
77+
To = 0,
78+
Duration = TimeSpan.FromMilliseconds(250)
79+
};
80+
81+
slideOut.Completed += (s, e) =>
82+
{
83+
Closed?.Invoke(this, EventArgs.Empty);
84+
};
85+
86+
SlideTransform.BeginAnimation(TranslateTransform.XProperty, slideOut);
87+
this.BeginAnimation(OpacityProperty, fadeOut);
88+
}
89+
90+
private void SetToastType(ToastType type)
91+
{
92+
switch (type)
93+
{
94+
case ToastType.Success:
95+
IconBorder.Background = new SolidColorBrush(Color.FromRgb(16, 185, 129)); // Green
96+
ToastIcon.Symbol = Wpf.Ui.Common.SymbolRegular.Checkmark24;
97+
break;
98+
99+
case ToastType.Info:
100+
IconBorder.Background = new SolidColorBrush(Color.FromRgb(0, 120, 212)); // Blue
101+
ToastIcon.Symbol = Wpf.Ui.Common.SymbolRegular.Info24;
102+
break;
103+
104+
case ToastType.Warning:
105+
IconBorder.Background = new SolidColorBrush(Color.FromRgb(245, 158, 11)); // Orange
106+
ToastIcon.Symbol = Wpf.Ui.Common.SymbolRegular.Warning24;
107+
break;
108+
109+
case ToastType.Error:
110+
IconBorder.Background = new SolidColorBrush(Color.FromRgb(239, 68, 68)); // Red
111+
ToastIcon.Symbol = Wpf.Ui.Common.SymbolRegular.ErrorCircle24;
112+
break;
113+
}
114+
}
115+
116+
private void OnCloseClick(object sender, RoutedEventArgs e)
117+
{
118+
Hide();
119+
}
120+
}
121+
}

0 commit comments

Comments
 (0)