Skip to content

Commit

Permalink
Implement custom icon file selection
Browse files Browse the repository at this point in the history
Related to #11
  • Loading branch information
timokoessler committed Apr 1, 2024
1 parent 87fb439 commit 8c02ce6
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 14 deletions.
55 changes: 54 additions & 1 deletion Guard/Core/Icons/IconManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal class TotpIcon
public required IconType Type { get; set; }
public required string Name { get; set; }
public string? Svg { get; set; }
public string? Path { get; set; }
public Uri? Path { get; set; }
}

private static readonly string customIconsPath = Path.Combine(
Expand Down Expand Up @@ -60,6 +60,16 @@ public static TotpIcon GetIcon(string name, IconType type)
return simpleIcon ?? defaultIcon;
}

if (type == IconType.Custom)
{
return new TotpIcon
{
Type = IconType.Custom,
Name = name,
Path = new Uri(Path.Combine(customIconsPath, name)),
};
}

return defaultIcon;
}

Expand All @@ -72,5 +82,48 @@ public static string GetLicense(TotpIcon icon)
}
return string.Empty;
}

public static async Task<string> AddCustomIcon(string path)
{
if (!Directory.Exists(customIconsPath))
{
Directory.CreateDirectory(customIconsPath);
}

if (!File.Exists(path))
{
throw new FileNotFoundException("The source file does not exist.");
}

if (new FileInfo(path).Length > 1000000)
{
throw new Exception(I18n.GetString("i.td.customicon.tolarge"));
}

string id = Guid.NewGuid().ToString();
string ext =
Path.GetExtension(path)
?? throw new Exception("The file extension could not be determined.");
string[] allowedExtensions = [".svg", ".png", ".jpg", ".jpeg"];
if (!allowedExtensions.Contains(ext))
{
throw new Exception("The file extension is not allowed.");
}

string destPath = Path.Combine(customIconsPath, $"{id}{ext}");

await Task.Run(() => File.Copy(path, destPath));

return $"{id}{ext}";
}

public static void RemoveCustomIcon(string name)
{
string path = Path.Combine(customIconsPath, name);
if (File.Exists(path))
{
File.Delete(path);
}
}
}
}
3 changes: 3 additions & 0 deletions Guard/Resources/Strings.de.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@
<system:String x:Key="i.td.invalidsecret">Der geheime Schlüssel ist ungültig</system:String>
<system:String x:Key="i.td.invaliddigits">Die Anzahl der Ziffern ist ungültig</system:String>
<system:String x:Key="i.td.invalidperiod">Die Gültigkeitsdauer ist ungültig</system:String>
<system:String x:Key="i.td.customicon.btn">Eigenes Icon</system:String>
<system:String x:Key="i.td.customicon.dialog.title">Eigenes Icon auswählen</system:String>
<system:String x:Key="i.td.customicon.tolarge">Ein Icon darf maximal 1 MB groß sein</system:String>

<!-- Token Success Page -->
<system:String x:Key="i.page.tokensuccesspage">Token</system:String>
Expand Down
3 changes: 3 additions & 0 deletions Guard/Resources/Strings.en.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@
<system:String x:Key="i.td.invaliddigits">The number of digits is invalid</system:String>
<system:String x:Key="i.td.invalidperiod">The validity period is invalid</system:String>
<system:String x:Key="i.settings.license">License</system:String>
<system:String x:Key="i.td.customicon.btn">Custom icon</system:String>
<system:String x:Key="i.td.customicon.dialog.title">Select an icon</system:String>
<system:String x:Key="i.td.customicon.tolarge">Only images with a maximum size of 1MB are supported</system:String>

<!-- Token Success Page -->
<system:String x:Key="i.page.tokensuccesspage">Token</system:String>
Expand Down
2 changes: 1 addition & 1 deletion Guard/Views/Controls/TokenCard.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
Margin="22,0,0,0"
VerticalAlignment="Center">
<svgc:SvgViewbox x:Name="SvgIconView" />
<ui:Image x:Name="ImageIconView" />
<ui:Image x:Name="ImageIconView" Visibility="Collapsed" />
</StackPanel>

<StackPanel
Expand Down
32 changes: 31 additions & 1 deletion Guard/Views/Controls/TokenCard.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,29 @@ internal TokenCard(TOTPTokenHelper token)
token.dBToken.IconType ?? IconManager.IconType.Any
);

SvgIconView.SvgSource = icon.Svg;
if (icon.Type != IconManager.IconType.Custom)
{
SvgIconView.SvgSource = icon.Svg;
}
else
{
if (icon.Path != null && icon.Path.AbsolutePath.EndsWith(".svg"))
{
SvgIconView.Source = icon.Path;
}
else
{
ImageIconView.Visibility = Visibility.Visible;
SvgIconView.Visibility = Visibility.Collapsed;

var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.UriSource = icon.Path;
bitmap.EndInit();
ImageIconView.Source = bitmap;
}
}
}
else
{
Expand Down Expand Up @@ -181,6 +203,14 @@ private async void MenuItem_Delete_Click(object sender, RoutedEventArgs e)
if (result == Wpf.Ui.Controls.MessageBoxResult.Primary)
{
TokenManager.DeleteTokenById(token.dBToken.Id);
if (
token.dBToken.Icon != null
&& token.dBToken.IconType != null
&& token.dBToken.IconType == IconManager.IconType.Custom
)
{
IconManager.RemoveCustomIcon(token.dBToken.Icon);
}
if (mainWindow.GetActivePage()?.Name != "Home")
{
mainWindow.Navigate(typeof(Home));
Expand Down
16 changes: 12 additions & 4 deletions Guard/Views/Pages/Add/TokenSettings.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@
<StackPanel
Grid.Column="0"
Width="200"
Margin="0,0,20,0">
<svgc:SvgViewbox
x:Name="IconSvgView"
Margin="0,0,25,0">
<StackPanel
Grid.Column="0"
Width="60"
Margin="0,80,0,25"
HorizontalAlignment="Center" />
HorizontalAlignment="Center">
<svgc:SvgViewbox x:Name="IconSvgView" />
<ui:Image x:Name="ImageIconView" Visibility="Collapsed" />
</StackPanel>
<ui:TextBlock
x:Name="NoIconText"
Opacity="0.7"
Expand All @@ -38,6 +40,12 @@
x:Name="ImageLicense"
Opacity="0.7"
TextWrapping="Wrap" />
<ui:Button
Margin="0,15,0,0"
HorizontalAlignment="Center"
Click="CustomIcon_Click"
Content="{DynamicResource i.td.customicon.btn}"
Icon="{ui:SymbolIcon ImageAdd24}" />
</StackPanel>
<StackPanel
Grid.Column="1"
Expand Down
111 changes: 104 additions & 7 deletions Guard/Views/Pages/Add/TokenSettings.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
using System.IO;
using System.Drawing;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media.Imaging;
using Guard.Core;
using Guard.Core.Icons;
using Guard.Core.Import;
using Guard.Core.Models;
using Guard.Core.Security;
using Guard.Views.UIComponents;
using Microsoft.Win32;
using Wpf.Ui.Controls;

namespace Guard.Views.Pages.Add
Expand Down Expand Up @@ -73,9 +76,18 @@ public TokenSettings()
existingToken.dBToken.Icon,
existingToken.dBToken.IconType ?? IconManager.IconType.Any
);
IconSvgView.SvgSource = selectedIcon.Svg;
ImageLicense.Text = IconManager.GetLicense(selectedIcon);

NoIconText.Visibility = Visibility.Collapsed;

if (selectedIcon.Type == IconManager.IconType.Custom)
{
showCustomImage();
}
else
{
IconSvgView.SvgSource = selectedIcon.Svg;
ImageLicense.Text = IconManager.GetLicense(selectedIcon);
}
}
if (existingToken.dBToken.EncryptedUsername != null)
{
Expand Down Expand Up @@ -151,7 +163,16 @@ AutoSuggestBoxSuggestionChosenEventArgs args
if (args.SelectedItem is not string selectedSuggestBoxItem)
return;

if (selectedIcon != null && selectedIcon.Type == IconManager.IconType.Custom)
{
IconManager.RemoveCustomIcon(selectedIcon.Name);
}

ImageIconView.Source = null;
ImageIconView.Visibility = Visibility.Collapsed;
IconSvgView.Visibility = Visibility.Visible;
selectedIcon = IconManager.GetIcon(selectedSuggestBoxItem, IconManager.IconType.Any);
IconSvgView.Source = null;
IconSvgView.SvgSource = selectedIcon.Svg;
ImageLicense.Text = IconManager.GetLicense(selectedIcon);
NoIconText.Visibility = Visibility.Collapsed;
Expand All @@ -162,12 +183,19 @@ private void AutoSuggestBoxOnTextChanged(
AutoSuggestBoxTextChangedEventArgs args
)
{
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
if (args.Reason != AutoSuggestionBoxTextChangeReason.UserInput)
{
IconSvgView.SvgSource = defaultIcon.Svg;
ImageLicense.Text = string.Empty;
NoIconText.Visibility = Visibility.Visible;
return;
}

if (selectedIcon != null && selectedIcon.Type == IconManager.IconType.Custom)
{
return;
}

IconSvgView.SvgSource = defaultIcon.Svg;
ImageLicense.Text = string.Empty;
NoIconText.Visibility = Visibility.Visible;
}

private void FormatButton_Click(object sender, RoutedEventArgs e)
Expand Down Expand Up @@ -332,5 +360,74 @@ private void ExpertSettings_Button_Click(object sender, RoutedEventArgs e)
DigitsBox.IsEnabled = true;
PeriodBox.IsEnabled = true;
}

private async void CustomIcon_Click(object sender, RoutedEventArgs e)
{
try
{
OpenFileDialog openFileDialog =
new()
{
Filter = "Icon (*.jpg, *.jpeg, *.png, *.svg) | *.jpg; *.jpeg; *.png; *.svg",
Title = I18n.GetString("i.td.customicon.dialog.title")
};
bool? result = openFileDialog.ShowDialog();
if (result != true)
{
return;
}
if (string.IsNullOrEmpty(openFileDialog.FileName))
{
return;
}

string name = await IconManager.AddCustomIcon(openFileDialog.FileName);

ImageIconView.Source = null;
IconSvgView.SvgSource = null;
IconSvgView.Source = null;

if (selectedIcon != null && selectedIcon.Type == IconManager.IconType.Custom)
{
IconManager.RemoveCustomIcon(selectedIcon.Name);
}

selectedIcon = IconManager.GetIcon(name, IconManager.IconType.Custom);

showCustomImage();
}
catch (Exception ex)
{
ShowEror(ex.Message);
}
}

private void showCustomImage()
{
if (selectedIcon == null || selectedIcon.Path == null)
{
return;
}
if (selectedIcon.Path.AbsolutePath.EndsWith(".svg"))
{
ImageIconView.Visibility = Visibility.Collapsed;
IconSvgView.Visibility = Visibility.Visible;
IconSvgView.Source = selectedIcon.Path;
}
else
{
IconSvgView.Visibility = Visibility.Collapsed;
ImageIconView.Visibility = Visibility.Visible;

var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.UriSource = selectedIcon.Path;
bitmap.EndInit();
ImageIconView.Source = bitmap;
}
ImageLicense.Text = string.Empty;
NoIconText.Visibility = Visibility.Collapsed;
}
}
}

0 comments on commit 8c02ce6

Please sign in to comment.