Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion Services/Screenshot/ScreenshotManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ public ScreenshotItem AddScreenshotWithMetadata(BitmapSource bitmap, string cate
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
TrayIconManager.Instance.ShowNotification(
ToastNotificationManager.Instance.ShowSuccess(
"Upload Complete",
$"Screenshot uploaded to {result.ProviderName}. URL copied to clipboard.");
});
Expand Down
136 changes: 136 additions & 0 deletions Services/ToastNotificationManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using PrettyScreenSHOT.Views.Controls;

namespace PrettyScreenSHOT.Services
{
public class ToastNotificationManager
{
private static ToastNotificationManager? _instance;
public static ToastNotificationManager Instance => _instance ??= new ToastNotificationManager();

private Grid? toastContainer;
private readonly List<ToastNotification> activeToasts = new();
private const int MaxToasts = 5;
private const int ToastSpacing = 8;

private ToastNotificationManager()
{
}

public void Initialize(Grid container)
{
toastContainer = container;
}

public void ShowSuccess(string title, string message, int durationMs = 4000)
{
ShowToast(title, message, ToastNotification.ToastType.Success, durationMs);
}

public void ShowInfo(string title, string message, int durationMs = 4000)
{
ShowToast(title, message, ToastNotification.ToastType.Info, durationMs);
}

public void ShowWarning(string title, string message, int durationMs = 4000)
{
ShowToast(title, message, ToastNotification.ToastType.Warning, durationMs);
}

public void ShowError(string title, string message, int durationMs = 5000)
{
ShowToast(title, message, ToastNotification.ToastType.Error, durationMs);
}

private void ShowToast(string title, string message, ToastNotification.ToastType type, int durationMs)
{
if (toastContainer == null)
{
// Fallback to window-based toast if container not initialized
ShowToastInWindow(title, message, type, durationMs);
return;
}

Application.Current.Dispatcher.Invoke(() =>
{
// Limit number of toasts
if (activeToasts.Count >= MaxToasts)
{
// Remove oldest toast
var oldest = activeToasts.First();
oldest.Hide();
}

var toast = new ToastNotification();
toast.Closed += (s, e) =>
{
activeToasts.Remove(toast);
toastContainer.Children.Remove(toast);
RepositionToasts();
};

// Position toast
toast.HorizontalAlignment = HorizontalAlignment.Right;
toast.VerticalAlignment = VerticalAlignment.Top;
toast.Margin = new Thickness(0, CalculateTopMargin(), 0, 0);

toastContainer.Children.Add(toast);
activeToasts.Add(toast);

toast.Show(title, message, type, durationMs);
});
}

private double CalculateTopMargin()
{
double margin = 20; // Initial top margin
foreach (var toast in activeToasts)
{
margin += toast.ActualHeight + ToastSpacing;
}
return margin;
}

private void RepositionToasts()
{
double margin = 20;
foreach (var toast in activeToasts)
{
toast.Margin = new Thickness(0, margin, 0, 0);
margin += toast.ActualHeight + ToastSpacing;
}
}

private void ShowToastInWindow(string title, string message, ToastNotification.ToastType type, int durationMs)
{
// Create a standalone window for the toast
var toastWindow = new Window
{
WindowStyle = WindowStyle.None,
AllowsTransparency = true,
Background = System.Windows.Media.Brushes.Transparent,
Topmost = true,
ShowInTaskbar = false,
SizeToContent = SizeToContent.WidthAndHeight,
WindowStartupLocation = WindowStartupLocation.Manual
};

// Position at bottom-right of screen
var workingArea = SystemParameters.WorkArea;
toastWindow.Left = workingArea.Right - 420; // 400px toast + 20px margin
toastWindow.Top = workingArea.Bottom - 200; // Adjust based on toast height

var toast = new ToastNotification();
toast.Closed += (s, e) => toastWindow.Close();

toastWindow.Content = toast;
toastWindow.Show();

toast.Show(title, message, type, durationMs);
}
}
}
9 changes: 8 additions & 1 deletion Services/TrayIconManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,17 @@ private void CreateTrayIcon()
var dashboardItem = new ToolStripMenuItem("Dashboard", null, ShowDashboard);
var editItem = new ToolStripMenuItem(LocalizationHelper.GetString("Menu_EditLastScreenshot"), null, EditLastScreenshot);
var historyItem = new ToolStripMenuItem(LocalizationHelper.GetString("Menu_History"), null, ShowHistory);
var uploadHistoryItem = new ToolStripMenuItem("Historia uploadów", null, ShowUploadHistory);
var scrollCaptureItem = new ToolStripMenuItem("Scroll Capture", null, StartScrollCapture);
var videoCaptureItem = new ToolStripMenuItem("Video Capture", null, StartVideoCapture);
var checkUpdateItem = new ToolStripMenuItem("Check for Updates", null, CheckForUpdates);
var settingsItem = new ToolStripMenuItem(LocalizationHelper.GetString("Menu_Settings"), null, ShowSettings);
var exitItem = new ToolStripMenuItem(LocalizationHelper.GetString("Menu_Exit"), null, ExitApplication);

contextMenu.Items.Add(dashboardItem);
contextMenu.Items.Add(new ToolStripSeparator());
contextMenu.Items.Add(editItem);
contextMenu.Items.Add(historyItem);
contextMenu.Items.Add(uploadHistoryItem);
contextMenu.Items.Add(new ToolStripSeparator());
contextMenu.Items.Add(scrollCaptureItem);
contextMenu.Items.Add(videoCaptureItem);
Expand Down Expand Up @@ -178,6 +179,12 @@ private void ShowHistory(object? sender, EventArgs? e)
historyWindow.Show();
}

private void ShowUploadHistory(object? sender, EventArgs? e)
{
var uploadHistoryWindow = new UploadHistoryWindow();
uploadHistoryWindow.Show();
}

private void ShowSettings(object? sender, EventArgs? e)
{
var settingsWindow = new SettingsWindow();
Expand Down
99 changes: 99 additions & 0 deletions Views/Controls/ToastNotification.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<UserControl x:Class="PrettyScreenSHOT.Views.Controls.ToastNotification"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
MaxWidth="400">
<Border x:Name="ToastBorder"
Background="#F0F0F0"
CornerRadius="8"
Padding="16,12"
Margin="12"
BorderThickness="1"
BorderBrush="#E0E0E0"
RenderTransformOrigin="1,0">
<Border.Effect>
<DropShadowEffect BlurRadius="20"
ShadowDepth="4"
Opacity="0.3"
Color="#000000"/>
</Border.Effect>
<Border.RenderTransform>
<TranslateTransform x:Name="SlideTransform" X="0" Y="0"/>
</Border.RenderTransform>

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>

<!-- Icon -->
<Border Grid.Column="0"
x:Name="IconBorder"
Width="40"
Height="40"
CornerRadius="20"
Background="#0078D4"
Margin="0,0,12,0"
VerticalAlignment="Top">
<ui:SymbolIcon x:Name="ToastIcon"
Symbol="Checkmark24"
Foreground="White"
FontSize="20"/>
</Border>

<!-- Content -->
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock x:Name="TitleText"
Text="Notification Title"
FontSize="14"
FontWeight="SemiBold"
Foreground="#202020"
TextWrapping="Wrap"
Margin="0,0,0,4"/>
<TextBlock x:Name="MessageText"
Text="Notification message goes here"
FontSize="12"
Foreground="#606060"
TextWrapping="Wrap"/>
</StackPanel>

<!-- Close Button -->
<Button Grid.Column="2"
x:Name="CloseButton"
Click="OnCloseClick"
Width="24"
Height="24"
Padding="0"
Background="Transparent"
BorderThickness="0"
VerticalAlignment="Top"
Cursor="Hand"
Margin="8,0,0,0">
<ui:SymbolIcon Symbol="Dismiss24" FontSize="14" Foreground="#808080"/>
<Button.Style>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="12"
Padding="{TemplateBinding Padding}">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#20000000"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</Border>
</UserControl>
121 changes: 121 additions & 0 deletions Views/Controls/ToastNotification.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace PrettyScreenSHOT.Views.Controls
{
public partial class ToastNotification : UserControl
{
public event EventHandler? Closed;

public enum ToastType
{
Success,
Info,
Warning,
Error
}

public ToastNotification()
{
InitializeComponent();
}

public void Show(string title, string message, ToastType type = ToastType.Success, int durationMs = 4000)
{
TitleText.Text = title;
MessageText.Text = message;
SetToastType(type);

// Slide in animation
var slideIn = new DoubleAnimation
{
From = 400,
To = 0,
Duration = TimeSpan.FromMilliseconds(300),
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
};

var fadeIn = new DoubleAnimation
{
From = 0,
To = 1,
Duration = TimeSpan.FromMilliseconds(300)
};

SlideTransform.BeginAnimation(TranslateTransform.XProperty, slideIn);
this.BeginAnimation(OpacityProperty, fadeIn);

// Auto-hide timer
var hideTimer = new System.Windows.Threading.DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(durationMs)
};
hideTimer.Tick += (s, e) =>
{
hideTimer.Stop();
Hide();
};
hideTimer.Start();
}

public void Hide()
{
var slideOut = new DoubleAnimation
{
From = 0,
To = 400,
Duration = TimeSpan.FromMilliseconds(250),
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn }
};

var fadeOut = new DoubleAnimation
{
From = 1,
To = 0,
Duration = TimeSpan.FromMilliseconds(250)
};

slideOut.Completed += (s, e) =>
{
Closed?.Invoke(this, EventArgs.Empty);
};

SlideTransform.BeginAnimation(TranslateTransform.XProperty, slideOut);
this.BeginAnimation(OpacityProperty, fadeOut);
}

private void SetToastType(ToastType type)
{
switch (type)
{
case ToastType.Success:
IconBorder.Background = new SolidColorBrush(Color.FromRgb(16, 185, 129)); // Green
ToastIcon.Symbol = Wpf.Ui.Common.SymbolRegular.Checkmark24;
break;

case ToastType.Info:
IconBorder.Background = new SolidColorBrush(Color.FromRgb(0, 120, 212)); // Blue
ToastIcon.Symbol = Wpf.Ui.Common.SymbolRegular.Info24;
break;

case ToastType.Warning:
IconBorder.Background = new SolidColorBrush(Color.FromRgb(245, 158, 11)); // Orange
ToastIcon.Symbol = Wpf.Ui.Common.SymbolRegular.Warning24;
break;

case ToastType.Error:
IconBorder.Background = new SolidColorBrush(Color.FromRgb(239, 68, 68)); // Red
ToastIcon.Symbol = Wpf.Ui.Common.SymbolRegular.ErrorCircle24;
break;
}
}

private void OnCloseClick(object sender, RoutedEventArgs e)
{
Hide();
}
}
}
Loading
Loading